From b20b47b100b2716273a5abfe2850e994c1d3e69d Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Fri, 2 Oct 2015 21:30:35 -0400 Subject: Add forwarder plugin This pugin allows to mangle and forward requests to another custodia server, locally or on the network. Signed-off-by: Simo Sorce Reviewed-by: Christian Heimes --- custodia.conf | 14 ++++++++++ custodia/forwarder.py | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ tests/custodia.py | 24 ++++++++++++++++- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 custodia/forwarder.py diff --git a/custodia.conf b/custodia.conf index 1a987df..c77cf8b 100644 --- a/custodia.conf +++ b/custodia.conf @@ -81,3 +81,17 @@ handler = custodia.root.Secrets allowed_keytypes = simple kem store = encrypted +# Forward +[authz:forwarders] +handler = custodia.httpd.authorizers.SimplePathAuthz +paths = /forwarder /forwarder_loop + +[/forwarder] +handler = custodia.forwarder.Forwarder +forward_uri = http+unix://%2e%2fserver_socket/secrets +forward_headers = {"CUSTODIA_AUTH_ID": "test", "CUSTODIA_AUTH_KEY": "foo-host-key"} + +[/forwarder_loop] +handler = custodia.forwarder.Forwarder +forward_uri = http+unix://%2e%2fserver_socket/forwarder_loop +forward_headers = {"REMOTE_USER": "test"} diff --git a/custodia/forwarder.py b/custodia/forwarder.py new file mode 100644 index 0000000..03fcfef --- /dev/null +++ b/custodia/forwarder.py @@ -0,0 +1,72 @@ +# Copyright (C) 2015 Custodia Project Contributors - see LICENSE file + +import json +import uuid + +from custodia import log +from custodia.client import CustodiaHTTPClient +from custodia.httpd.consumer import HTTPConsumer +from custodia.httpd.server import HTTPError + + +class Forwarder(HTTPConsumer): + + def __init__(self, *args, **kwargs): + super(Forwarder, self).__init__(*args, **kwargs) + self._auditlog = log.AuditLog(self.config) + self.client = CustodiaHTTPClient(self.config['forward_uri']) + self.headers = json.loads(self.config.get('forward_headers', '{}')) + self.uuid = str(uuid.uuid4()) + self.headers['X-LOOP-CUSTODIA'] = self.uuid + + def _path(self, request): + trail = request.get('trail', []) + prefix = request.get('remote_user', 'guest') + return '/'.join([prefix.rstrip('/')] + trail) + + def _headers(self, request): + headers = {} + headers.update(self.headers) + loop = request['headers'].get('X-LOOP-CUSTODIA', None) + if loop is not None: + headers['X-LOOP-CUSTODIA'] += ',' + loop + return headers + + def _response(self, reply, response): + if reply.status_code < 200 or reply.status_code > 299: + raise HTTPError(reply.status_code) + response['code'] = reply.status_code + if reply.content: + response['output'] = reply.content + + def _request(self, cmd, request, response, path, **kwargs): + if self.uuid in request['headers'].get('X-LOOP-CUSTODIA', ''): + raise HTTPError(502, "Loop detected") + reply = cmd(path, **kwargs) + self._response(reply, response) + + def GET(self, request, response): + self._request(self.client.get, request, response, + self._path(request), + params=request.get('query', None), + headers=self._headers(request)) + + def PUT(self, request, response): + self._request(self.client.put, request, response, + self._path(request), + data=request.get('body', None), + params=request.get('query', None), + headers=self._headers(request)) + + def DELETE(self, request, response): + self._request(self.client.delete, request, response, + self._path(request), + params=request.get('query', None), + headers=self._headers(request)) + + def POST(self, request, response): + self._request(self.client.post, request, response, + self._path(request), + data=request.get('body', None), + params=request.get('query', None), + headers=self._headers(request)) diff --git a/tests/custodia.py b/tests/custodia.py index 54563d5..ed80010 100644 --- a/tests/custodia.py +++ b/tests/custodia.py @@ -30,6 +30,8 @@ class CustodiaTests(unittest.TestCase): time.sleep(1) cls.client = CustodiaClient('http+unix://%2E%2Fserver_socket/secrets') cls.client.headers['REMOTE_USER'] = 'test' + cls.fwd = CustodiaClient('http+unix://%2E%2Fserver_socket/forwarder') + cls.fwd.headers['REMOTE_USER'] = 'test' @classmethod def tearDownClass(cls): @@ -68,9 +70,29 @@ class CustodiaTests(unittest.TestCase): r = self.client.list_container('test') self.assertEqual(r.json(), []) - def test_6_delete_container(self): + def test_6_create_forwarded_container(self): + self.fwd.create_container('dir') + r = self.client.list_container('test/dir') + self.assertEqual(r.json(), []) + + def test_7_delete_forwarded_container(self): + self.fwd.delete_container('dir') + try: + self.client.list_container('test/dir') + except HTTPError as e: + self.assertEqual(e.response.status_code, 404) + + def test_8_delete_container(self): self.client.delete_container('test') try: self.client.list_container('test') except HTTPError as e: self.assertEqual(e.response.status_code, 404) + + def test_9_loop(self): + loop = CustodiaClient('http+unix://%2E%2Fserver_socket/forwarder_loop') + loop.headers['REMOTE_USER'] = 'test' + try: + loop.list_container('test') + except HTTPError as e: + self.assertEqual(e.response.status_code, 502) -- cgit