Attempt to run client as part of unittests

This commit is contained in:
Lauri Võsandi 2017-05-01 16:20:50 +00:00
parent cc4f13086e
commit b0683b268d
11 changed files with 178 additions and 116 deletions

View File

@ -9,8 +9,8 @@ virtualenv:
system_site_packages: true system_site_packages: true
install: install:
- pip install -r requirements.txt - pip install -r requirements.txt
- pip install codecov pytest-cov
- pip install --editable . - pip install --editable .
- pip install codecov pytest-cov click ipaddress humanize falcon simplepam
script: script:
- sudo useradd adminbot -G sudo -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1' - sudo useradd adminbot -G sudo -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1'
- sudo useradd userbot -G users -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1' - sudo useradd userbot -G users -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1'
@ -20,12 +20,12 @@ cache:
directories: directories:
- $HOME/.cache/pip - $HOME/.cache/pip
addons: addons:
hostname: ca
hosts:
- ca.example.lan
- vpn.example.lan
- ipsec.example.lan
apt: apt:
packages: packages:
- python-xattr - python-click
- python-setproctitle
- python-markdown
- python-jinja2
- python-configparser - python-configparser
- python-pyasn1
- python-openssl

View File

@ -4,7 +4,6 @@ import falcon
import logging import logging
import xattr import xattr
from datetime import datetime from datetime import datetime
from pyasn1.codec.der import decoder
from certidude import config, authority, push from certidude import config, authority, push
from certidude.auth import login_required, authorize_admin from certidude.auth import login_required, authorize_admin
from certidude.decorators import serialize from certidude.decorators import serialize

View File

