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)
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)
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)
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)

View file

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