summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOliver Poignant <oliver@poignant.se>2017-01-07 11:13:10 +0100
committerOliver Poignant <oliver@poignant.se>2017-01-07 11:13:10 +0100
commit6dfc5094495bda3c513ca461da863a25ce04907d (patch)
tree779bce3ca6d29da5863ba7923341ef814caaf319
parentf4b4c6c160a84bb23523cc4cc8946b4b9c9e281f (diff)
downloadGit-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.zip
Git-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.tar.gz
Git-Auto-Deploy-6dfc5094495bda3c513ca461da863a25ce04907d.tar.bz2
Basic auth for web UI
-rwxr-xr-xconfig.json.sample4
-rw-r--r--gitautodeploy/cli/config.py2
-rwxr-xr-xgitautodeploy/data/git-auto-deploy.conf.json2
-rw-r--r--gitautodeploy/httpserver.py154
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