@ -16,18 +16,10 @@ 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 from certidude.common import expand_paths, ip_address, ip_network, apt, rpm, pip
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from datetime import datetime, timedelta from datetime import datetime, timedelta
from jinja2 import Environment, PackageLoader
import const import const
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
# http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml # http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml
# https://kjur.github.io/jsrsasign/ # https://kjur.github.io/jsrsasign/
@ -41,8 +33,13 @@ NOW = datetime.utcnow().replace(tzinfo=None)
@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")
def certidude_request(fork, renew): @click.option("-nw", "--no-wait", default=False, is_flag=True, help="Return immideately if server doesn't autosign")
def certidude_request(fork, renew, no_wait):
rpm("openssl")
apt("openssl")
import requests import requests
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
if not os.path.exists(const.CLIENT_CONFIG_PATH): if not os.path.exists(const.CLIENT_CONFIG_PATH):
click.echo("No %s!" % const.CLIENT_CONFIG_PATH) click.echo("No %s!" % const.CLIENT_CONFIG_PATH)
@ -59,12 +56,10 @@ def certidude_request(fork, renew):
service_config.readfp(open(const.SERVICES_CONFIG_PATH)) service_config.readfp(open(const.SERVICES_CONFIG_PATH))
# Process directories # Process directories
run_dir = "/run/certidude" if not os.path.exists(const.RUN_DIR):
click.echo("Creating: %s" % const.RUN_DIR)
os.makedirs(const.RUN_DIR)
# Prepare signer PID-s directory
if not os.path.exists(run_dir):
click.echo("Creating: %s" % run_dir)
os.makedirs(run_dir)
context = globals() context = globals()
context.update(locals()) context.update(locals())
@ -82,7 +77,7 @@ def certidude_request(fork, renew):
try: try:
endpoint_dhparam = clients.get(authority, "dhparam path") endpoint_dhparam = clients.get(authority, "dhparam path")
if not os.path.exists(endpoint_dhparam): if not os.path.exists(endpoint_dhparam):
cmd = "openssl", "dhparam", "-out", endpoint_dhparam, "2048" cmd = "openssl", "dhparam", "-out", endpoint_dhparam, ("512" if os.getenv("TRAVIS") else "2048")
subprocess.check_call(cmd) subprocess.check_call(cmd)
except NoOptionError: except NoOptionError:
pass pass
@ -125,7 +120,7 @@ def certidude_request(fork, renew):
elif clients.get(authority, "trigger") != "interface up": elif clients.get(authority, "trigger") != "interface up":
continue continue
pid_path = os.path.join(run_dir, authority + ".pid") pid_path = os.path.join(const.RUN_DIR, authority + ".pid")
try: try:
with open(pid_path) as fh: with open(pid_path) as fh:
@ -163,7 +158,7 @@ def certidude_request(fork, renew):
endpoint_common_name, endpoint_common_name,
insecure=endpoint_insecure, insecure=endpoint_insecure,
autosign=True, autosign=True,
wait=True, wait=not no_wait,
renew=renew) renew=renew)
break break
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
@ -337,6 +332,7 @@ def certidude_request(fork, renew):
@click.command("server", help="Set up OpenVPN server") @click.command("server", help="Set up OpenVPN server")
@click.argument("authority") @click.argument("authority")
@click.option("--common-name", "-cn", default=const.FQDN, help="Common name, %s by default" % const.FQDN)
@click.option("--subnet", "-s", default="192.168.33.0/24", type=ip_network, help="OpenVPN subnet, 192.168.33.0/24 by default") @click.option("--subnet", "-s", default="192.168.33.0/24", type=ip_network, help="OpenVPN subnet, 192.168.33.0/24 by default")
@click.option("--local", "-l", default="0.0.0.0", help="OpenVPN listening address, defaults to all interfaces") @click.option("--local", "-l", default="0.0.0.0", help="OpenVPN listening address, defaults to all interfaces")
@click.option("--port", "-p", default=1194, type=click.IntRange(1,60000), help="OpenVPN listening port, 1194 by default") @click.option("--port", "-p", default=1194, type=click.IntRange(1,60000), help="OpenVPN listening port, 1194 by default")
@ -346,7 +342,7 @@ def certidude_request(fork, renew):
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, config, subnet, route, local, proto, port): def certidude_setup_openvpn_server(authority, common_name, config, subnet, route, local, proto, port):
# Install dependencies # Install dependencies
apt("openvpn") apt("openvpn")
rpm("openvpn") rpm("openvpn")
@ -358,11 +354,13 @@ def certidude_setup_openvpn_server(authority, config, subnet, route, local, prot
if client_config.has_section(authority): if client_config.has_section(authority):
click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH)) click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH))
else: else:
client_config.add_section(authority)
client_config.set(authority, "trigger", "interface up") client_config.set(authority, "trigger", "interface up")
client_config.set(authority, "common name", const.HOSTNAME) client_config.set(authority, "common name", common_name)
client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % const.HOSTNAME) slug = common_name.replace(".", "-")
client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % const.HOSTNAME) client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % slug)
client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % const.HOSTNAME) 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, "authority path", "/etc/openvpn/keys/ca.crt")
client_config.set(authority, "revocations path", "/etc/openvpn/keys/ca.crl") client_config.set(authority, "revocations path", "/etc/openvpn/keys/ca.crl")
client_config.set(authority, "dhparam path", "/etc/openvpn/keys/dhparam.pem") client_config.set(authority, "dhparam path", "/etc/openvpn/keys/dhparam.pem")
@ -373,7 +371,7 @@ def certidude_setup_openvpn_server(authority, config, subnet, route, local, prot
# Create corresponding section in /etc/certidude/services.conf # Create corresponding section in /etc/certidude/services.conf
endpoint = "OpenVPN server %s of %s" % (const.FQDN, authority) endpoint = "OpenVPN server %s of %s" % (common_name, authority)
service_config = ConfigParser() service_config = ConfigParser()
if os.path.exists(const.SERVICES_CONFIG_PATH): if os.path.exists(const.SERVICES_CONFIG_PATH):
service_config.readfp(open(const.SERVICES_CONFIG_PATH)) service_config.readfp(open(const.SERVICES_CONFIG_PATH))
@ -487,12 +485,13 @@ def certidude_setup_nginx(authority, site_config, tls_config, common_name, direc
@click.command("client", help="Set up OpenVPN client") @click.command("client", help="Set up OpenVPN client")
@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('--proto', "-t", default="udp", type=click.Choice(['udp', 'tcp']), help="OpenVPN transport protocol, UDP by default") @click.option('--proto', "-t", default="udp", type=click.Choice(['udp', 'tcp']), help="OpenVPN transport protocol, UDP by default")
@click.option("--config", "-o", @click.option("--config", "-o",
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, config, proto): def certidude_setup_openvpn_client(authority, remote, common_name, config, proto):
# Install dependencies # Install dependencies
apt("openvpn") apt("openvpn")
rpm("openvpn") rpm("openvpn")
@ -506,10 +505,10 @@ def certidude_setup_openvpn_client(authority, remote, config, proto):
else: else:
client_config.add_section(authority) client_config.add_section(authority)
client_config.set(authority, "trigger", "interface up") client_config.set(authority, "trigger", "interface up")
client_config.set(authority, "common name", const.HOSTNAME) client_config.set(authority, "common name", common_name)
client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % const.HOSTNAME) 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" % const.HOSTNAME) client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % common_name)
client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % const.HOSTNAME) 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, "authority path", "/etc/openvpn/keys/ca.crt")
client_config.set(authority, "revocations path", "/etc/openvpn/keys/ca.crl") client_config.set(authority, "revocations path", "/etc/openvpn/keys/ca.crl")
with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh: with open(const.CLIENT_CONFIG_PATH + ".part", 'wb') as fh:
@ -549,8 +548,8 @@ def certidude_setup_openvpn_client(authority, remote, config, proto):
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("up /etc/openvpn/update-resolv-conf") config.write("up /etc/openvpn/update-resolv-conf\n")
config.write("down /etc/openvpn/update-resolv-conf") config.write("down /etc/openvpn/update-resolv-conf\n")
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:")
@ -791,10 +790,21 @@ def certidude_setup_openvpn_networkmanager(authority, remote):
@click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags") @click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags")
@click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN) @click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN)
def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags): def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags):
# Install dependencies if "." not in common_name:
apt("python-setproctitle python-openssl python-falcon python-humanize python-markdown python-xattr") raise ValueError("No FQDN configured on this system!")
rpm("python-setproctitle pyOpenSSL python-falcon python-humanize python-markdown pyxattr") # Install only rarely changing stuff from OS package management
pip("gssapi") apt("python-setproctitle cython python-dev libkrb5-dev libldap2-dev libffi-dev libssl-dev")
apt("python-mimeparse python-markdown python-xattr python-jinja2 python-cffi python-openssl")
pip("gssapi falcon cryptography humanize ipaddress simplepam humanize requests")
from cryptography import x509
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
# Generate secret for tokens # Generate secret for tokens
token_secret = ''.join(random.choice(string.letters + string.digits + '!@#$%^&*()') for i in range(50)) token_secret = ''.join(random.choice(string.letters + string.digits + '!@#$%^&*()') for i in range(50))
@ -961,17 +971,22 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country,
click.echo("Signing %s..." % cert.subject) click.echo("Signing %s..." % cert.subject)
# Create authority directory with 750 permissions # Create directories with 770 permissions
os.umask(0o027) os.umask(0o027)
if not os.path.exists(directory): if not os.path.exists(directory):
os.makedirs(directory) os.makedirs(directory)
# Create subdirectories with 770 permissions # Create subdirectories with 770 permissions
os.umask(0o007) os.umask(0o007)
for subdir in ("signed", "requests", "revoked", "expired"): for subdir in ("signed", "requests", "revoked", "expired", "meta"):
if not os.path.exists(os.path.join(directory, subdir)): if not os.path.exists(os.path.join(directory, subdir)):
os.mkdir(os.path.join(directory, subdir)) os.mkdir(os.path.join(directory, subdir))
# Create SQLite database file with correct permissions
os.umask(0o117)
with open(os.path.join(directory, "meta", "db.sqlite"), "wb") as fh:
pass
# Set permission bits to 640 # Set permission bits to 640
os.umask(0o137) os.umask(0o137)
with open(ca_crt, "wb") as fh: with open(ca_crt, "wb") as fh:
@ -1138,6 +1153,11 @@ def certidude_serve(port, listen, fork):
# Fetch UID, GID of certidude user # Fetch UID, GID of certidude user
if os.getuid() == 0: if os.getuid() == 0:
# Process directories
if not os.path.exists(const.RUN_DIR):
click.echo("Creating: %s" % const.RUN_DIR)
os.makedirs(const.RUN_DIR)
import pwd import pwd
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
restricted_groups = [] restricted_groups = []
@ -1211,7 +1231,6 @@ def certidude_serve(port, listen, fork):
click.echo("Listening on %s:%d" % (listen, port)) click.echo("Listening on %s:%d" % (listen, port))
app = certidude_app(log_handlers) app = certidude_app(log_handlers)
httpd = make_server(listen, port, app, ThreadingWSGIServer) httpd = make_server(listen, port, app, ThreadingWSGIServer)
@ -1219,28 +1238,17 @@ def certidude_serve(port, listen, fork):
Drop privileges Drop privileges
""" """
if os.getuid() == 0:
# Initialize LDAP service ticket # Initialize LDAP service ticket
if os.path.exists("/etc/cron.hourly/certidude"): if os.path.exists("/etc/cron.hourly/certidude"):
os.system("/etc/cron.hourly/certidude") os.system("/etc/cron.hourly/certidude")
# Drop privileges # PAM needs access to /etc/shadow
if config.AUTHENTICATION_BACKENDS == {"pam"}: if config.AUTHENTICATION_BACKENDS == {"pam"}:
# PAM needs access to /etc/shadow import grp
import grp name, passwd, num, mem = grp.getgrnam("shadow")
name, passwd, num, mem = grp.getgrnam("shadow") click.echo("Adding current user to shadow group due to PAM authentication backend")
click.echo("Adding current user to shadow group due to PAM authentication backend") restricted_groups.append(num)
restricted_groups.append(num)
os.setgroups(restricted_groups)
os.setgid(gid)
os.setuid(uid)
click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" %
("certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()])))
os.umask(0o007)
if config.EVENT_SOURCE_PUBLISH: if config.EVENT_SOURCE_PUBLISH:
from certidude.push import EventSourceLogHandler from certidude.push import EventSourceLogHandler
@ -1254,13 +1262,28 @@ def certidude_serve(port, listen, fork):
j.addHandler(handler) j.addHandler(handler)
def exit_handler():
logger.debug("Shutting down Certidude")
import atexit
atexit.register(exit_handler)
logger.debug("Started Certidude at %s", const.FQDN)
if not fork or not os.fork(): if not fork or not os.fork():
pid = os.getpid()
with open(const.SERVER_PID_PATH, "w") as pidfile:
pidfile.write("%d\n" % pid)
def exit_handler():
logger.debug("Shutting down Certidude")
import atexit
atexit.register(exit_handler)
logger.debug("Started Certidude at %s", const.FQDN)
# Drop privileges
os.setgroups(restricted_groups)
os.setgid(gid)
os.setuid(uid)
click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" %
("certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()])))
os.umask(0o007)
httpd.serve_forever() httpd.serve_forever()

View File

@ -1,13 +1,14 @@
import os import os
import click import click
import ipaddress
import subprocess import subprocess
def ip_network(j): def ip_network(j):
import ipaddress
return ipaddress.ip_network(unicode(j)) return ipaddress.ip_network(unicode(j))
def ip_address(j): def ip_address(j):
import ipaddress
return ipaddress.ip_address(unicode(j)) return ipaddress.ip_address(unicode(j))
def expand_paths(): def expand_paths():

View File

@ -71,7 +71,7 @@ EVENT_SOURCE_SUBSCRIBE = cp.get("push", "event source subscribe")
LONG_POLL_PUBLISH = cp.get("push", "long poll publish") LONG_POLL_PUBLISH = cp.get("push", "long poll publish")
LONG_POLL_SUBSCRIBE = cp.get("push", "long poll subscribe") LONG_POLL_SUBSCRIBE = cp.get("push", "long poll subscribe")
if os.getenv("TRAVIS"): # TODO: include nginx setup in Travis if const.DOMAIN == "example.lan": # TODO: include nginx setup in Travis
EVENT_SOURCE_PUBLISH = "" EVENT_SOURCE_PUBLISH = ""
LONG_POLL_PUBLISH = "" LONG_POLL_PUBLISH = ""
LONG_POLL_SUBSCRIBE = "//nonexistant/lp/sub/%s" LONG_POLL_SUBSCRIBE = "//nonexistant/lp/sub/%s"

View File

@ -4,26 +4,23 @@ import os
import socket import socket
import sys import sys
RUN_DIR = "/run/certidude"
CONFIG_DIR = os.path.expanduser("~/.certidude") if os.getuid() else "/etc/certidude" CONFIG_DIR = os.path.expanduser("~/.certidude") if os.getuid() else "/etc/certidude"
CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf") CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf")
CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf") CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf")
SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf") SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf")
SERVER_PID_PATH = os.path.join(CONFIG_DIR if os.getuid() else RUN_DIR, "server.pid")
SERVER_LOG_PATH = os.path.join(CONFIG_DIR, "server.log") if os.getuid() else "/var/log/certidude-server.log" SERVER_LOG_PATH = os.path.join(CONFIG_DIR, "server.log") if os.getuid() else "/var/log/certidude-server.log"
SIGNER_SOCKET_PATH = os.path.join(CONFIG_DIR, "signer.sock") if os.getuid() else "/run/certidude/signer.sock" SIGNER_SOCKET_PATH = os.path.join(CONFIG_DIR, "signer.sock") if os.getuid() else "/run/certidude/signer.sock"
SIGNER_PID_PATH = os.path.join(CONFIG_DIR, "signer.pid") if os.getuid() else "/run/certidude/signer.pid" SIGNER_PID_PATH = os.path.join(CONFIG_DIR if os.getuid() else RUN_DIR, "signer.pid")
SIGNER_LOG_PATH = os.path.join(CONFIG_DIR, "signer.log") if os.getuid() else "/var/log/certidude-signer.log" SIGNER_LOG_PATH = os.path.join(CONFIG_DIR, "signer.log") if os.getuid() else "/var/log/certidude-signer.log"
# Work around the 'asn1 encoding routines:ASN1_mbstring_ncopy:string too long' try:
# issue within OpenSSL ASN1 parser while running on Travis FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
if os.getenv("TRAVIS"): except socket.gaierror:
FQDN = "buildbot" click.echo("Failed to resolve fully qualified hostname of this machine, make sure hostname -f works")
else: sys.exit(254)
try:
FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
except socket.gaierror:
click.echo("Failed to resolve fully qualified hostname of this machine, make sure hostname -f works")
sys.exit(254)
if "." in FQDN: if "." in FQDN:
HOSTNAME, DOMAIN = FQDN.split(".", 1) HOSTNAME, DOMAIN = FQDN.split(".", 1)

