mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-30 17:09:19 +00:00 
			
		
		
		
	tests: Better code coverage
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -37,7 +37,7 @@ pip-delete-this-directory.txt | |||||||
| # Unit test / coverage reports | # Unit test / coverage reports | ||||||
| htmlcov/ | htmlcov/ | ||||||
| .tox/ | .tox/ | ||||||
| .coverage | .coverage* | ||||||
| .cache | .cache | ||||||
| nosetests.xml | nosetests.xml | ||||||
| coverage.xml | coverage.xml | ||||||
|   | |||||||
| @@ -10,8 +10,9 @@ virtualenv: | |||||||
| install: | install: | ||||||
|   - echo "127.0.0.1 localhost" | sudo tee /etc/hosts |   - echo "127.0.0.1 localhost" | sudo tee /etc/hosts | ||||||
|   - echo "127.0.1.1 ca.example.lan ca" | sudo tee -a /etc/hosts |   - echo "127.0.1.1 ca.example.lan ca" | sudo tee -a /etc/hosts | ||||||
|   - echo "127.0.0.1 vpn.koodur.lan" | sudo tee -a /etc/hosts |   - echo "127.0.0.1 vpn.example.lan vpn" | sudo tee -a /etc/hosts | ||||||
|   - sudo mkdir -p /etc/systemd/system /etc/NetworkManager/system-connections |   - echo "127.0.0.1 www.example.lan www" | sudo tee -a /etc/hosts | ||||||
|  |   - sudo mkdir -p /etc/systemd/system | ||||||
|   - sudo pip install -r requirements.txt |   - sudo pip install -r requirements.txt | ||||||
|   - sudo pip install codecov pytest-cov |   - sudo pip install codecov pytest-cov | ||||||
|   - sudo pip install -e . |   - sudo pip install -e . | ||||||
|   | |||||||
| @@ -154,7 +154,7 @@ class StaticResource(object): | |||||||
|         else: |         else: | ||||||
|             resp.status = falcon.HTTP_404 |             resp.status = falcon.HTTP_404 | ||||||
|             resp.body = "File '%s' not found" % req.path |             resp.body = "File '%s' not found" % req.path | ||||||
|             logger.info("Faile '%s' not found, path resolved to '%s'", req.path, path) |             logger.info("Fail '%s' not found, path resolved to '%s'", req.path, path) | ||||||
| import ipaddress | import ipaddress | ||||||
|  |  | ||||||
| class NormalizeMiddleware(object): | class NormalizeMiddleware(object): | ||||||
|   | |||||||
| @@ -29,6 +29,7 @@ class RequestListResource(object): | |||||||
|         """ |         """ | ||||||
|         Validate and parse certificate signing request |         Validate and parse certificate signing request | ||||||
|         """ |         """ | ||||||
|  |         reason = "No reason" | ||||||
|         body = req.stream.read(req.content_length) |         body = req.stream.read(req.content_length) | ||||||
|         csr = x509.load_pem_x509_csr(body, default_backend()) |         csr = x509.load_pem_x509_csr(body, default_backend()) | ||||||
|         try: |         try: | ||||||
| @@ -87,14 +88,15 @@ class RequestListResource(object): | |||||||
|                         verifier.verify() |                         verifier.verify() | ||||||
|                     except InvalidSignature: |                     except InvalidSignature: | ||||||
|                         logger.error("Renewal failed, invalid signature supplied for %s", common_name.value) |                         logger.error("Renewal failed, invalid signature supplied for %s", common_name.value) | ||||||
|  |                         reason = "Renewal failed, invalid signature supplied" | ||||||
|                     else: |                     else: | ||||||
|                         # At this point renewal signature was valid but we need to perform some extra checks |                         # At this point renewal signature was valid but we need to perform some extra checks | ||||||
|                         if datetime.utcnow() > cert.not_valid_after: |                         if datetime.utcnow() > cert.not_valid_after: | ||||||
|                             logger.error("Renewal failed, current certificate for %s has expired", common_name.value) |                             logger.error("Renewal failed, current certificate for %s has expired", common_name.value) | ||||||
|                             # Put on hold |                             reason = "Renewal failed, current certificate expired" | ||||||
|                         elif not config.CERTIFICATE_RENEWAL_ALLOWED: |                         elif not config.CERTIFICATE_RENEWAL_ALLOWED: | ||||||
|                             logger.error("Renewal requested for %s, but not allowed by authority settings", common_name.value) |                             logger.error("Renewal requested for %s, but not allowed by authority settings", common_name.value) | ||||||
|                             # Put on hold |                             reason = "Renewal requested, but not allowed by authority settings" | ||||||
|                         else: |                         else: | ||||||
|                             resp.set_header("Content-Type", "application/x-x509-user-cert") |                             resp.set_header("Content-Type", "application/x-x509-user-cert") | ||||||
|                             _, resp.body = authority._sign(csr, body, overwrite=True) |                             _, resp.body = authority._sign(csr, body, overwrite=True) | ||||||
| @@ -106,25 +108,30 @@ class RequestListResource(object): | |||||||
|         Process automatic signing if the IP address is whitelisted, |         Process automatic signing if the IP address is whitelisted, | ||||||
|         autosigning was requested and certificate can be automatically signed |         autosigning was requested and certificate can be automatically signed | ||||||
|         """ |         """ | ||||||
|         if req.get_param_as_bool("autosign") and "." not in common_name.value: |         if req.get_param_as_bool("autosign"): | ||||||
|             for subnet in config.AUTOSIGN_SUBNETS: |             if "." not in common_name.value: | ||||||
|                 if req.context.get("remote_addr") in subnet: |                 reason = "Autosign failed, IP address not whitelisted" | ||||||
|                     try: |                 for subnet in config.AUTOSIGN_SUBNETS: | ||||||
|                         resp.set_header("Content-Type", "application/x-pem-file") |                     if req.context.get("remote_addr") in subnet: | ||||||
|                         _, resp.body = authority._sign(csr, body) |                         try: | ||||||
|                         logger.info("Autosigned %s as %s is whitelisted", common_name.value, req.context.get("remote_addr")) |                             resp.set_header("Content-Type", "application/x-pem-file") | ||||||
|                         return |                             _, resp.body = authority._sign(csr, body) | ||||||
|                     except EnvironmentError: |                             logger.info("Autosigned %s as %s is whitelisted", common_name.value, req.context.get("remote_addr")) | ||||||
|                         logger.info("Autosign for %s failed, signed certificate already exists", |                             return | ||||||
|                             common_name.value, req.context.get("remote_addr")) |                         except EnvironmentError: | ||||||
|                     break |                             logger.info("Autosign for %s failed, signed certificate already exists", | ||||||
|  |                                 common_name.value, req.context.get("remote_addr")) | ||||||
|  |                             reason = "Autosign failed, signed certificate already exists" | ||||||
|  |                         break | ||||||
|  |             else: | ||||||
|  |                 reason = "Autosign failed, only client certificates allowed to be signed automatically" | ||||||
|  |  | ||||||
|         # Attempt to save the request otherwise |         # Attempt to save the request otherwise | ||||||
|         try: |         try: | ||||||
|             csr = authority.store_request(body) |             csr = authority.store_request(body) | ||||||
|         except errors.RequestExists: |         except errors.RequestExists: | ||||||
|  |             reason = "Same request already uploaded exists" | ||||||
|             # We should still redirect client to long poll URL below |             # We should still redirect client to long poll URL below | ||||||
|             pass |  | ||||||
|         except errors.DuplicateCommonNameError: |         except errors.DuplicateCommonNameError: | ||||||
|             # TODO: Certificate renewal |             # TODO: Certificate renewal | ||||||
|             logger.warning(u"Rejected signing request with overlapping common name from %s", |             logger.warning(u"Rejected signing request with overlapping common name from %s", | ||||||
| @@ -147,6 +154,7 @@ class RequestListResource(object): | |||||||
|         else: |         else: | ||||||
|             # Request was accepted, but not processed |             # Request was accepted, but not processed | ||||||
|             resp.status = falcon.HTTP_202 |             resp.status = falcon.HTTP_202 | ||||||
|  |             resp.body = reason | ||||||
|  |  | ||||||
|  |  | ||||||
| class RequestDetailResource(object): | class RequestDetailResource(object): | ||||||
|   | |||||||
							
								
								
									
										268
									
								
								certidude/cli.py
									
									
									
									
									
								
							
							
						
						
									
										268
									
								
								certidude/cli.py
									
									
									
									
									
								
							| @@ -15,7 +15,7 @@ import subprocess | |||||||
| import sys | import sys | ||||||
| from configparser import ConfigParser, NoOptionError, NoSectionError | from configparser import ConfigParser, NoOptionError, NoSectionError | ||||||
| from certidude.helpers import certidude_request_certificate | from certidude.helpers import certidude_request_certificate | ||||||
| from certidude.common import expand_paths, ip_address, ip_network, apt, rpm, pip, drop_privileges | from certidude.common import ip_address, ip_network, apt, rpm, pip, drop_privileges | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| from time import sleep | from time import sleep | ||||||
| import const | import const | ||||||
| @@ -31,6 +31,56 @@ logger = logging.getLogger(__name__) | |||||||
|  |  | ||||||
| NOW = datetime.utcnow().replace(tzinfo=None) | NOW = datetime.utcnow().replace(tzinfo=None) | ||||||
|  |  | ||||||
|  | def setup_client(prefix="client_"): | ||||||
|  |     # Create section in /etc/certidude/client.conf | ||||||
|  |     def wrapper(func): | ||||||
|  |         def wrapped(**arguments): | ||||||
|  |             from certidude import const | ||||||
|  |             common_name = arguments.get("common_name") | ||||||
|  |             authority = arguments.get("authority") | ||||||
|  |             b = os.path.join(const.STORAGE_PATH, authority) | ||||||
|  |  | ||||||
|  |             # Create corresponding section in Certidude client configuration file | ||||||
|  |             client_config = ConfigParser() | ||||||
|  |             if os.path.exists(const.CLIENT_CONFIG_PATH): | ||||||
|  |                 client_config.readfp(open(const.CLIENT_CONFIG_PATH)) | ||||||
|  |             if client_config.has_section(authority): | ||||||
|  |                 click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) | ||||||
|  |             else: | ||||||
|  |                 client_config.add_section(authority) | ||||||
|  |                 client_config.set(authority, "trigger", "interface up") | ||||||
|  |                 client_config.set(authority, "common name", common_name) | ||||||
|  |                 client_config.set(authority, "request path", os.path.join(b, prefix + "req.pem")) | ||||||
|  |                 client_config.set(authority, "key path", os.path.join(b, prefix + "key.pem")) | ||||||
|  |                 client_config.set(authority, "certificate path", os.path.join(b, prefix + "cert.pem")) | ||||||
|  |                 client_config.set(authority, "authority path",  os.path.join(b, "ca_cert.pem")) | ||||||
|  |                 client_config.set(authority, "revocations path",  os.path.join(b, "ca_crl.pem")) | ||||||
|  |                 with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: | ||||||
|  |                     client_config.write(fh) | ||||||
|  |                 os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) | ||||||
|  |                 click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) | ||||||
|  |  | ||||||
|  |             for j in ("key", "request", "certificate", "authority", "revocations"): | ||||||
|  |                 arguments["%s_path" % j] = client_config.get(authority, "%s path" % j) | ||||||
|  |             return func(**arguments) | ||||||
|  |         return wrapped | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def generate_dhparam(path): | ||||||
|  |     # Prevent logjam etc for OpenVPN and nginx server | ||||||
|  |     def wrapper(func): | ||||||
|  |         def wrapped(**arguments): | ||||||
|  |             if not os.path.exists(path): | ||||||
|  |                 rpm("openssl") | ||||||
|  |                 apt("openssl") | ||||||
|  |                 cmd = "openssl", "dhparam", "-out", path, ("1024" if os.getenv("TRAVIS") else "2048") | ||||||
|  |                 subprocess.check_call(cmd) | ||||||
|  |             arguments["dhparam_path"] = path | ||||||
|  |             return func(**arguments) | ||||||
|  |         return wrapped | ||||||
|  |     return wrapper | ||||||
|  |  | ||||||
| @click.command("request", help="Run processes for requesting certificates and configuring services") | @click.command("request", help="Run processes for requesting certificates and configuring services") | ||||||
| @click.option("-r", "--renew", default=False, is_flag=True, help="Renew now") | @click.option("-r", "--renew", default=False, is_flag=True, help="Renew now") | ||||||
| @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") | @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") | ||||||
| @@ -46,15 +96,12 @@ def certidude_request(fork, renew, no_wait): | |||||||
|         click.echo("No %s!" % const.CLIENT_CONFIG_PATH) |         click.echo("No %s!" % const.CLIENT_CONFIG_PATH) | ||||||
|         return 1 |         return 1 | ||||||
|  |  | ||||||
|     if not os.path.exists(const.SERVICES_CONFIG_PATH): |  | ||||||
|         click.echo("No %s!" % const.SERVICES_CONFIG_PATH) |  | ||||||
|         return 1 |  | ||||||
|  |  | ||||||
|     clients = ConfigParser() |     clients = ConfigParser() | ||||||
|     clients.readfp(open(const.CLIENT_CONFIG_PATH)) |     clients.readfp(open(const.CLIENT_CONFIG_PATH)) | ||||||
|  |  | ||||||
|     service_config = ConfigParser() |     service_config = ConfigParser() | ||||||
|     service_config.readfp(open(const.SERVICES_CONFIG_PATH)) |     if os.path.exists(const.SERVICES_CONFIG_PATH): | ||||||
|  |         service_config.readfp(open(const.SERVICES_CONFIG_PATH)) | ||||||
|  |  | ||||||
|     # Process directories |     # Process directories | ||||||
|     if not os.path.exists(const.RUN_DIR): |     if not os.path.exists(const.RUN_DIR): | ||||||
| @@ -76,13 +123,9 @@ def certidude_request(fork, renew, no_wait): | |||||||
|  |  | ||||||
|     for authority in clients.sections(): |     for authority in clients.sections(): | ||||||
|         try: |         try: | ||||||
|             endpoint_dhparam = clients.get(authority, "dhparam path") |             endpoint_renewal_overlap = clients.getint(authority, "renewal overlap") | ||||||
|             if not os.path.exists(endpoint_dhparam): |  | ||||||
|                 cmd = "openssl", "dhparam", "-out", endpoint_dhparam, ("512" if os.getenv("TRAVIS") else "2048") |  | ||||||
|                 subprocess.check_call(cmd) |  | ||||||
|         except NoOptionError: |         except NoOptionError: | ||||||
|             pass |             endpoint_renewal_overlap = None | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             endpoint_insecure = clients.getboolean(authority, "insecure") |             endpoint_insecure = clients.getboolean(authority, "insecure") | ||||||
|         except NoOptionError: |         except NoOptionError: | ||||||
| @@ -157,6 +200,7 @@ def certidude_request(fork, renew, no_wait): | |||||||
|                     endpoint_authority_path, |                     endpoint_authority_path, | ||||||
|                     endpoint_revocations_path, |                     endpoint_revocations_path, | ||||||
|                     endpoint_common_name, |                     endpoint_common_name, | ||||||
|  |                     endpoint_renewal_overlap, | ||||||
|                     insecure=endpoint_insecure, |                     insecure=endpoint_insecure, | ||||||
|                     autosign=True, |                     autosign=True, | ||||||
|                     wait=not no_wait, |                     wait=not no_wait, | ||||||
| @@ -196,7 +240,6 @@ def certidude_request(fork, renew, no_wait): | |||||||
|  |  | ||||||
|             # IPSec set up with initscripts |             # IPSec set up with initscripts | ||||||
|             if service_config.get(endpoint, "service") == "init/strongswan": |             if service_config.get(endpoint, "service") == "init/strongswan": | ||||||
|                 remote = service_config.get(endpoint, "remote") |  | ||||||
|                 from ipsecparse import loads |                 from ipsecparse import loads | ||||||
|                 config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read()) |                 config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read()) | ||||||
|                 for section_type, section_name in config: |                 for section_type, section_name in config: | ||||||
| @@ -286,7 +329,8 @@ def certidude_request(fork, renew, no_wait): | |||||||
|                 with open(nm_config_path, "w") as fh: |                 with open(nm_config_path, "w") as fh: | ||||||
|                     nm_config.write(fh) |                     nm_config.write(fh) | ||||||
|                     click.echo("Created %s" % nm_config_path) |                     click.echo("Created %s" % nm_config_path) | ||||||
|                 os.system("nmcli con reload") |                 if os.path.exists("/run/NetworkManager"): | ||||||
|  |                     os.system("nmcli con reload") | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -323,7 +367,8 @@ def certidude_request(fork, renew, no_wait): | |||||||
|                 with open(os.path.join("/etc/NetworkManager/system-connections", endpoint), "w") as fh: |                 with open(os.path.join("/etc/NetworkManager/system-connections", endpoint), "w") as fh: | ||||||
|                     nm_config.write(fh) |                     nm_config.write(fh) | ||||||
|                     click.echo("Created %s" % fh.name) |                     click.echo("Created %s" % fh.name) | ||||||
|                 os.system("nmcli con reload") |                 if os.path.exists("/run/NetworkManager"): | ||||||
|  |                     os.system("nmcli con reload") | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             # TODO: Puppet, OpenLDAP, <insert awesomeness here> |             # TODO: Puppet, OpenLDAP, <insert awesomeness here> | ||||||
| @@ -343,34 +388,13 @@ def certidude_request(fork, renew, no_wait): | |||||||
|     default="/etc/openvpn/site-to-client.conf", |     default="/etc/openvpn/site-to-client.conf", | ||||||
|     type=click.File(mode="w", atomic=True, lazy=True), |     type=click.File(mode="w", atomic=True, lazy=True), | ||||||
|     help="OpenVPN configuration file") |     help="OpenVPN configuration file") | ||||||
| def certidude_setup_openvpn_server(authority, common_name, config, subnet, route, local, proto, port): | @generate_dhparam("/etc/openvpn/dh.pem") | ||||||
|  | @setup_client(prefix="server_") | ||||||
|  | def certidude_setup_openvpn_server(authority, common_name, config, subnet, route, local, proto, port, **paths): | ||||||
|     # Install dependencies |     # Install dependencies | ||||||
|     apt("openvpn") |     apt("openvpn") | ||||||
|     rpm("openvpn") |     rpm("openvpn") | ||||||
|  |  | ||||||
|     # Create corresponding section in Certidude client configuration file |  | ||||||
|     client_config = ConfigParser() |  | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |  | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |  | ||||||
|     if client_config.has_section(authority): |  | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|     else: |  | ||||||
|         client_config.add_section(authority) |  | ||||||
|         client_config.set(authority, "trigger", "interface up") |  | ||||||
|         client_config.set(authority, "common name", common_name) |  | ||||||
|         slug = common_name.replace(".", "-") |  | ||||||
|         client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % slug) |  | ||||||
|         client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % slug) |  | ||||||
|         client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % slug) |  | ||||||
|         client_config.set(authority, "authority path",  "/etc/openvpn/keys/ca.crt") |  | ||||||
|         client_config.set(authority, "revocations path",  "/etc/openvpn/keys/ca.crl") |  | ||||||
|         client_config.set(authority, "dhparam path", "/etc/openvpn/keys/dhparam.pem") |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/services.conf |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     endpoint = "OpenVPN server %s of %s" % (common_name, authority) |     endpoint = "OpenVPN server %s of %s" % (common_name, authority) | ||||||
|     service_config = ConfigParser() |     service_config = ConfigParser() | ||||||
| @@ -394,17 +418,18 @@ def certidude_setup_openvpn_server(authority, common_name, config, subnet, route | |||||||
|     config.write("proto %s\n" % proto) |     config.write("proto %s\n" % proto) | ||||||
|     config.write("port %d\n" % port) |     config.write("port %d\n" % port) | ||||||
|     config.write("local %s\n" % local) |     config.write("local %s\n" % local) | ||||||
|     config.write("key %s\n" % client_config.get(authority, "key path")) |     config.write("key %s\n" % paths.get("key_path")) | ||||||
|     config.write("cert %s\n" % client_config.get(authority, "certificate path")) |     config.write("cert %s\n" % paths.get("certificate_path")) | ||||||
|     config.write("ca %s\n" % client_config.get(authority, "authority path")) |     config.write("ca %s\n" % paths.get("authority_path")) | ||||||
|     config.write("dh %s\n" % client_config.get(authority, "dhparam path")) |     config.write("crl-verify %s\n" % paths.get("revocations_path")) | ||||||
|  |     config.write("dh %s\n" % paths.get("dhparam_path")) | ||||||
|     config.write("comp-lzo\n") |     config.write("comp-lzo\n") | ||||||
|     config.write("user nobody\n") |     config.write("user nobody\n") | ||||||
|     config.write("group nogroup\n") |     config.write("group nogroup\n") | ||||||
|     config.write("persist-tun\n") |     config.write("persist-tun\n") | ||||||
|     config.write("persist-key\n") |     config.write("persist-key\n") | ||||||
|     config.write("#ifconfig-pool-persist /tmp/openvpn-leases.txt\n") |     config.write("#ifconfig-pool-persist /tmp/openvpn-leases.txt\n") | ||||||
|     config.write("#crl-verify %s\n" % client_config.get(authority, "revocations path")) |  | ||||||
|  |  | ||||||
|     click.echo("Generated %s" % config.name) |     click.echo("Generated %s" % config.name) | ||||||
|     click.echo("Inspect generated files and issue following to request certificate:") |     click.echo("Inspect generated files and issue following to request certificate:") | ||||||
| @@ -423,18 +448,14 @@ def certidude_setup_openvpn_server(authority, common_name, config, subnet, route | |||||||
|     default="/etc/nginx/sites-available/%s.conf" % const.HOSTNAME, |     default="/etc/nginx/sites-available/%s.conf" % const.HOSTNAME, | ||||||
|     type=click.File(mode="w", atomic=True, lazy=True), |     type=click.File(mode="w", atomic=True, lazy=True), | ||||||
|     help="Site configuration file of nginx, /etc/nginx/sites-available/%s.conf by default" % const.HOSTNAME) |     help="Site configuration file of nginx, /etc/nginx/sites-available/%s.conf by default" % const.HOSTNAME) | ||||||
| @click.option("--directory", "-d", default="/etc/nginx/ssl", help="Directory for keys, /etc/nginx/ssl by default") |  | ||||||
| @click.option("--key-path", "-key", default=const.HOSTNAME + ".key", help="Key path, %s.key relative to -d by default" % const.HOSTNAME) |  | ||||||
| @click.option("--request-path", "-csr", default=const.HOSTNAME + ".csr", help="Request path, %s.csr relative to -d by default" % const.HOSTNAME) |  | ||||||
| @click.option("--certificate-path", "-crt", default=const.HOSTNAME + ".crt", help="Certificate path, %s.crt relative to -d by default" % const.HOSTNAME) |  | ||||||
| @click.option("--dhparam-path", "-dh", default="dhparam2048.pem", help="Diffie/Hellman parameters path, dhparam2048.pem relative to -d by default") |  | ||||||
| @click.option("--authority-path", "-ca", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to -d by default") |  | ||||||
| @click.option("--revocations-path", "-crl", default="ca.crl", help="Certificate revocation list, ca.crl relative to -d by default") |  | ||||||
| @click.option("--verify-client", "-vc", default="optional", type=click.Choice(['optional', 'on', 'off'])) | @click.option("--verify-client", "-vc", default="optional", type=click.Choice(['optional', 'on', 'off'])) | ||||||
| @expand_paths() | @generate_dhparam("/etc/nginx/ssl/dh.pem") | ||||||
| def certidude_setup_nginx(authority, site_config, tls_config, common_name, directory, key_path, request_path, certificate_path, authority_path, revocations_path, dhparam_path, verify_client): | @setup_client(prefix="server_") | ||||||
|     if not os.path.exists("/etc/nginx"): | def certidude_setup_nginx(authority, common_name, site_config, tls_config, verify_client, **paths): | ||||||
|         raise ValueError("nginx not installed") |     apt("nginx") | ||||||
|  |     rpm("nginx") | ||||||
|  |     from jinja2 import Environment, PackageLoader | ||||||
|  |     env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) | ||||||
|     if "." not in common_name: |     if "." not in common_name: | ||||||
|         raise ValueError("Fully qualified hostname not specified as common name, make sure hostname -f works") |         raise ValueError("Fully qualified hostname not specified as common name, make sure hostname -f works") | ||||||
|     client_config = ConfigParser() |     client_config = ConfigParser() | ||||||
| @@ -450,7 +471,6 @@ def certidude_setup_nginx(authority, site_config, tls_config, common_name, direc | |||||||
|         client_config.set(authority, "key path", key_path) |         client_config.set(authority, "key path", key_path) | ||||||
|         client_config.set(authority, "certificate path", certificate_path) |         client_config.set(authority, "certificate path", certificate_path) | ||||||
|         client_config.set(authority, "authority path",  authority_path) |         client_config.set(authority, "authority path",  authority_path) | ||||||
|         client_config.set(authority, "dhparam path",  dhparam_path) |  | ||||||
|         client_config.set(authority, "revocations path",  revocations_path) |         client_config.set(authority, "revocations path",  revocations_path) | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: | ||||||
|             client_config.write(fh) |             client_config.write(fh) | ||||||
| @@ -459,6 +479,7 @@ def certidude_setup_nginx(authority, site_config, tls_config, common_name, direc | |||||||
|  |  | ||||||
|     context = globals() # Grab const.BLAH |     context = globals() # Grab const.BLAH | ||||||
|     context.update(locals()) |     context.update(locals()) | ||||||
|  |     context.update(paths) | ||||||
|  |  | ||||||
|     if os.path.exists(site_config.name): |     if os.path.exists(site_config.name): | ||||||
|         click.echo("Configuration file %s already exists, not overwriting" % site_config.name) |         click.echo("Configuration file %s already exists, not overwriting" % site_config.name) | ||||||
| @@ -492,30 +513,12 @@ def certidude_setup_nginx(authority, site_config, tls_config, common_name, direc | |||||||
|     default="/etc/openvpn/client-to-site.conf", |     default="/etc/openvpn/client-to-site.conf", | ||||||
|     type=click.File(mode="w", atomic=True, lazy=True), |     type=click.File(mode="w", atomic=True, lazy=True), | ||||||
|     help="OpenVPN configuration file") |     help="OpenVPN configuration file") | ||||||
| def certidude_setup_openvpn_client(authority, remote, common_name, config, proto): | @setup_client() | ||||||
|  | def certidude_setup_openvpn_client(authority, remote, common_name, config, proto, **ctx): | ||||||
|     # Install dependencies |     # Install dependencies | ||||||
|     apt("openvpn") |     apt("openvpn") | ||||||
|     rpm("openvpn") |     rpm("openvpn") | ||||||
|  |  | ||||||
|     # Create corresponding section in Certidude client configuration file |  | ||||||
|     client_config = ConfigParser() |  | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |  | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |  | ||||||
|     if client_config.has_section(authority): |  | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|     else: |  | ||||||
|         client_config.add_section(authority) |  | ||||||
|         client_config.set(authority, "trigger", "interface up") |  | ||||||
|         client_config.set(authority, "common name", common_name) |  | ||||||
|         client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % const.common_name) |  | ||||||
|         client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % common_name) |  | ||||||
|         client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % common_name) |  | ||||||
|         client_config.set(authority, "authority path",  "/etc/openvpn/keys/ca.crt") |  | ||||||
|         client_config.set(authority, "revocations path",  "/etc/openvpn/keys/ca.crl") |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/services.conf |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     endpoint = "OpenVPN to %s" % remote |     endpoint = "OpenVPN to %s" % remote | ||||||
| @@ -538,7 +541,7 @@ def certidude_setup_openvpn_client(authority, remote, common_name, config, proto | |||||||
|     config.write("remote %s\n" % remote) |     config.write("remote %s\n" % remote) | ||||||
|     config.write("remote-cert-tls server\n") |     config.write("remote-cert-tls server\n") | ||||||
|     config.write("proto %s\n" % proto) |     config.write("proto %s\n" % proto) | ||||||
|     config.write("dev tun\n") |     config.write("dev tun-%s\n" % remote.split(".")[0]) | ||||||
|     config.write("nobind\n") |     config.write("nobind\n") | ||||||
|     config.write("key %s\n" % client_config.get(authority, "key path")) |     config.write("key %s\n" % client_config.get(authority, "key path")) | ||||||
|     config.write("cert %s\n" % client_config.get(authority, "certificate path")) |     config.write("cert %s\n" % client_config.get(authority, "certificate path")) | ||||||
| @@ -563,7 +566,8 @@ def certidude_setup_openvpn_client(authority, remote, common_name, config, proto | |||||||
| @click.option("--common-name", "-cn", default=const.FQDN, help="Common name, %s by default" % const.FQDN) | @click.option("--common-name", "-cn", default=const.FQDN, help="Common name, %s by default" % const.FQDN) | ||||||
| @click.option("--subnet", "-sn", default=u"192.168.33.0/24", type=ip_network, help="IPsec virtual subnet, 192.168.33.0/24 by default") | @click.option("--subnet", "-sn", default=u"192.168.33.0/24", type=ip_network, help="IPsec virtual subnet, 192.168.33.0/24 by default") | ||||||
| @click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed") | @click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed") | ||||||
| def certidude_setup_strongswan_server(authority, common_name, subnet, route): | @setup_client(prefix="server_") | ||||||
|  | def certidude_setup_strongswan_server(authority, common_name, subnet, route, **paths): | ||||||
|     if "." not in common_name: |     if "." not in common_name: | ||||||
|         raise ValueError("Hostname has to be fully qualified!") |         raise ValueError("Hostname has to be fully qualified!") | ||||||
|  |  | ||||||
| @@ -572,31 +576,27 @@ def certidude_setup_strongswan_server(authority, common_name, subnet, route): | |||||||
|     rpm("strongswan") |     rpm("strongswan") | ||||||
|     pip("ipsecparse") |     pip("ipsecparse") | ||||||
|  |  | ||||||
|     # Create corresponding section in Certidude client configuration file |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     client_config = ConfigParser() |     endpoint = "IPsec gateway for %s" % authority | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |     service_config = ConfigParser() | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |     if os.path.exists(const.SERVICES_CONFIG_PATH): | ||||||
|     if client_config.has_section(authority): |         service_config.readfp(open(const.SERVICES_CONFIG_PATH)) | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |     if service_config.has_section(endpoint): | ||||||
|  |         click.echo("Section '%s' already exists in %s, not reconfiguring" % (endpoint, const.SERVICES_CONFIG_PATH)) | ||||||
|     else: |     else: | ||||||
|         client_config.add_section(authority) |         service_config.add_section(endpoint) | ||||||
|         client_config.set(authority, "trigger", "interface up") |         service_config.set(endpoint, "authority", authority) | ||||||
|         client_config.set(authority, "common name", const.FQDN) |         service_config.set(endpoint, "service", "init/strongswan") | ||||||
|         client_config.set(authority, "request path", "%s/ipsec.d/reqs/%s.pem" % (const.STRONGSWAN_PREFIX, const.HOSTNAME)) |         with open(const.SERVICES_CONFIG_PATH + ".part", 'wb') as fh: | ||||||
|         client_config.set(authority, "key path", "%s/ipsec.d/private/%s.pem" % (const.STRONGSWAN_PREFIX, const.HOSTNAME)) |             service_config.write(fh) | ||||||
|         client_config.set(authority, "certificate path", "%s/ipsec.d/certs/%s.pem" % (const.STRONGSWAN_PREFIX, const.HOSTNAME)) |         os.rename(const.SERVICES_CONFIG_PATH + ".part", const.SERVICES_CONFIG_PATH) | ||||||
|         client_config.set(authority, "authority path",  "%s/ipsec.d/cacerts/ca.pem" % const.STRONGSWAN_PREFIX) |         click.echo("Section '%s' added to %s" % (endpoint, const.SERVICES_CONFIG_PATH)) | ||||||
|         client_config.set(authority, "revocations path",  "%s/ipsec.d/crls/ca.pem" % const.STRONGSWAN_PREFIX) |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|     # Create corresponding section to /etc/ipsec.conf |     # Create corresponding section to /etc/ipsec.conf | ||||||
|     from ipsecparse import loads |     from ipsecparse import loads | ||||||
|     config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read()) |     config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read()) | ||||||
|     config["conn", authority] = dict( |     config["conn", authority] = dict( | ||||||
|         leftcert=client_config.get(authority, "certificate path"), |         leftcert=paths.get("certificate_path"), | ||||||
|         leftsubnet=",".join(route), |         leftsubnet=",".join(route), | ||||||
|         right="%any", |         right="%any", | ||||||
|         rightsourceip=str(subnet), |         rightsourceip=str(subnet), | ||||||
| @@ -617,32 +617,13 @@ def certidude_setup_strongswan_server(authority, common_name, subnet, route): | |||||||
| @click.argument("authority") | @click.argument("authority") | ||||||
| @click.argument("remote") | @click.argument("remote") | ||||||
| @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | ||||||
| def certidude_setup_strongswan_client(authority, remote, common_name): | @setup_client() | ||||||
|  | def certidude_setup_strongswan_client(authority, remote, common_name, **paths): | ||||||
|     # Install dependencies |     # Install dependencies | ||||||
|     apt("strongswan") |     apt("strongswan") | ||||||
|     rpm("strongswan") |     rpm("strongswan") | ||||||
|     pip("ipsecparse") |     pip("ipsecparse") | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/client.conf |  | ||||||
|     client_config = ConfigParser() |  | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |  | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |  | ||||||
|     if client_config.has_section(authority): |  | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|     else: |  | ||||||
|         client_config.add_section(authority) |  | ||||||
|         client_config.set(authority, "trigger", "interface up") |  | ||||||
|         client_config.set(authority, "common name", common_name) |  | ||||||
|         client_config.set(authority, "request path", "%s/ipsec.d/reqs/%s.pem" % (const.STRONGSWAN_PREFIX, common_name)) |  | ||||||
|         client_config.set(authority, "key path", "%s/ipsec.d/private/%s.pem" % (const.STRONGSWAN_PREFIX, common_name)) |  | ||||||
|         client_config.set(authority, "certificate path", "%s/ipsec.d/certs/%s.pem" % (const.STRONGSWAN_PREFIX, common_name)) |  | ||||||
|         client_config.set(authority, "authority path", "%s/ipsec.d/cacerts/ca.pem" % const.STRONGSWAN_PREFIX) |  | ||||||
|         client_config.set(authority, "revocations path", "%s/ipsec.d/crls/ca.pem" % const.STRONGSWAN_PREFIX) |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/services.conf |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     endpoint = "IPsec connection to %s" % remote |     endpoint = "IPsec connection to %s" % remote | ||||||
|     service_config = ConfigParser() |     service_config = ConfigParser() | ||||||
| @@ -666,7 +647,7 @@ def certidude_setup_strongswan_client(authority, remote, common_name): | |||||||
|     config["conn", remote] = dict( |     config["conn", remote] = dict( | ||||||
|         leftsourceip="%config", |         leftsourceip="%config", | ||||||
|         left="%defaultroute", |         left="%defaultroute", | ||||||
|         leftcert=client_config.get(authority, "certificate path"), |         leftcert=paths.get("certificate_path"), | ||||||
|         rightid="%any", |         rightid="%any", | ||||||
|         right=remote, |         right=remote, | ||||||
|         #rightsubnet=route, |         #rightsubnet=route, | ||||||
| @@ -689,33 +670,14 @@ def certidude_setup_strongswan_client(authority, remote, common_name): | |||||||
| @click.argument("authority") # Certidude server | @click.argument("authority") # Certidude server | ||||||
| @click.argument("remote") # StrongSwan gateway | @click.argument("remote") # StrongSwan gateway | ||||||
| @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | ||||||
| def certidude_setup_strongswan_networkmanager(authority, remote, common_name): | @setup_client() | ||||||
|  | def certidude_setup_strongswan_networkmanager(authority, remote, common_name, **paths): | ||||||
|     # Install dependencies |     # Install dependencies | ||||||
|     apt("strongswan-nm") |     apt("strongswan-nm") | ||||||
|     rpm("NetworkManager-strongswan-gnome") |     rpm("NetworkManager-strongswan-gnome") | ||||||
|  |  | ||||||
|     endpoint = "IPSec to %s" % remote |     endpoint = "IPSec to %s" % remote | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/client.conf |  | ||||||
|     client_config = ConfigParser() |  | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |  | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |  | ||||||
|     if client_config.has_section(authority): |  | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|     else: |  | ||||||
|         client_config.add_section(authority) |  | ||||||
|         client_config.set(authority, "trigger", "interface up") |  | ||||||
|         client_config.set(authority, "common name", common_name) |  | ||||||
|         client_config.set(authority, "request path", "/etc/ipsec.d/reqs/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "key path", "/etc/ipsec.d/private/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "certificate path", "/etc/ipsec.d/certs/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "authority path",  "/etc/ipsec.d/cacerts/ca.pem") |  | ||||||
|         client_config.set(authority, "revocations path",  "/etc/ipsec.d/crls/ca.pem") |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|     # Create corresponding section in /etc/certidude/services.conf |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     service_config = ConfigParser() |     service_config = ConfigParser() | ||||||
|     if os.path.exists(const.SERVICES_CONFIG_PATH): |     if os.path.exists(const.SERVICES_CONFIG_PATH): | ||||||
| @@ -737,27 +699,9 @@ def certidude_setup_strongswan_networkmanager(authority, remote, common_name): | |||||||
| @click.argument("authority") | @click.argument("authority") | ||||||
| @click.argument("remote") # OpenVPN gateway | @click.argument("remote") # OpenVPN gateway | ||||||
| @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) | ||||||
| def certidude_setup_openvpn_networkmanager(authority, remote, common_name): | @setup_client() | ||||||
|     # Create corresponding section in /etc/certidude/client.conf | def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **paths): | ||||||
|     client_config = ConfigParser() |     # Create corresponding section in /etc/certidude/services.conf | ||||||
|     if os.path.exists(const.CLIENT_CONFIG_PATH): |  | ||||||
|         client_config.readfp(open(const.CLIENT_CONFIG_PATH)) |  | ||||||
|     if client_config.has_section(authority): |  | ||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|     else: |  | ||||||
|         client_config.add_section(authority) |  | ||||||
|         client_config.set(authority, "trigger", "interface up") |  | ||||||
|         client_config.set(authority, "common name", common_name) |  | ||||||
|         client_config.set(authority, "request path", "/etc/ipsec.d/reqs/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "key path", "/etc/ipsec.d/private/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "certificate path", "/etc/ipsec.d/certs/%s.pem" % common_name) |  | ||||||
|         client_config.set(authority, "authority path",  "/etc/ipsec.d/cacerts/ca.pem") |  | ||||||
|         client_config.set(authority, "revocations path",  "/etc/ipsec.d/crls/ca.pem") |  | ||||||
|         with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: |  | ||||||
|             client_config.write(fh) |  | ||||||
|         os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH) |  | ||||||
|         click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) |  | ||||||
|  |  | ||||||
|     endpoint = "OpenVPN to %s" % remote |     endpoint = "OpenVPN to %s" % remote | ||||||
|  |  | ||||||
|     service_config = ConfigParser() |     service_config = ConfigParser() | ||||||
| @@ -767,7 +711,7 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name): | |||||||
|         click.echo("Section '%s' already exists in %s, remove to regenerate" % (endpoint, const.SERVICES_CONFIG_PATH)) |         click.echo("Section '%s' already exists in %s, remove to regenerate" % (endpoint, const.SERVICES_CONFIG_PATH)) | ||||||
|     else: |     else: | ||||||
|         service_config.add_section(endpoint) |         service_config.add_section(endpoint) | ||||||
|         service_config.set(authority, "authority", server) |         service_config.set(endpoint, "authority", authority) | ||||||
|         service_config.set(endpoint, "remote", remote) |         service_config.set(endpoint, "remote", remote) | ||||||
|         service_config.set(endpoint, "service", "network-manager/openvpn") |         service_config.set(endpoint, "service", "network-manager/openvpn") | ||||||
|         service_config.write(open("/etc/certidude/services.conf", "w")) |         service_config.write(open("/etc/certidude/services.conf", "w")) | ||||||
|   | |||||||
| @@ -32,33 +32,6 @@ def ip_address(j): | |||||||
|     import ipaddress |     import ipaddress | ||||||
|     return ipaddress.ip_address(unicode(j)) |     return ipaddress.ip_address(unicode(j)) | ||||||
|  |  | ||||||
| def expand_paths(): |  | ||||||
|     """ |  | ||||||
|     Prefix '..._path' keyword arguments of target function with 'directory' keyword argument |  | ||||||
|     and create the directory if necessary |  | ||||||
|  |  | ||||||
|     TODO: Move to separate file |  | ||||||
|     """ |  | ||||||
|     def wrapper(func): |  | ||||||
|         def wrapped(**arguments): |  | ||||||
|             d = arguments.get("directory") |  | ||||||
|             for key, value in arguments.items(): |  | ||||||
|                 if key.endswith("_path"): |  | ||||||
|                     if d: |  | ||||||
|                         value = os.path.join(d, value) |  | ||||||
|                     value = os.path.realpath(value) |  | ||||||
|                     parent = os.path.dirname(value) |  | ||||||
|                     if not os.path.exists(parent): |  | ||||||
|                         click.echo("Making directory %s for %s" % (repr(parent), repr(key))) |  | ||||||
|                         os.makedirs(parent) |  | ||||||
|                     elif not os.path.isdir(parent): |  | ||||||
|                         raise Exception("Path %s is not directory!" % parent) |  | ||||||
|                     arguments[key] = value |  | ||||||
|             return func(**arguments) |  | ||||||
|         return wrapped |  | ||||||
|     return wrapper |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def apt(packages): | def apt(packages): | ||||||
|     """ |     """ | ||||||
|     Install packages for Debian and Ubuntu |     Install packages for Debian and Ubuntu | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ SERVER_LOG_PATH = "/var/log/certidude-server.log" | |||||||
| SIGNER_SOCKET_PATH = "/run/certidude/signer.sock" | SIGNER_SOCKET_PATH = "/run/certidude/signer.sock" | ||||||
| SIGNER_PID_PATH = os.path.join(RUN_DIR, "signer.pid") | SIGNER_PID_PATH = os.path.join(RUN_DIR, "signer.pid") | ||||||
| SIGNER_LOG_PATH = "/var/log/certidude-signer.log" | SIGNER_LOG_PATH = "/var/log/certidude-signer.log" | ||||||
|  | STORAGE_PATH = "/var/lib/certidude" | ||||||
|  |  | ||||||
| try: | try: | ||||||
|     FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3] |     FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3] | ||||||
|   | |||||||
| @@ -16,12 +16,12 @@ def selinux_fixup(path): | |||||||
|     cmd = "chcon", "--type=home_cert_t", path |     cmd = "chcon", "--type=home_cert_t", path | ||||||
|     subprocess.call(cmd) |     subprocess.call(cmd) | ||||||
|  |  | ||||||
| def certidude_request_certificate(server, system_keytab_required, key_path, request_path, certificate_path, authority_path, revocations_path, common_name, autosign=False, wait=False, bundle=False, renew=False, insecure=False): | def certidude_request_certificate(server, system_keytab_required, key_path, request_path, certificate_path, authority_path, revocations_path, common_name, renewal_overlap, autosign=False, wait=False, bundle=False, renew=False, insecure=False): | ||||||
|     """ |     """ | ||||||
|     Exchange CSR for certificate using Certidude HTTP API server |     Exchange CSR for certificate using Certidude HTTP API server | ||||||
|     """ |     """ | ||||||
|     import requests |     import requests | ||||||
|     from certidude import errors, const |     from certidude import errors, const, config | ||||||
|     from cryptography import x509 |     from cryptography import x509 | ||||||
|     from cryptography.hazmat.primitives.asymmetric import rsa, padding |     from cryptography.hazmat.primitives.asymmetric import rsa, padding | ||||||
|     from cryptography.hazmat.backends import default_backend |     from cryptography.hazmat.backends import default_backend | ||||||
| @@ -178,14 +178,15 @@ def certidude_request_certificate(server, system_keytab_required, key_path, requ | |||||||
|         cert_buf = open(certificate_path).read() |         cert_buf = open(certificate_path).read() | ||||||
|         cert = x509.load_pem_x509_certificate(cert_buf, default_backend()) |         cert = x509.load_pem_x509_certificate(cert_buf, default_backend()) | ||||||
|         lifetime = (cert.not_valid_after - cert.not_valid_before) |         lifetime = (cert.not_valid_after - cert.not_valid_before) | ||||||
|         overlap = lifetime / 4 # TODO: Make overlap configurable |         if renewal_overlap and datetime.now() > cert.not_valid_after - timedelta(days=renewal_overlap): | ||||||
|         if datetime.now() > cert.not_valid_after - overlap: |             click.echo("Certificate will expire %s, will attempt to renew" % cert.not_valid_after) | ||||||
|             click.echo("Certificate expired %s" % cert.not_valid_after) |  | ||||||
|             renew = True |             renew = True | ||||||
|         else: |         else: | ||||||
|             click.echo("Found valid certificate: %s" % certificate_path) |             click.echo("Found valid certificate: %s" % certificate_path) | ||||||
|             if not renew: # Don't do anything if renewal wasn't requested explicitly |             if not renew: # Don't do anything if renewal wasn't requested explicitly | ||||||
|                 return |                 return | ||||||
|  |     else: | ||||||
|  |         cert = None | ||||||
|  |  | ||||||
|     # If machine is joined to domain attempt to present machine credentials for authentication |     # If machine is joined to domain attempt to present machine credentials for authentication | ||||||
|     if system_keytab_required: |     if system_keytab_required: | ||||||
| @@ -211,7 +212,7 @@ def certidude_request_certificate(server, system_keytab_required, key_path, requ | |||||||
|         "Accept": "application/x-x509-user-cert,application/x-pem-file" |         "Accept": "application/x-x509-user-cert,application/x-pem-file" | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if renew: |     if renew and cert: | ||||||
|         signer = key.signer( |         signer = key.signer( | ||||||
|             padding.PSS( |             padding.PSS( | ||||||
|                 mgf=padding.MGF1(hashes.SHA512()), |                 mgf=padding.MGF1(hashes.SHA512()), | ||||||
| @@ -233,7 +234,7 @@ def certidude_request_certificate(server, system_keytab_required, key_path, requ | |||||||
|     if submission.status_code == requests.codes.ok: |     if submission.status_code == requests.codes.ok: | ||||||
|         pass |         pass | ||||||
|     if submission.status_code == requests.codes.accepted: |     if submission.status_code == requests.codes.accepted: | ||||||
|         # Server stored the request for processing (202 Accepted), but waiting was not requested, hence quitting for now |         click.echo("Server accepted the request, but refused to sign immideately (%s). Waiting was not requested, hence quitting for now" % submission.text)  | ||||||
|         return |         return | ||||||
|     if submission.status_code == requests.codes.conflict: |     if submission.status_code == requests.codes.conflict: | ||||||
|         raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite") |         raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite") | ||||||
|   | |||||||
| @@ -1,7 +1,11 @@ | |||||||
| Token for {{ user.name }} | Token for {{ user.name }} | ||||||
|  |  | ||||||
|  | {% if issuer == user %} | ||||||
|  | Token has been issued for {{ user }} for retrieving profile from link below. | ||||||
|  | {% else %} | ||||||
| {{ issuer }} has provided {{ user }} a token for retrieving | {{ issuer }} has provided {{ user }} a token for retrieving | ||||||
| profile from the link below. | profile from the link below. | ||||||
|  | {% endif %} | ||||||
|  |  | ||||||
| {% if config.BUNDLE_FORMAT == "ovpn" %} | {% if config.BUNDLE_FORMAT == "ovpn" %} | ||||||
| To set up OpenVPN for your device: | To set up OpenVPN for your device: | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
|  |  | ||||||
| server { | server { | ||||||
|     listen 80; |     listen 80; | ||||||
|     server_name {{const.FQDN}}; |     server_name {{common_name}}; | ||||||
|     rewrite ^ https://{{const.FQDN}}$request_uri?; |     rewrite ^ https://{{common_name}}$request_uri?; | ||||||
| } | } | ||||||
|  |  | ||||||
| server { | server { | ||||||
| @@ -10,7 +10,7 @@ server { | |||||||
|     add_header X-Frame-Options "DENY"; |     add_header X-Frame-Options "DENY"; | ||||||
|     add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; |     add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; | ||||||
|     listen 443 ssl; |     listen 443 ssl; | ||||||
|     server_name {{const.FQDN}}; |     server_name {{common_name}}; | ||||||
|     client_max_body_size 10G; |     client_max_body_size 10G; | ||||||
|     ssl_certificate {{certificate_path}}; |     ssl_certificate {{certificate_path}}; | ||||||
|     ssl_certificate_key {{key_path}}; |     ssl_certificate_key {{key_path}}; | ||||||
|   | |||||||
| @@ -88,6 +88,7 @@ revoked url = {{ revoked_url }} | |||||||
| renewal allowed = false | renewal allowed = false | ||||||
| ;renewal allowed = true | ;renewal allowed = true | ||||||
|  |  | ||||||
|  |  | ||||||
| [push] | [push] | ||||||
| # This should occasionally be regenerated | # This should occasionally be regenerated | ||||||
| event source token = {{ push_token }} | event source token = {{ push_token }} | ||||||
|   | |||||||
| @@ -55,6 +55,21 @@ def generate_csr(cn=None): | |||||||
|         ).public_bytes(serialization.Encoding.PEM) |         ).public_bytes(serialization.Encoding.PEM) | ||||||
|     return buf |     return buf | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def clean_client(): | ||||||
|  |     assert os.getuid() == 0 and os.getgid() == 0 | ||||||
|  |     if os.path.exists("/etc/certidude/client.conf"): | ||||||
|  |         os.unlink("/etc/certidude/client.conf") | ||||||
|  |     if os.path.exists("/etc/certidude/services.conf"): | ||||||
|  |         os.unlink("/etc/certidude/services.conf") | ||||||
|  |  | ||||||
|  |     # Remove client storage area | ||||||
|  |     if os.path.exists("/tmp/ca.example.lan"): | ||||||
|  |         for filename in os.listdir("/tmp/ca.example.lan"): | ||||||
|  |             if filename.endswith(".pem"): | ||||||
|  |                 os.unlink(os.path.join("/tmp/ca.example.lan", filename)) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_cli_setup_authority(): | def test_cli_setup_authority(): | ||||||
|     import os |     import os | ||||||
|     import sys |     import sys | ||||||
| @@ -78,13 +93,19 @@ def test_cli_setup_authority(): | |||||||
|         shutil.rmtree("/var/lib/certidude/ca.example.lan") |         shutil.rmtree("/var/lib/certidude/ca.example.lan") | ||||||
|     if os.path.exists("/etc/certidude/server.conf"): |     if os.path.exists("/etc/certidude/server.conf"): | ||||||
|         os.unlink("/etc/certidude/server.conf") |         os.unlink("/etc/certidude/server.conf") | ||||||
|     if os.path.exists("/etc/certidude/client.conf"): |  | ||||||
|         os.unlink("/etc/certidude/client.conf") |  | ||||||
|     if os.path.exists("/run/certidude"): |     if os.path.exists("/run/certidude"): | ||||||
|         shutil.rmtree("/run/certidude") |         shutil.rmtree("/run/certidude") | ||||||
|     if os.path.exists("/var/log/certidude.log"): |     if os.path.exists("/var/log/certidude.log"): | ||||||
|         os.unlink("/var/log/certidude.log") |         os.unlink("/var/log/certidude.log") | ||||||
|  |  | ||||||
|  |     # Remove nginx stuff | ||||||
|  |     if os.path.exists("/etc/nginx/sites-available/ca.conf"): | ||||||
|  |         os.unlink("/etc/nginx/sites-available/ca.conf") | ||||||
|  |     if os.path.exists("/etc/nginx/sites-enabled/ca.conf"): | ||||||
|  |         os.unlink("/etc/nginx/sites-enabled/ca.conf") | ||||||
|  |     if os.path.exists("/etc/nginx/conf.d/tls.conf"): | ||||||
|  |         os.unlink("/etc/nginx/conf.d/tls.conf") | ||||||
|  |  | ||||||
|     with open("/etc/ipsec.conf", "w") as fh: # TODO: make compatible with Fedora |     with open("/etc/ipsec.conf", "w") as fh: # TODO: make compatible with Fedora | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
| @@ -96,6 +117,8 @@ def test_cli_setup_authority(): | |||||||
|         if os.path.exists("/etc/openvpn/keys"): |         if os.path.exists("/etc/openvpn/keys"): | ||||||
|             shutil.rmtree("/etc/openvpn/keys") |             shutil.rmtree("/etc/openvpn/keys") | ||||||
|  |  | ||||||
|  |     clean_client() | ||||||
|  |  | ||||||
|     from certidude.cli import entry_point as cli |     from certidude.cli import entry_point as cli | ||||||
|     from certidude import const |     from certidude import const | ||||||
|  |  | ||||||
| @@ -397,34 +420,62 @@ def test_cli_setup_authority(): | |||||||
|     assert r2.headers.get('content-type') == "application/x-pkcs12" |     assert r2.headers.get('content-type') == "application/x-pkcs12" | ||||||
|     assert "Signed " in inbox.pop(), inbox |     assert "Signed " in inbox.pop(), inbox | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"]) |     # Beyond this point don't use client() | ||||||
|     assert not result.exception, result.output |     const.STORAGE_PATH = "/tmp/" | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"]) |     ############# | ||||||
|     assert not result.exception, result.output |     ### nginx ### | ||||||
|  |     ############# | ||||||
|  |     clean_client() | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"]) |     result = runner.invoke(cli, ["setup", "nginx", "-cn", "www.example.lan", "ca.example.lan"]) | ||||||
|     assert not result.exception, result.output |  | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"]) |  | ||||||
|     assert not result.exception, result.output |  | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'openvpn', 'networkmanager', "-cn", "roadwarrior3", "ca.example.lan", "vpn.example.lan"]) |  | ||||||
|     assert not result.exception, result.output |  | ||||||
|  |  | ||||||
|     result = runner.invoke(cli, ['setup', 'strongswan', 'networkmanager', "-cn", "roadwarrior4", "ca.example.lan", "ipsec.example.lan"]) |  | ||||||
|     assert not result.exception, result.output |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|     import os |     import os | ||||||
|     if not os.path.exists("/etc/openvpn/keys"): |  | ||||||
|         os.makedirs("/etc/openvpn/keys") |  | ||||||
|  |  | ||||||
|     with open("/etc/certidude/client.conf", "a") as fh: |     with open("/etc/certidude/client.conf", "a") as fh: | ||||||
|         fh.write("insecure = true\n") |         fh.write("insecure = true\n") | ||||||
|  |  | ||||||
|     # pregen dhparam |  | ||||||
|     result = runner.invoke(cli, ["request", "--no-wait"]) |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|     assert not result.exception, "server responded %s, server logs say %s"  % (result.output, open("/var/log/certidude.log").read()) |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     child_pid = os.fork() | ||||||
|  |     if not child_pid: | ||||||
|  |         result = runner.invoke(cli, ['sign', 'www.example.lan']) | ||||||
|  |         assert not result.exception, result.output | ||||||
|  |         return | ||||||
|  |     else: | ||||||
|  |         os.waitpid(child_pid, 0) | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--renew", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |  | ||||||
|  |     # Test nginx setup | ||||||
|  |     assert os.system("nginx -t") == 0, "Generated nginx config was invalid" | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ############### | ||||||
|  |     ### OpenVPN ### | ||||||
|  |     ############### | ||||||
|  |  | ||||||
|  |     clean_client() | ||||||
|  |  | ||||||
|  |     if not os.path.exists("/etc/openvpn/keys"): | ||||||
|  |         os.makedirs("/etc/openvpn/keys") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     with open("/etc/certidude/client.conf", "a") as fh: | ||||||
|  |         fh.write("insecure = true\n") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|     child_pid = os.fork() |     child_pid = os.fork() | ||||||
|     if not child_pid: |     if not child_pid: | ||||||
| @@ -436,13 +487,87 @@ def test_cli_setup_authority(): | |||||||
|  |  | ||||||
|     result = runner.invoke(cli, ["request", "--no-wait"]) |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|     assert not result.exception, result.output |     assert not result.exception, result.output | ||||||
|     result = runner.invoke(cli, ["request", "--renew"]) |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |     assert os.path.exists("/tmp/ca.example.lan/server_cert.pem") | ||||||
|  |  | ||||||
|  |     # Reset config | ||||||
|  |     os.unlink("/etc/certidude/client.conf") | ||||||
|  |     os.unlink("/etc/certidude/services.conf") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"]) | ||||||
|     assert not result.exception, result.output |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     with open("/etc/certidude/client.conf", "a") as fh: | ||||||
|  |         fh.write("insecure = true\n") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |  | ||||||
|  |     # TODO: test client verification with curl | ||||||
|  |  | ||||||
|  |     ############### | ||||||
|  |     ### IPSec ### | ||||||
|  |     ############### | ||||||
|  |  | ||||||
|  |     clean_client() | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     with open("/etc/certidude/client.conf", "a") as fh: | ||||||
|  |         fh.write("insecure = true\n") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     child_pid = os.fork() | ||||||
|  |     if not child_pid: | ||||||
|  |         result = runner.invoke(cli, ['sign', 'ipsec.example.lan']) | ||||||
|  |         assert not result.exception, result.output | ||||||
|  |         return | ||||||
|  |     else: | ||||||
|  |         os.waitpid(child_pid, 0) | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |     assert os.path.exists("/tmp/ca.example.lan/server_cert.pem") | ||||||
|  |  | ||||||
|  |     # Reset config | ||||||
|  |     os.unlink("/etc/certidude/client.conf") | ||||||
|  |     os.unlink("/etc/certidude/services.conf") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     with open("/etc/certidude/client.conf", "a") as fh: | ||||||
|  |         fh.write("insecure = true\n") | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |     assert "Writing certificate to:" in result.output, result.output | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ###################### | ||||||
|  |     ### NetworkManager ### | ||||||
|  |     ###################### | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'openvpn', 'networkmanager', "-cn", "roadwarrior3", "ca.example.lan", "vpn.example.lan"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |     result = runner.invoke(cli, ['setup', 'strongswan', 'networkmanager', "-cn", "roadwarrior4", "ca.example.lan", "ipsec.example.lan"]) | ||||||
|  |     assert not result.exception, result.output | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     ################### | ||||||
|  |     ### Final tests ### | ||||||
|  |     ################### | ||||||
|  |  | ||||||
|     # Test revocation on command-line |     # Test revocation on command-line | ||||||
|     child_pid = os.fork() |     child_pid = os.fork() | ||||||
|     if not child_pid: |     if not child_pid: | ||||||
|         result = runner.invoke(cli, ['revoke', 'vpn.example.lan']) |         result = runner.invoke(cli, ['revoke', 'www.example.lan']) | ||||||
|         assert not result.exception, result.output |         assert not result.exception, result.output | ||||||
|         return |         return | ||||||
|     else: |     else: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user