PASSWORD can be provided as SHA256 optionally (Issue #100)

This commit is contained in:
Daniel Perna 2018-07-10 01:00:15 +02:00
parent 8e3aabb895
commit 2707ba5dcc
2 changed files with 34 additions and 21 deletions

View file

@ -55,7 +55,7 @@ If you plan on using the restart button, you have to set your API password. Call
#### USERNAME (string) #### USERNAME (string)
If you want to enable [HTTP basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) you can set the desired username here. The `:` character is not allowed. If you want to enable [HTTP basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) you can set the desired username here. The `:` character is not allowed.
#### PASSWORD (string) #### PASSWORD (string)
Set the password that should be used for authentication. Only if `USERNAME` __and__ `PASSWORD` are set authentication will be enabled. Set the password that should be used for authentication. Only if `USERNAME` __and__ `PASSWORD` are set authentication will be enabled. You may provide the password as a SHA256-hash with the prefix `{sha256}`. For example `PASSWORD = "test"` is functionally equal to `PASSWORD = "{sha256}9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"`. The hash will be converted to lower case automatically. Using the hash provides extra security by not exposing the actual password in plaintext in your configuration.
#### CREDENTIALS (string) #### CREDENTIALS (string)
The credentials in the form of `"username:password"` are now deprecated and should be removed from you configuration. Replace it by specifying `USERNAME` and `PASSWORD`. It will still work though to ensure backwards compatibility. The credentials in the form of `"username:password"` are now deprecated and should be removed from you configuration. Replace it by specifying `USERNAME` and `PASSWORD`. It will still work though to ensure backwards compatibility.
#### ALLOWED_NETWORKS (list) #### ALLOWED_NETWORKS (list)

View file

@ -18,6 +18,7 @@ import shlex
import subprocess import subprocess
import logging import logging
import fnmatch import fnmatch
import hashlib
from string import Template from string import Template
from http.server import BaseHTTPRequestHandler from http.server import BaseHTTPRequestHandler
import urllib.request import urllib.request
@ -3443,6 +3444,8 @@ def load_settings(settingsfile):
if CREDENTIALS and (USERNAME is None or PASSWORD is None): if CREDENTIALS and (USERNAME is None or PASSWORD is None):
USERNAME = CREDENTIALS.split(":")[0] USERNAME = CREDENTIALS.split(":")[0]
PASSWORD = ":".join(CREDENTIALS.split(":")[1:]) PASSWORD = ":".join(CREDENTIALS.split(":")[1:])
if PASSWORD and PASSWORD.startswith("{sha256}"):
PASSWORD = PASSWORD.lower()
def is_safe_path(basedir, path, follow_symlinks=True): def is_safe_path(basedir, path, follow_symlinks=True):
if basedir is None: if basedir is None:
@ -4498,18 +4501,24 @@ class AuthHandler(RequestHandler):
if not verify_hostname(self.headers.get('Host', '')): if not verify_hostname(self.headers.get('Host', '')):
self.do_BLOCK(403, "Forbidden") self.do_BLOCK(403, "Forbidden")
return return
authorization = self.headers.get('Authorization', None) header = self.headers.get('Authorization', None)
token = base64.b64encode(bytes("%s:%s" % (USERNAME, PASSWORD), "utf-8")) if header is None:
if authorization is None:
self.do_AUTHHEAD() self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8')) self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % token.decode('utf-8'):
if BANLIMIT:
FAIL2BAN_IPS.pop(self.client_address[0], None)
super().do_GET()
pass
else: else:
authorization = header.split()
if len(authorization) == 2 and authorization[0] == "Basic":
plain = base64.b64decode(authorization[1]).decode("utf-8")
parts = plain.split(':')
username = parts[0]
password = ":".join(parts[1:])
if PASSWORD.startswith("{sha256}"):
password = "{sha256}%s" % hashlib.sha256(password.encode("utf-8")).hexdigest()
if username == USERNAME and password == PASSWORD:
if BANLIMIT:
FAIL2BAN_IPS.pop(self.client_address[0], None)
super().do_GET()
return
if BANLIMIT: if BANLIMIT:
bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1) bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1)
if bancounter >= BANLIMIT: if bancounter >= BANLIMIT:
@ -4520,24 +4529,29 @@ class AuthHandler(RequestHandler):
FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1 FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1
self.do_AUTHHEAD() self.do_AUTHHEAD()
self.wfile.write(bytes('Authentication required', 'utf-8')) self.wfile.write(bytes('Authentication required', 'utf-8'))
pass
def do_POST(self): def do_POST(self):
if not verify_hostname(self.headers.get('Host', '')): if not verify_hostname(self.headers.get('Host', '')):
self.do_BLOCK(403, "Forbidden") self.do_BLOCK(403, "Forbidden")
return return
authorization = self.headers.get('Authorization', None) header = self.headers.get('Authorization', None)
token = base64.b64encode(bytes("%s:%s" % (USERNAME, PASSWORD), "utf-8")) if header is None:
if authorization is None:
self.do_AUTHHEAD() self.do_AUTHHEAD()
self.wfile.write(bytes('no auth header received', 'utf-8')) self.wfile.write(bytes('no auth header received', 'utf-8'))
pass
elif authorization == 'Basic %s' % token.decode('utf-8'):
if BANLIMIT:
FAIL2BAN_IPS.pop(self.client_address[0], None)
super().do_POST()
pass
else: else:
authorization = header.split()
if len(authorization) == 2 and authorization[0] == "Basic":
plain = base64.b64decode(authorization[1]).decode("utf-8")
parts = plain.split(':')
username = parts[0]
password = ":".join(parts[1:])
if PASSWORD.startswith("{sha256}"):
password = "{sha256}%s" % hashlib.sha256(password.encode("utf-8")).hexdigest()
if username == USERNAME and password == PASSWORD:
if BANLIMIT:
FAIL2BAN_IPS.pop(self.client_address[0], None)
super().do_POST()
return
if BANLIMIT: if BANLIMIT:
bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1) bancounter = FAIL2BAN_IPS.get(self.client_address[0], 1)
if bancounter >= BANLIMIT: if bancounter >= BANLIMIT:
@ -4548,7 +4562,6 @@ class AuthHandler(RequestHandler):
FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1 FAIL2BAN_IPS[self.client_address[0]] = bancounter + 1
self.do_AUTHHEAD() self.do_AUTHHEAD()
self.wfile.write(bytes('Authentication required', 'utf-8')) self.wfile.write(bytes('Authentication required', 'utf-8'))
pass
class SimpleServer(socketserver.ThreadingMixIn, socketserver.TCPServer): class SimpleServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
daemon_threads = True daemon_threads = True