View File

@ -6,15 +6,7 @@ import tempfile
from base64 import b64encode from base64 import b64encode
from datetime import datetime, timedelta from datetime import datetime, timedelta
from certidude import errors, const from certidude import errors, const
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID, AuthorityInformationAccessOID
from configparser import ConfigParser from configparser import ConfigParser
from cryptography import x509
from cryptography.hazmat.backends import default_backend
def selinux_fixup(path): def selinux_fixup(path):
""" """
@ -30,6 +22,12 @@ def certidude_request_certificate(server, system_keytab_required, key_path, requ
Exchange CSR for certificate using Certidude HTTP API server Exchange CSR for certificate using Certidude HTTP API server
""" """
import requests import requests
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID, AuthorityInformationAccessOID
# Create directories # Create directories
for path in key_path, request_path, certificate_path, authority_path, revocations_path: for path in key_path, request_path, certificate_path, authority_path, revocations_path:

View File

@ -58,7 +58,7 @@ autosign subnets = 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# Use SQLite backend # Use SQLite backend
backend = sql backend = sql
database = sqlite://{{ directory }}/db.sqlite database = sqlite://{{ directory }}/meta/db.sqlite
[signature] [signature]
# Server certificate is granted to certificate with # Server certificate is granted to certificate with

View File

@ -1,7 +1,2 @@
click>=6.7 click>=6.7
configparser>=3.5.0 configparser>=3.5.0
cryptography>=1.7.1
Jinja2>=2.8
pyasn1>=0.1.9
requests>=2.12.4
requests-kerberos>=0.7.0

View File

@ -20,12 +20,7 @@ setup(
# Include here only stuff required to run certidude client # Include here only stuff required to run certidude client
install_requires=[ install_requires=[
"click", "click",
"cryptography", "configparser"
"configparser",
"jinja2",
"pyasn1",
"requests",
"requests-kerberos"
], ],
scripts=[ scripts=[
"misc/certidude" "misc/certidude"

View File

@ -1,18 +1,9 @@
import os
import requests
import subprocess import subprocess
import pwd import pwd
from falcon import testing
from click.testing import CliRunner from click.testing import CliRunner
from certidude.cli import entry_point as cli from certidude.cli import entry_point as cli
from datetime import datetime, timedelta from datetime import datetime, timedelta
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import NameOID
import pytest import pytest
from xattr import setxattr
# pkill py && rm -Rfv ~/.certidude && TRAVIS=1 py.test tests # pkill py && rm -Rfv ~/.certidude && TRAVIS=1 py.test tests
@ -21,9 +12,16 @@ runner = CliRunner()
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def client(): def client():
from certidude.api import certidude_app from certidude.api import certidude_app
return testing.TestClient(certidude_app()) from falcon import testing
app = certidude_app()
return testing.TestClient(app)
def generate_csr(cn=None): def generate_csr(cn=None):
from cryptography import x509
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import NameOID
key = rsa.generate_private_key( key = rsa.generate_private_key(
public_exponent=65537, public_exponent=65537,
key_size=1024, key_size=1024,
@ -36,17 +34,50 @@ def generate_csr(cn=None):
return buf return buf
def test_cli_setup_authority(): def test_cli_setup_authority():
import shutil
import os
if os.path.exists("/run/certidude/signer.pid"):
with open("/run/certidude/signer.pid") as fh:
try:
os.kill(int(fh.read()), 15)
except OSError:
pass
os.unlink("/run/certidude/signer.pid")
if os.path.exists("/run/certidude/server.pid"):
with open("/run/certidude/server.pid") as fh:
try:
os.kill(int(fh.read()), 15)
except OSError:
pass
os.unlink("/run/certidude/server.pid")
if os.path.exists("/var/lib/certidude/ca.example.lan"):
shutil.rmtree("/var/lib/certidude/ca.example.lan")
if os.path.exists("/etc/certidude/server.conf"):
os.unlink("/etc/certidude/server.conf")
if os.path.exists("/etc/certidude/client.conf"):
os.unlink("/etc/certidude/client.conf")
# Remove OpenVPN stuff
for filename in os.listdir("/etc/openvpn"):
if filename.endswith(".conf"):
os.unlink(os.path.join("/etc/openvpn", filename))
if os.path.exists("/etc/openvpn/keys"):
shutil.rmtree("/etc/openvpn/keys")
from certidude import const
result = runner.invoke(cli, ['setup', 'authority']) result = runner.invoke(cli, ['setup', 'authority'])
assert not result.exception assert not result.exception
from certidude import const, config, authority from certidude import config, authority
assert authority.ca_cert.serial_number >= 0x100000000000000000000000000000000000000 assert authority.ca_cert.serial_number >= 0x100000000000000000000000000000000000000
assert authority.ca_cert.serial_number <= 0xfffffffffffffffffffffffffffffffffffffff assert authority.ca_cert.serial_number <= 0xfffffffffffffffffffffffffffffffffffffff
assert authority.ca_cert.not_valid_before < datetime.now() assert authority.ca_cert.not_valid_before < datetime.now()
assert authority.ca_cert.not_valid_after > datetime.now() + timedelta(days=7000) assert authority.ca_cert.not_valid_after > datetime.now() + timedelta(days=7000)
# Start server before any signing operations are performed # Start server before any signing operations are performed
result = runner.invoke(cli, ['serve', '-f', '-p', '8080']) result = runner.invoke(cli, ['serve', '-f'])
assert not result.exception assert not result.exception
# Password is bot, users created by Travis # Password is bot, users created by Travis
@ -186,6 +217,7 @@ def test_cli_setup_authority():
# Insert lease as if VPN gateway had submitted it # Insert lease as if VPN gateway had submitted it
path, _, _ = authority.get_signed("test2") path, _, _ = authority.get_signed("test2")
from xattr import setxattr
setxattr(path, "user.lease.address", b"127.0.0.1") setxattr(path, "user.lease.address", b"127.0.0.1")
setxattr(path, "user.lease.last_seen", b"random") setxattr(path, "user.lease.last_seen", b"random")
r = client().simulate_get("/api/signed/test2/attr/") r = client().simulate_get("/api/signed/test2/attr/")
@ -303,3 +335,25 @@ def test_cli_setup_authority():
r2 = client().simulate_get("/api/token/", query_string=r.content) r2 = client().simulate_get("/api/token/", query_string=r.content)
assert r2.status_code == 200 # token consumed by anyone on unknown device assert r2.status_code == 200 # token consumed by anyone on unknown device
assert r2.headers.get('content-type') == "application/x-pkcs12" assert r2.headers.get('content-type') == "application/x-pkcs12"
result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"])
assert not result.exception
result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"])
assert not result.exception
import os
if not os.path.exists("/etc/openvpn/keys"):
os.makedirs("/etc/openvpn/keys")
with open("/etc/certidude/client.conf", "a") as fh:
fh.write("insecure = true\n")
# pregen dhparam
result = runner.invoke(cli, ["request", "--no-wait"])
assert not result.exception
result = runner.invoke(cli, ['sign', 'vpn.example.lan'])
assert not result.exception
result = runner.invoke(cli, ["request", "--no-wait"])
assert not result.exception