mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 16:25:17 +00:00
tests: Better code coverage
This commit is contained in:
parent
a5565439ab
commit
189c604832
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:
|
||||||
|
Loading…
Reference in New Issue
Block a user