#!/usr/bin/python2 from __future__ import print_function import ldap import ldap.filter from OpenSSL import crypto import sys # Validate arguments if len(sys.argv) < 3: exit('usage: gencsr-pony LOCKER HOSTNAME [HOSTNAME...]') [progname, locker], hostnames = sys.argv[:2], sys.argv[2:] if any(hostname for hostname in hostnames if '.' not in hostname): exit('error: Hostnames must be fully qualified') # Connect to LDAP ll = ldap.initialize('ldapi://%2fvar%2frun%2fslapd-scripts.socket/') with open('/etc/signup-ldap-pw') as pw_file: ll.simple_bind_s('cn=Directory Manager', pw_file.read()) # Verify hostname existence and ownership locker_dn = ldap.dn.dn2str([[('uid', locker, 1)], [('ou', 'People', 1)], [('dc', 'scripts', 1)], [('dc', 'mit', 1)], [('dc', 'edu', 1)]]) search_hostnames = set(hostnames) while search_hostnames: res = ll.search_s( 'ou=VirtualHosts,dc=scripts,dc=mit,dc=edu', ldap.SCOPE_SUBTREE, ldap.filter.filter_format( '(&(objectClass=scriptsVhost)(|' + '(scriptsVhostName=%s)' * len(search_hostnames) + '(scriptsVhostAlias=%s)' * len(search_hostnames) + '))', list(search_hostnames) * 2), ['scriptsVhostName', 'scriptsVhostAlias', 'scriptsVhostAccount']) search_hostnames -= {h for cn, attrs in res if attrs['scriptsVhostAccount'] == [locker_dn] for h in attrs['scriptsVhostName'] + attrs.get('scriptsVhostAlias', [])} if '*' in search_hostnames or search_hostnames & {h for cn, attrs in res for h in attrs['scriptsVhostName'] + attrs.get('scriptsVhostAlias', [])}: exit('error: Hostnames must exist and be owned by the specified locker') # Strip one hostname component and try again with wildcards (foo.bar.baz -> *.bar.baz -> *.baz -> *) search_hostnames = {'.'.join(['*'] + hostname.split('.')[1 + hostname.startswith('*.'):]) for hostname in search_hostnames} # Create a CSR req = crypto.X509Req() subject = req.get_subject() subject.countryName = 'US' subject.stateOrProvinceName = 'Massachusetts' subject.localityName = 'Cambridge' subject.organizationName = 'Massachusetts Institute of Technology' subject.organizationalUnitName = 'scripts.mit.edu web hosting service' subject.CN = hostnames[0] req.add_extensions([ crypto.X509Extension('basicConstraints', False, 'CA:FALSE'), crypto.X509Extension('keyUsage', False, 'nonRepudiation, digitalSignature, keyEncipherment'), crypto.X509Extension('subjectAltName', False, ', '.join('DNS:' + hostname for hostname in hostnames)), ]) # Add the private key, and sign the CSR with open('/etc/pki/tls/private/scripts-2048.key') as key_file: private_key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_file.read()) req.set_pubkey(private_key) req.sign(private_key, 'sha256') print(end=crypto.dump_certificate_request(crypto.FILETYPE_PEM, req))