|
| 1 | +#!/usr/bin/python2 |
| 2 | + |
| 3 | +from __future__ import print_function |
| 4 | + |
| 5 | +import ldap |
| 6 | +import ldap.filter |
| 7 | +from OpenSSL import crypto |
| 8 | +import sys |
| 9 | + |
| 10 | +# Validate arguments |
| 11 | +if len(sys.argv) < 3: |
| 12 | + exit('usage: gencsr-pony LOCKER HOSTNAME [HOSTNAME...]') |
| 13 | + |
| 14 | +[progname, locker], hostnames = sys.argv[:2], sys.argv[2:] |
| 15 | + |
| 16 | +if any(hostname for hostname in hostnames if '.' not in hostname): |
| 17 | + exit('error: Hostnames must be fully qualified') |
| 18 | + |
| 19 | +# Connect to LDAP |
| 20 | +ll = ldap.initialize('ldapi://%2fvar%2frun%2fslapd-scripts.socket/') |
| 21 | +with open('/etc/signup-ldap-pw') as pw_file: |
| 22 | + ll.simple_bind_s('cn=Directory Manager', pw_file.read()) |
| 23 | + |
| 24 | +# Verify hostname existence and ownership |
| 25 | +locker_dn = ldap.dn.dn2str([[('uid', locker, 1)], [('ou', 'People', 1)], [('dc', 'scripts', 1)], [('dc', 'mit', 1)], [('dc', 'edu', 1)]]) |
| 26 | +search_hostnames = set(hostnames) |
| 27 | +while search_hostnames: |
| 28 | + res = ll.search_s( |
| 29 | + 'ou=VirtualHosts,dc=scripts,dc=mit,dc=edu', |
| 30 | + ldap.SCOPE_SUBTREE, |
| 31 | + ldap.filter.filter_format( |
| 32 | + '(&(objectClass=scriptsVhost)(|' + |
| 33 | + '(scriptsVhostName=%s)' * len(search_hostnames) + |
| 34 | + '(scriptsVhostAlias=%s)' * len(search_hostnames) + |
| 35 | + '))', |
| 36 | + list(search_hostnames) * 2), |
| 37 | + ['scriptsVhostName', 'scriptsVhostAlias', 'scriptsVhostAccount']) |
| 38 | + search_hostnames -= {h for cn, attrs in res if attrs['scriptsVhostAccount'] == [locker_dn] for h in attrs['scriptsVhostName'] + attrs.get('scriptsVhostAlias', [])} |
| 39 | + if '*' in search_hostnames or search_hostnames & {h for cn, attrs in res for h in attrs['scriptsVhostName'] + attrs.get('scriptsVhostAlias', [])}: |
| 40 | + exit('error: Hostnames must exist and be owned by the specified locker') |
| 41 | + |
| 42 | + # Strip one hostname component and try again with wildcards (foo.bar.baz -> *.bar.baz -> *.baz -> *) |
| 43 | + search_hostnames = {'.'.join(['*'] + hostname.split('.')[1 + hostname.startswith('*.'):]) for hostname in search_hostnames} |
| 44 | + |
| 45 | +# Create a CSR |
| 46 | +req = crypto.X509Req() |
| 47 | + |
| 48 | +subject = req.get_subject() |
| 49 | +subject.countryName = 'US' |
| 50 | +subject.stateOrProvinceName = 'Massachusetts' |
| 51 | +subject.localityName = 'Cambridge' |
| 52 | +subject.organizationName = 'Massachusetts Institute of Technology' |
| 53 | +subject.organizationalUnitName = 'scripts.mit.edu web hosting service' |
| 54 | +subject.CN = hostnames[0] |
| 55 | + |
| 56 | +req.add_extensions([ |
| 57 | + crypto.X509Extension('basicConstraints', False, 'CA:FALSE'), |
| 58 | + crypto.X509Extension('keyUsage', False, 'nonRepudiation, digitalSignature, keyEncipherment'), |
| 59 | + crypto.X509Extension('subjectAltName', False, ', '.join('DNS:' + hostname for hostname in hostnames)), |
| 60 | +]) |
| 61 | + |
| 62 | +# Add the private key, and sign the CSR |
| 63 | +with open('/etc/pki/tls/private/scripts-2048.key') as key_file: |
| 64 | + private_key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_file.read()) |
| 65 | + |
| 66 | +req.set_pubkey(private_key) |
| 67 | +req.sign(private_key, 'sha256') |
| 68 | + |
| 69 | +print(end=crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) |
0 commit comments