PASSWORD can be provided as SHA256 optionally (Issue #100)
This commit is contained in:
parent
8e3aabb895
commit
2707ba5dcc
2 changed files with 34 additions and 21 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue