tests: Better code coverage

This commit is contained in:
Lauri Võsandi 2017-05-03 21:03:51 +00:00
parent a5565439ab
commit 189c604832
12 changed files with 298 additions and 240 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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 .

View File

@ -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):

View File

@ -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):

View File

@ -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"))

View File

@ -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

View File

@ -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]

View File

@ -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")

View File

@ -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:

View File

@ -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}};

View File

@ -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 }}

View File

@ -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: