mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	Merge branch 'master' of github.com:laurivosandi/certidude
This commit is contained in:
		| @@ -44,7 +44,6 @@ class SessionResource(object): | ||||
|     @login_required | ||||
|     @event_source | ||||
|     def on_get(self, req, resp): | ||||
|  | ||||
|         return dict( | ||||
|             user = dict( | ||||
|                 name=req.context.get("user").name, | ||||
|   | ||||
| @@ -12,6 +12,8 @@ KEYWORDS = ( | ||||
|     (u"iPhone", u"iphone"), | ||||
|     (u"iPad", u"ipad"), | ||||
|     (u"Ubuntu", u"ubuntu"), | ||||
|     (u"Fedora", u"fedora"), | ||||
|     (u"Linux", u"linux"), | ||||
| ) | ||||
|  | ||||
| class BundleResource(object): | ||||
| @@ -29,8 +31,15 @@ class BundleResource(object): | ||||
|                 hashlib.sha256(req.user_agent).hexdigest()[:8]) | ||||
|  | ||||
|         logger.info(u"Signing bundle %s for %s", common_name, req.context.get("user")) | ||||
|         if config.BUNDLE_FORMAT == "p12": | ||||
|             resp.set_header("Content-Type", "application/x-pkcs12") | ||||
|             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")) | ||||
|  | ||||
|         elif config.BUNDLE_FORMAT == "ovpn": | ||||
|             resp.set_header("Content-Type", "application/x-openvpn") | ||||
|             resp.set_header("Content-Disposition", "attachment; filename=%s.ovpn" % common_name.encode("ascii")) | ||||
|             resp.body, cert = authority.generate_ovpn_bundle(common_name, | ||||
|                 owner=req.context.get("user")) | ||||
|         else: | ||||
|             raise ValueError("Unknown bundle format %s" % config.BUNDLE_FORMAT) | ||||
|   | ||||
| @@ -122,9 +122,9 @@ def authenticate(optional=False): | ||||
|             import ldap | ||||
|  | ||||
|             if not req.auth: | ||||
|                 resp.append_header("WWW-Authenticate", "Basic") | ||||
|                 raise falcon.HTTPUnauthorized("Forbidden", | ||||
|                     "Please authenticate with %s domain account or supply UPN" % const.DOMAIN) | ||||
|                 raise falcon.HTTPUnauthorized("Unauthorized", | ||||
|                     "No authentication header provided", | ||||
|                     ("Basic",)) | ||||
|  | ||||
|             if not req.auth.startswith("Basic "): | ||||
|                 raise falcon.HTTPForbidden("Forbidden", "Bad header: %s" % req.auth) | ||||
| @@ -133,26 +133,35 @@ def authenticate(optional=False): | ||||
|             basic, token = req.auth.split(" ", 1) | ||||
|             user, passwd = b64decode(token).split(":", 1) | ||||
|  | ||||
|             for server in config.LDAP_SERVERS: | ||||
|                 click.echo("Connecting to %s as %s" % (server, user)) | ||||
|                 conn = ldap.initialize(server) | ||||
|             click.echo("Connecting to %s as %s" % (config.LDAP_AUTHENTICATION_URI, user)) | ||||
|             conn = ldap.initialize(config.LDAP_AUTHENTICATION_URI) | ||||
|             conn.set_option(ldap.OPT_REFERRALS, 0) | ||||
|  | ||||
|             if "@" not in user: | ||||
|                 user = "%s@%s" % (user, const.DOMAIN) | ||||
|                 logger.debug("Expanded username to %s", user) | ||||
|  | ||||
|             try: | ||||
|                     conn.simple_bind_s(user if "@" in user else "%s@%s" % (user, const.DOMAIN), passwd) | ||||
|                 except ldap.LDAPError, e: | ||||
|                     resp.append_header("WWW-Authenticate", "Basic") | ||||
|                 conn.simple_bind_s(user, passwd) | ||||
|             except ldap.STRONG_AUTH_REQUIRED: | ||||
|                 logger.critical("LDAP server demands encryption, use ldaps:// instead of ldaps://") | ||||
|                 raise | ||||
|             except ldap.SERVER_DOWN: | ||||
|                 logger.critical("Failed to connect LDAP server at %s, are you sure LDAP server's CA certificate has been copied to this machine?", | ||||
|                     config.LDAP_AUTHENTICATION_URI) | ||||
|                 raise | ||||
|             except ldap.INVALID_CREDENTIALS: | ||||
|                 logger.critical(u"LDAP bind authentication failed for user %s from  %s", | ||||
|                     repr(user), req.context.get("remote_addr")) | ||||
|                 raise falcon.HTTPUnauthorized("Forbidden", | ||||
|                         "Please authenticate with %s domain account or supply UPN" % const.DOMAIN) | ||||
|                     "Please authenticate with %s domain account or supply UPN" % const.DOMAIN, | ||||
| 		("Basic",)) | ||||
|  | ||||
|             req.context["ldap_conn"] = conn | ||||
|                 break | ||||
|             else: | ||||
|                 raise ValueError("No LDAP servers!") | ||||
|  | ||||
|             req.context["user"] = User.objects.get(user) | ||||
|             return func(resource, req, resp, *args, **kwargs) | ||||
|             retval = func(resource, req, resp, *args, **kwargs) | ||||
|             conn.unbind_s() | ||||
|             return retval | ||||
|  | ||||
|  | ||||
|         def pam_authenticate(resource, req, resp, *args, **kwargs): | ||||
|   | ||||
| @@ -14,6 +14,7 @@ from cryptography.hazmat.primitives import hashes, serialization | ||||
| from certidude import config, push, mailer, const | ||||
| from certidude.wrappers import Certificate, Request | ||||
| from certidude import errors | ||||
| from jinja2 import Template | ||||
|  | ||||
| RE_HOSTNAME =  "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])(@(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]))?$" | ||||
|  | ||||
| @@ -21,7 +22,6 @@ RE_HOSTNAME =  "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z | ||||
| # https://jamielinux.com/docs/openssl-certificate-authority/ | ||||
| # http://pycopia.googlecode.com/svn/trunk/net/pycopia/ssl/certs.py | ||||
|  | ||||
|  | ||||
| # Cache CA certificate | ||||
| certificate = Certificate(open(config.AUTHORITY_CERTIFICATE_PATH)) | ||||
|  | ||||
| @@ -185,6 +185,39 @@ def delete_request(common_name): | ||||
|     requests.delete(config.PUSH_PUBLISH % request.fingerprint(), | ||||
|         headers={"User-Agent": "Certidude API"}) | ||||
|  | ||||
| def generate_ovpn_bundle(common_name, owner=None): | ||||
|     # Construct private key | ||||
|     click.echo("Generating 4096-bit RSA key...") | ||||
|  | ||||
|     key = rsa.generate_private_key( | ||||
|         public_exponent=65537, | ||||
|         key_size=4096, | ||||
|         backend=default_backend() | ||||
|     ) | ||||
|  | ||||
|     csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ | ||||
|         x509.NameAttribute(k, v) for k, v in ( | ||||
|             (NameOID.COMMON_NAME, common_name), | ||||
|             (NameOID.GIVEN_NAME, owner and owner.given_name), | ||||
|             (NameOID.SURNAME, owner and owner.surname), | ||||
|         ) if v | ||||
|     ])) | ||||
|  | ||||
|     # Sign CSR | ||||
|     cert = sign(Request( | ||||
|         csr.sign(key, hashes.SHA512(), default_backend()).public_bytes(serialization.Encoding.PEM)), overwrite=True) | ||||
|  | ||||
|     bundle = Template(open(config.OPENVPN_BUNDLE_TEMPLATE).read()).render( | ||||
|         ca = certificate.dump(), | ||||
|         key = key.private_bytes( | ||||
|             encoding=serialization.Encoding.PEM, | ||||
|             format=serialization.PrivateFormat.TraditionalOpenSSL, | ||||
|             encryption_algorithm=serialization.NoEncryption() | ||||
|         ), | ||||
|         cert = cert.dump(), | ||||
|         crl=export_crl(), | ||||
|     ) | ||||
|     return bundle, cert | ||||
|  | ||||
| def generate_pkcs12_bundle(common_name, key_size=4096, owner=None): | ||||
|     """ | ||||
|   | ||||
| @@ -18,8 +18,10 @@ AUTHENTICATION_BACKENDS = set([j for j in | ||||
| AUTHORIZATION_BACKEND = cp.get("authorization", "backend")  # whitelist, ldap, posix | ||||
| ACCOUNTS_BACKEND = cp.get("accounts", "backend")             # posix, ldap | ||||
|  | ||||
| if ACCOUNTS_BACKEND == "ldap": | ||||
| LDAP_AUTHENTICATION_URI = cp.get("authentication", "ldap uri") | ||||
| LDAP_GSSAPI_CRED_CACHE = cp.get("accounts", "ldap gssapi credential cache") | ||||
| LDAP_ACCOUNTS_URI = cp.get("accounts", "ldap uri") | ||||
| LDAP_BASE = cp.get("accounts", "ldap base") | ||||
|  | ||||
| USER_SUBNETS = set([ipaddress.ip_network(j) for j in | ||||
|     cp.get("authorization", "user subnets").split(" ") if j]) | ||||
| @@ -38,6 +40,9 @@ SIGNED_DIR = cp.get("authority", "signed dir") | ||||
| REVOKED_DIR = cp.get("authority", "revoked dir") | ||||
| OUTBOX = cp.get("authority", "outbox") | ||||
|  | ||||
| BUNDLE_FORMAT = cp.get("authority", "bundle format") | ||||
| OPENVPN_BUNDLE_TEMPLATE = cp.get("authority", "openvpn bundle template") | ||||
|  | ||||
| USER_CERTIFICATE_ENROLLMENT = { | ||||
|     "forbidden": False, "single allowed": True, "multiple allowed": True }[ | ||||
|     cp.get("authority", "user certificate enrollment")] | ||||
| @@ -78,17 +83,4 @@ elif "ldap" == AUTHORIZATION_BACKEND: | ||||
| else: | ||||
|     raise NotImplementedError("Unknown authorization backend '%s'" % AUTHORIZATION_BACKEND) | ||||
|  | ||||
| for line in open("/etc/ldap/ldap.conf"): | ||||
|     line = line.strip().lower() | ||||
|     if "#" in line: | ||||
|         line, _ = line.split("#", 1) | ||||
|     if not " " in line: | ||||
|         continue | ||||
|     key, value = line.split(" ", 1) | ||||
|     if key == "uri": | ||||
|         LDAP_SERVERS = set([j for j in value.split(" ") if j]) | ||||
|         click.echo("LDAP servers: %s" % " ".join(LDAP_SERVERS)) | ||||
|     elif key == "base": | ||||
|         LDAP_BASE = value | ||||
|  | ||||
| # TODO: Check if we don't have base or servers | ||||
|   | ||||
| @@ -9,11 +9,12 @@ backends = pam | ||||
| ;backends = ldap | ||||
| ;backends = kerberos ldap | ||||
| ;backends = kerberos pam | ||||
| ldap uri = ldaps://dc1.example.com | ||||
|  | ||||
| [accounts] | ||||
| # The accounts backend specifies how the user's given name, surname and e-mail | ||||
| # address are looked up. In case of 'posix' basically 'getent passwd' is performed, | ||||
| # in case of 'ldap' a search is performed on LDAP server specified in /etc/ldap/ldap.conf | ||||
| # in case of 'ldap' a search is performed on LDAP server specified by ldap uri | ||||
| # with Kerberos credential cache initialized at path specified by environment variable KRB5CCNAME | ||||
| # If certidude setup authority was performed correctly the credential cache should be | ||||
| # updated automatically by /etc/cron.hourly/certidude | ||||
| @@ -21,6 +22,8 @@ backends = pam | ||||
| backend = posix | ||||
| ;backend = ldap | ||||
| ldap gssapi credential cache = /run/certidude/krb5cc | ||||
| ldap uri = ldap://dc1.example.com | ||||
| ldap base = {% if base %}{{ base }}{% else %}dc=example,dc=com{% endif %} | ||||
|  | ||||
| [authorization] | ||||
| # The authorization backend specifies how the users are authorized. | ||||
| @@ -92,3 +95,8 @@ revoked dir = {{ directory }}/revoked/ | ||||
| expired dir = {{ directory }}/expired/ | ||||
| outbox = {{ outbox }} | ||||
|  | ||||
| bundle format = p12 | ||||
| ;bundle format = ovpn | ||||
|  | ||||
| openvpn bundle template = /etc/certidude/template.ovpn | ||||
|  | ||||
|   | ||||
| @@ -70,17 +70,15 @@ class DirectoryConnection(object): | ||||
|             raise ValueError("Ticket cache at %s not initialized, unable to " | ||||
|                 "authenticate with computer account against LDAP server!" % config.LDAP_GSSAPI_CRED_CACHE) | ||||
|         os.environ["KRB5CCNAME"] = config.LDAP_GSSAPI_CRED_CACHE | ||||
|         for server in config.LDAP_SERVERS: | ||||
|             self.conn = ldap.initialize(server) | ||||
|         self.conn = ldap.initialize(config.LDAP_ACCOUNTS_URI) | ||||
|         self.conn.set_option(ldap.OPT_REFERRALS, 0) | ||||
|         click.echo("Connecing to %s using Kerberos ticket cache from %s" % | ||||
|                 (server, config.LDAP_GSSAPI_CRED_CACHE)) | ||||
|             (config.LDAP_ACCOUNTS_URI, config.LDAP_GSSAPI_CRED_CACHE)) | ||||
|         self.conn.sasl_interactive_bind_s('', ldap.sasl.gssapi()) | ||||
|         return self.conn | ||||
|         raise ValueError("No LDAP servers specified!") | ||||
|  | ||||
|     def __exit__(self, type, value, traceback): | ||||
|         self.conn.unbind_s | ||||
|         self.conn.unbind_s() | ||||
|  | ||||
|  | ||||
| class ActiveDirectoryUserManager(object): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user