mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +00:00 
			
		
		
		
	Refactor users, add OpenVPN and mailing support
* Add abstraction for user objects * Mail authority admins about pending, revoked and signed certificates * Add NetworkManager's OpenVPN plugin support * Improve CRL support * Refactor CSRF protection * Update documentation
This commit is contained in:
		| @@ -9,6 +9,7 @@ from datetime import datetime | ||||
| from time import sleep | ||||
| from certidude import authority, mailer | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.user import User | ||||
| from certidude.decorators import serialize, event_source, csrf_protection | ||||
| from certidude.wrappers import Request, Certificate | ||||
| from certidude import constants, config | ||||
| @@ -33,34 +34,16 @@ class CertificateAuthorityResource(object): | ||||
|         logger.info("Served CA certificate to %s", req.context.get("remote_addr")) | ||||
|         resp.stream = open(config.AUTHORITY_CERTIFICATE_PATH, "rb") | ||||
|         resp.append_header("Content-Type", "application/x-x509-ca-cert") | ||||
|         resp.append_header("Content-Disposition", "attachment; filename=ca.crt") | ||||
|         resp.append_header("Content-Disposition", "attachment; filename=%s.crt" % | ||||
|             constants.HOSTNAME.encode("ascii")) | ||||
|  | ||||
|  | ||||
| class SessionResource(object): | ||||
|     @csrf_protection | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     @event_source | ||||
|     def on_get(self, req, resp): | ||||
|         if config.ACCOUNTS_BACKEND == "ldap": | ||||
|             import ldap | ||||
|             ft = config.LDAP_MEMBERS_FILTER % (config.ADMINS_GROUP, "*") | ||||
|             r = req.context.get("ldap_conn").search_s(config.LDAP_BASE, | ||||
|                     ldap.SCOPE_SUBTREE, ft.encode("utf-8"), ["cn", "member"]) | ||||
|  | ||||
|             for dn,entry in r: | ||||
|                 cn, = entry.get("cn") | ||||
|                 break | ||||
|             else: | ||||
|                 raise ValueError("Failed to look up group %s in LDAP" % repr(group_name)) | ||||
|  | ||||
|             admins = dict([(j, j.split(",")[0].split("=")[1]) for j in entry.get("member")]) | ||||
|         elif config.ACCOUNTS_BACKEND == "posix": | ||||
|             import grp | ||||
|             _, _, gid, members = grp.getgrnam(config.ADMINS_GROUP) | ||||
|             admins = dict([(j, j) for j in members]) | ||||
|         else: | ||||
|             raise NotImplementedError("Authorization backend %s not supported" % config.AUTHORIZATION_BACKEND) | ||||
|  | ||||
|         return dict( | ||||
|             user = dict( | ||||
| @@ -72,12 +55,6 @@ class SessionResource(object): | ||||
|             request_submission_allowed = sum( # Dirty hack! | ||||
|                 [req.context.get("remote_addr") in j | ||||
|                     for j in config.REQUEST_SUBNETS]), | ||||
|             user_subnets = config.USER_SUBNETS, | ||||
|             autosign_subnets = config.AUTOSIGN_SUBNETS, | ||||
|             request_subnets = config.REQUEST_SUBNETS, | ||||
|             admin_subnets=config.ADMIN_SUBNETS, | ||||
|             admin_users = admins, | ||||
|             #admin_users=config.ADMIN_USERS, | ||||
|             authority = dict( | ||||
|                 outbox = config.OUTBOX, | ||||
|                 certificate = authority.certificate, | ||||
| @@ -85,7 +62,12 @@ class SessionResource(object): | ||||
|                 requests=authority.list_requests(), | ||||
|                 signed=authority.list_signed(), | ||||
|                 revoked=authority.list_revoked(), | ||||
|             ) if config.ADMINS_GROUP in req.context.get("groups") else None, | ||||
|                 admin_users = User.objects.filter_admins(), | ||||
|                 user_subnets = config.USER_SUBNETS, | ||||
|                 autosign_subnets = config.AUTOSIGN_SUBNETS, | ||||
|                 request_subnets = config.REQUEST_SUBNETS, | ||||
|                 admin_subnets=config.ADMIN_SUBNETS, | ||||
|             ) if req.context.get("user").is_admin() else None, | ||||
|             features=dict( | ||||
|                 tagging=config.TAGGING_BACKEND, | ||||
|                 leases=False, #config.LEASES_BACKEND, | ||||
| @@ -124,7 +106,7 @@ class BundleResource(object): | ||||
|         common_name = req.context["user"].mail | ||||
|         logger.info("Signing bundle %s for %s", common_name, req.context.get("user")) | ||||
|         resp.set_header("Content-Type", "application/x-pkcs12") | ||||
|         resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name) | ||||
|         resp.set_header("Content-Disposition", "attachment; filename=%s.p12" % common_name.encode("ascii")) | ||||
|         resp.body, cert = authority.generate_pkcs12_bundle(common_name, | ||||
|                                 owner=req.context.get("user")) | ||||
|  | ||||
| @@ -132,7 +114,6 @@ class BundleResource(object): | ||||
| import ipaddress | ||||
|  | ||||
| class NormalizeMiddleware(object): | ||||
|     @csrf_protection | ||||
|     def process_request(self, req, resp, *args): | ||||
|         assert not req.get_param("unicode") or req.get_param("unicode") == u"✓", "Unicode sanity check failed" | ||||
|         req.context["remote_addr"] = ipaddress.ip_address(req.env["REMOTE_ADDR"].decode("utf-8")) | ||||
|   | ||||
| @@ -6,7 +6,7 @@ import ipaddress | ||||
| import os | ||||
| from certidude import config, authority, helpers, push, errors | ||||
| from certidude.auth import login_required, login_optional, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
| from certidude.decorators import serialize, csrf_protection | ||||
| from certidude.wrappers import Request, Certificate | ||||
| from certidude.firewall import whitelist_subnets, whitelist_content_types | ||||
|  | ||||
| @@ -19,6 +19,7 @@ class RequestListResource(object): | ||||
|     def on_get(self, req, resp): | ||||
|         return authority.list_requests() | ||||
|  | ||||
|  | ||||
|     @login_optional | ||||
|     @whitelist_subnets(config.REQUEST_SUBNETS) | ||||
|     @whitelist_content_types("application/pkcs10") | ||||
| @@ -53,7 +54,7 @@ class RequestListResource(object): | ||||
|         # Process automatic signing if the IP address is whitelisted and autosigning was requested | ||||
|         if req.get_param_as_bool("autosign"): | ||||
|             for subnet in config.AUTOSIGN_SUBNETS: | ||||
|                 if subnet.overlaps(req.context.get("remote_addr")): | ||||
|                 if req.context.get("remote_addr") in subnet: | ||||
|                     try: | ||||
|                         resp.set_header("Content-Type", "application/x-x509-user-cert") | ||||
|                         resp.body = authority.sign(csr).dump() | ||||
| @@ -103,6 +104,8 @@ class RequestDetailResource(object): | ||||
|             csr.common_name, req.context.get("remote_addr")) | ||||
|         return csr | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_patch(self, req, resp, cn): | ||||
| @@ -118,6 +121,8 @@ class RequestDetailResource(object): | ||||
|         logger.info("Signing request %s signed by %s from %s", csr.common_name, | ||||
|             req.context.get("user"), req.context.get("remote_addr")) | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_delete(self, req, resp, cn): | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import falcon | ||||
| import logging | ||||
| from certidude import authority | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
| from certidude.decorators import serialize, csrf_protection | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| @@ -24,20 +24,21 @@ class SignedCertificateDetailResource(object): | ||||
|         try: | ||||
|             cert = authority.get_signed(cn) | ||||
|         except EnvironmentError: | ||||
|             logger.warning("Failed to serve non-existant certificate %s to %s", | ||||
|             logger.warning(u"Failed to serve non-existant certificate %s to %s", | ||||
|                 cn, req.context.get("remote_addr")) | ||||
|             resp.body = "No certificate CN=%s found" % cn | ||||
|             raise falcon.HTTPNotFound() | ||||
|         else: | ||||
|             logger.debug("Served certificate %s to %s", | ||||
|             logger.debug(u"Served certificate %s to %s", | ||||
|                 cn, req.context.get("remote_addr")) | ||||
|             return cert | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|     def on_delete(self, req, resp, cn): | ||||
|         logger.info("Revoked certificate %s by %s from %s", | ||||
|         logger.info(u"Revoked certificate %s by %s from %s", | ||||
|             cn, req.context.get("user"), req.context.get("remote_addr")) | ||||
|         authority.revoke_certificate(cn) | ||||
|  | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import falcon | ||||
| import logging | ||||
| from certidude.relational import RelationalMixin | ||||
| from certidude.auth import login_required, authorize_admin | ||||
| from certidude.decorators import serialize | ||||
| from certidude.decorators import serialize, csrf_protection | ||||
|  | ||||
| logger = logging.getLogger("api") | ||||
|  | ||||
| @@ -17,6 +17,7 @@ class TagResource(RelationalMixin): | ||||
|         return self.iterfetch("select * from tag") | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
| @@ -51,6 +52,7 @@ class TagDetailResource(RelationalMixin): | ||||
|         raise falcon.HTTPNotFound() | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
| @@ -63,6 +65,7 @@ class TagDetailResource(RelationalMixin): | ||||
|         push.publish("tag-updated", identifier) | ||||
|  | ||||
|  | ||||
|     @csrf_protection | ||||
|     @serialize | ||||
|     @login_required | ||||
|     @authorize_admin | ||||
|   | ||||
		Reference in New Issue
	
	Block a user