diff --git a/README.rst b/README.rst index 9043bf5..f062394 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,10 @@ Introduction ------------ Certidude is a novel X.509 Certificate Authority management tool -with privilege isolation mechanism aiming to +with privilege isolation mechanism and Kerberos authentication aiming to eventually support PKCS#11 and in far future WebCrypto. +.. figure:: doc/usecase-diagram.png Features -------- @@ -106,19 +107,19 @@ Use web interface or following to sign a certificate on Certidude server: Production deployment --------------------- -Install uWSGI: +Install ``nginx`` and ``uwsgi``: .. code:: bash apt-get install nginx uwsgi uwsgi-plugin-python3 -To set up ``nginx`` and ``uwsgi`` is suggested: +For easy setup following is reccommended: .. code:: bash certidude setup production -Otherwise manually configure uUWSGI application in ``/etc/uwsgi/apps-available/certidude.ini``: +Otherwise manually configure ``uwsgi`` application in ``/etc/uwsgi/apps-available/certidude.ini``: .. code:: ini @@ -136,8 +137,12 @@ Otherwise manually configure uUWSGI application in ``/etc/uwsgi/apps-available/c callable = app chmod-socket = 660 chown-socket = certidude:www-data + buffer-size = 32768 env = PUSH_PUBLISH=http://localhost/event/publish/%(channel)s env = PUSH_SUBSCRIBE=http://localhost/event/subscribe/%(channel)s + env = LANG=C.UTF-8 + env = LC_ALL=C.UTF-8 + env = KRB5_KTNAME=/etc/certidude.keytab Also enable the application: @@ -272,11 +277,11 @@ to generate user whitelist via LDAP: .. code:: bash - ldapsearch -H ldap://dc1.id.stipit.com -s sub -x -LLL \ - -D 'cn=certidude,cn=Users,dc=id,dc=stipit,dc=com' \ + ldapsearch -H ldap://dc1.example.com -s sub -x -LLL \ + -D 'cn=certidude,cn=Users,dc=example,dc=com' \ -w 'certidudepass' \ - -b 'ou=sso,dc=id,dc=stipit,dc=com' \ - '(objectClass=user)' sAMAccountName userPrincipalName givenName sn \ + -b 'dc=example,dc=com' \ + '(&(objectClass=user)(memberOf=cn=Domain Admins,cn=Users,dc=example,dc=com))' sAMAccountName userPrincipalName givenName sn \ | python3 -c "import ldif3; import sys; [sys.stdout.write('%s:%s:%s:%s\n' % (a.pop('sAMAccountName')[0], a.pop('userPrincipalName')[0], a.pop('givenName')[0], a.pop('sn')[0])) for _, a in ldif3.LDIFParser(sys.stdin.buffer).parse()]" \ > /run/certidude/user.whitelist diff --git a/certidude/api.py b/certidude/api.py index 62843c9..702d698 100644 --- a/certidude/api.py +++ b/certidude/api.py @@ -1,5 +1,6 @@ import re import falcon +import ipaddress import os import json import types @@ -23,9 +24,25 @@ def omit(**kwargs): def authorize_admin(func): def wrapped(self, req, resp, *args, **kwargs): + authority = kwargs.get("ca") + + # Parse remote IPv4/IPv6 address + remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"]) + + # Check for administration subnet whitelist + print("Comparing:", authority.admin_subnets, "To:", remote_addr) + for subnet in authority.admin_subnets: + if subnet.overlaps(remote_addr): + break + else: + raise falcon.HTTPForbidden("Forbidden", "Remote address %s not whitelisted" % remote_addr) + + # Check for username whitelist kerberos_username, kerberos_realm = kwargs.get("user") - if kerberos_username not in kwargs.get("ca").admin_users: - raise falcon.HTTPForbidden("User %s not whitelisted" % kerberos_username) + if kerberos_username not in authority.admin_users: + raise falcon.HTTPForbidden("Forbidden", "User %s not whitelisted" % kerberos_username) + + # Retain username, TODO: Better abstraction with username, e-mail, sn, gn? kwargs["user"] = kerberos_username return func(self, req, resp, *args, **kwargs) return wrapped @@ -34,7 +51,6 @@ def authorize_admin(func): def pop_certificate_authority(func): def wrapped(self, req, resp, *args, **kwargs): kwargs["ca"] = self.config.instantiate_authority(kwargs["ca"]) - print(func) return func(self, req, resp, *args, **kwargs) return wrapped @@ -86,7 +102,6 @@ def templatize(path): def wrapper(func): def wrapped(instance, req, resp, *args, **kwargs): assert not req.get_param("unicode") or req.get_param("unicode") == u"✓", "Unicode sanity check failed" - print("templatize would call", func, "with", args, kwargs) r = func(instance, req, resp, *args, **kwargs) r.pop("self") if not resp.body: @@ -212,7 +227,7 @@ class RequestListResource(CertificateAuthorityBase): Submit certificate signing request (CSR) in PEM format """ # Parse remote IPv4/IPv6 address - remote_addr = ipaddress.ip_address(req.env["REMOTE_ADDR"]) + remote_addr = ipaddress.ip_network(req.env["REMOTE_ADDR"]) # Check for CSR submission whitelist if ca.request_subnets: @@ -220,7 +235,7 @@ class RequestListResource(CertificateAuthorityBase): if subnet.overlaps(remote_addr): break else: - raise falcon.HTTPForbidden("IP address %s not whitelisted" % remote_addr) + raise falcon.HTTPForbidden("Forbidden", "IP address %s not whitelisted" % remote_addr) if req.get_header("Content-Type") != "application/pkcs10": raise falcon.HTTPUnsupportedMediaType( @@ -308,6 +323,7 @@ class CertificateAuthorityResource(CertificateAuthorityBase): class IndexResource(CertificateAuthorityBase): @login_required @pop_certificate_authority + @authorize_admin @templatize("index.html") def on_get(self, req, resp, ca, user): return locals() diff --git a/doc/usecase-diagram.dia b/doc/usecase-diagram.dia new file mode 100644 index 0000000..e3db949 Binary files /dev/null and b/doc/usecase-diagram.dia differ diff --git a/doc/usecase-diagram.png b/doc/usecase-diagram.png new file mode 100644 index 0000000..c01efe5 Binary files /dev/null and b/doc/usecase-diagram.png differ