diff options
author | Oliver Poignant <oliver@poignant.se> | 2017-01-07 11:13:10 +0100 |
---|---|---|
committer | Oliver Poignant <oliver@poignant.se> | 2017-01-07 11:13:10 +0100 |
commit | 6dfc5094495bda3c513ca461da863a25ce04907d (patch) | |
tree | 779bce3ca6d29da5863ba7923341ef814caaf319 | |
parent | f4b4c6c160a84bb23523cc4cc8946b4b9c9e281f (diff) | |
download | Git-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.zip Git-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.tar.gz Git-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.tar.bz2 |
Basic auth for web UI
-rwxr-xr-x | config.json.sample | 4 | ||||
-rw-r--r-- | gitautodeploy/cli/config.py | 2 | ||||
-rwxr-xr-x | gitautodeploy/data/git-auto-deploy.conf.json | 2 | ||||
-rw-r--r-- | gitautodeploy/httpserver.py | 154 |
4 files changed, 114 insertions, 48 deletions
diff --git a/config.json.sample b/config.json.sample index 5bfbe88..959fe70 100755 --- a/config.json.sample +++ b/config.json.sample @@ -16,6 +16,8 @@ // Web user interface options //"web-ui-enabled": false, + //"web-ui-username": null, + //"web-ui-password": null, //"web-ui-whitelist": ["127.0.0.1"], // TLS/SSL cert (necessary for HTTPS and web socket server to work) @@ -65,4 +67,4 @@ ] } ] -}
\ No newline at end of file +} diff --git a/gitautodeploy/cli/config.py b/gitautodeploy/cli/config.py index f2f279e..4199c11 100644 --- a/gitautodeploy/cli/config.py +++ b/gitautodeploy/cli/config.py @@ -39,6 +39,8 @@ def get_config_defaults(): # Web user interface options config['web-ui-enabled'] = False # Disabled by default until authentication is in place + config['web-ui-username'] = None + config['web-ui-password'] = None config['web-ui-whitelist'] = ['127.0.0.1'] config['web-ui-require-https'] = True diff --git a/gitautodeploy/data/git-auto-deploy.conf.json b/gitautodeploy/data/git-auto-deploy.conf.json index 652ca8a..5f8ebc2 100755 --- a/gitautodeploy/data/git-auto-deploy.conf.json +++ b/gitautodeploy/data/git-auto-deploy.conf.json @@ -16,6 +16,8 @@ // Web user interface options //"web-ui-enabled": false, + //"web-ui-username": null, + //"web-ui-password": null, //"web-ui-whitelist": ["127.0.0.1"], // TLS/SSL cert (necessary for HTTPS and web socket server to work) diff --git a/gitautodeploy/httpserver.py b/gitautodeploy/httpserver.py index ab50ecf..6717ae3 100644 --- a/gitautodeploy/httpserver.py +++ b/gitautodeploy/httpserver.py @@ -10,81 +10,75 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa HTTP requests.""" def __init__(self, *args, **kwargs): - self._config = config - self._event_store = event_store - self._server_status = server_status - self._is_https = is_https - super(WebhookRequestHandler, self).__init__(*args, **kwargs) + self._config = config + self._event_store = event_store + self._server_status = server_status + self._is_https = is_https + super(WebhookRequestHandler, self).__init__(*args, **kwargs) - def end_headers (self): + def end_headers(self): self.send_header('Access-Control-Allow-Origin', '*') SimpleHTTPRequestHandler.end_headers(self) def do_HEAD(self): - import json - if not self._config['web-ui-enabled']: - self.send_error(403, "Web UI is not enabled") + # Web UI needs to be enabled + if not self.validate_web_ui_enabled(): return - if not self._is_https and self._config['web-ui-require-https']: - - # Attempt to redirect the request to HTTPS - server_status = self.get_server_status() - if 'https-uri' in server_status: - self.send_response(307) - self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path)) - self.end_headers() - return + # Web UI might require HTTPS + if not self.validate_web_ui_https(): + return - self.send_error(403, "Web UI is only accessible through HTTPS") + # Client needs to be whitelisted + if not self.validate_web_ui_whitelist(): return - if not self.client_address[0] in self._config['web-ui-whitelist']: - self.send_error(403, "%s is not allowed access" % self.client_address[0]) + # Client needs to authenticate + if not self.validate_web_ui_authentication(): return return SimpleHTTPRequestHandler.do_HEAD(self) def do_GET(self): - import json - if not self._config['web-ui-enabled']: - self.send_error(403, "Web UI is not enabled") + # Web UI needs to be enabled + if not self.validate_web_ui_enabled(): return - if not self._is_https and self._config['web-ui-require-https']: - - # Attempt to redirect the request to HTTPS - server_status = self.get_server_status() - if 'https-uri' in server_status: - self.send_response(307) - self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path)) - self.end_headers() - return + # Web UI might require HTTPS + if not self.validate_web_ui_https(): + return - self.send_error(403, "Web UI is only accessible through HTTPS") + # Client needs to be whitelisted + if not self.validate_web_ui_whitelist(): return - if not self.client_address[0] in self._config['web-ui-whitelist']: - self.send_error(403, "%s is not allowed access" % self.client_address[0]) + # Client needs to authenticate + if not self.validate_web_ui_authentication(): return + # Handle API call if self.path == "/api/status": - data = { - 'events': self._event_store.dict_repr(), - } - - data.update(self.get_server_status()) - - self.send_response(200, 'OK') - self.send_header('Content-type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps(data).encode('utf-8')) + self.handle_status_api() return + # Serve static file return SimpleHTTPRequestHandler.do_GET(self) + def handle_status_api(self): + import json + data = { + 'events': self._event_store.dict_repr(), + } + + data.update(self.get_server_status()) + + self.send_response(200, 'OK') + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(data).encode('utf-8')) + def do_POST(self): """Invoked on incoming POST requests""" from threading import Timer @@ -235,17 +229,83 @@ def WebhookRequestHandlerFactory(config, event_store, server_status, is_https=Fa def get_server_status(self): """Generate a copy of the server status object that contains the public IP or hostname.""" + server_status = {} for item in self._server_status.items(): key, value = item public_host = self.headers.get('host').split(':')[0] + if key == 'http-uri': server_status[key] = value.replace(self._config['http-host'], public_host) + if key == 'https-uri': server_status[key] = value.replace(self._config['https-host'], public_host) + if key == 'wss-uri': server_status[key] = value.replace(self._config['wss-host'], public_host) + return server_status - return WebhookRequestHandler + def validate_web_ui_enabled(self): + """Verify that the Web UI is enabled""" + + if self._config['web-ui-enabled']: + return True + + self.send_error(403, "Web UI is not enabled") + return False + + def validate_web_ui_https(self): + """Verify that the request is made over HTTPS""" + + if self._is_https and self._config['web-ui-require-https']: + return True + + # Attempt to redirect the request to HTTPS + server_status = self.get_server_status() + if 'https-uri' in server_status: + self.send_response(307) + self.send_header('Location', '%s%s' % (server_status['https-uri'], self.path)) + self.end_headers() + return False + self.send_error(403, "Web UI is only accessible through HTTPS") + return False + + def validate_web_ui_whitelist(self): + """Verify that the client address is whitelisted""" + + # Allow all if whitelist is empty + if len(self._config['web-ui-whitelist']) == 0: + return True + + # Verify that client IP is whitelisted + if self.client_address[0] in self._config['web-ui-whitelist']: + return True + + self.send_error(403, "%s is not allowed access" % self.client_address[0]) + return False + + def validate_web_ui_authentication(self): + """Authenticate the user""" + import base64 + + # Verify that a username and password is specified in the config + if self._config['web-ui-username'] is None or self._config['web-ui-password'] is None: + self.send_error(403, "Authentication credentials missing in config") + return False + + # Verify that the provided username and password matches the ones in the config + key = base64.b64encode("%s:%s" % (self._config['web-ui-username'], self._config['web-ui-password'])) + if self.headers.getheader('Authorization') == 'Basic ' + key: + return True + + # Let the client know that authentication is required + self.send_response(401) + self.send_header('WWW-Authenticate', 'Basic realm=\"GAD\"') + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write('Not authenticated') + return False + + return WebhookRequestHandler |