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

View File

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

View File

@ -16,18 +16,10 @@ import sys
from configparser import ConfigParser, NoOptionError, NoSectionError
from certidude.helpers import certidude_request_certificate
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 jinja2 import Environment, PackageLoader
import const
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
# 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.option("-r", "--renew", default=False, is_flag=True, help="Renew now")
@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
from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
if not os.path.exists(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))
# 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.update(locals())
@ -82,7 +77,7 @@ def certidude_request(fork, renew):
try:
endpoint_dhparam = clients.get(authority, "dhparam path")
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)
except NoOptionError:
pass
@ -125,7 +120,7 @@ def certidude_request(fork, renew):
elif clients.get(authority, "trigger") != "interface up":
continue
pid_path = os.path.join(run_dir, authority + ".pid")
pid_path = os.path.join(const.RUN_DIR, authority + ".pid")
try:
with open(pid_path) as fh:
@ -163,7 +158,7 @@ def certidude_request(fork, renew):
endpoint_common_name,
insecure=endpoint_insecure,
autosign=True,
wait=True,
wait=not no_wait,
renew=renew)
break
except requests.exceptions.Timeout:
@ -337,6 +332,7 @@ def certidude_request(fork, renew):
@click.command("server", help="Set up OpenVPN server")
@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("--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")
@ -346,7 +342,7 @@ def certidude_request(fork, renew):
default="/etc/openvpn/site-to-client.conf",
type=click.File(mode="w", atomic=True, lazy=True),
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
apt("openvpn")
rpm("openvpn")
@ -358,11 +354,13 @@ def certidude_setup_openvpn_server(authority, config, subnet, route, local, prot
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", const.HOSTNAME)
client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % const.HOSTNAME)
client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % const.HOSTNAME)
client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % const.HOSTNAME)
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")
@ -373,7 +371,7 @@ def certidude_setup_openvpn_server(authority, config, subnet, route, local, prot
# 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()
if os.path.exists(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.argument("authority")
@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("--config", "-o",
default="/etc/openvpn/client-to-site.conf",
type=click.File(mode="w", atomic=True, lazy=True),
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
apt("openvpn")
rpm("openvpn")
@ -506,10 +505,10 @@ def certidude_setup_openvpn_client(authority, remote, config, proto):
else:
client_config.add_section(authority)
client_config.set(authority, "trigger", "interface up")
client_config.set(authority, "common name", const.HOSTNAME)
client_config.set(authority, "request path", "/etc/openvpn/keys/%s.csr" % const.HOSTNAME)
client_config.set(authority, "key path", "/etc/openvpn/keys/%s.key" % const.HOSTNAME)
client_config.set(authority, "certificate path", "/etc/openvpn/keys/%s.crt" % const.HOSTNAME)
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:
@ -549,8 +548,8 @@ def certidude_setup_openvpn_client(authority, remote, config, proto):
config.write("group nogroup\n")
config.write("persist-tun\n")
config.write("persist-key\n")
config.write("up /etc/openvpn/update-resolv-conf")
config.write("down /etc/openvpn/update-resolv-conf")
config.write("up /etc/openvpn/update-resolv-conf\n")
config.write("down /etc/openvpn/update-resolv-conf\n")
click.echo("Generated %s" % config.name)
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("--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):
# Install dependencies
apt("python-setproctitle python-openssl python-falcon python-humanize python-markdown python-xattr")
rpm("python-setproctitle pyOpenSSL python-falcon python-humanize python-markdown pyxattr")
pip("gssapi")
if "." not in common_name:
raise ValueError("No FQDN configured on this system!")
# Install only rarely changing stuff from OS package management
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
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)
# Create authority directory with 750 permissions
# Create directories with 770 permissions
os.umask(0o027)
if not os.path.exists(directory):
os.makedirs(directory)
# Create subdirectories with 770 permissions
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)):
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
os.umask(0o137)
with open(ca_crt, "wb") as fh:
@ -1138,6 +1153,11 @@ def certidude_serve(port, listen, fork):
# Fetch UID, GID of certidude user
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
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
restricted_groups = []
@ -1211,7 +1231,6 @@ def certidude_serve(port, listen, fork):
click.echo("Listening on %s:%d" % (listen, port))
app = certidude_app(log_handlers)
httpd = make_server(listen, port, app, ThreadingWSGIServer)
@ -1219,28 +1238,17 @@ def certidude_serve(port, listen, fork):
Drop privileges
"""
if os.getuid() == 0:
# Initialize LDAP service ticket
if os.path.exists("/etc/cron.hourly/certidude"):
os.system("/etc/cron.hourly/certidude")
# Initialize LDAP service ticket
if os.path.exists("/etc/cron.hourly/certidude"):
os.system("/etc/cron.hourly/certidude")
# Drop privileges
if config.AUTHENTICATION_BACKENDS == {"pam"}:
# PAM needs access to /etc/shadow
import grp
name, passwd, num, mem = grp.getgrnam("shadow")
click.echo("Adding current user to shadow group due to PAM authentication backend")
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)
# PAM needs access to /etc/shadow
if config.AUTHENTICATION_BACKENDS == {"pam"}:
import grp
name, passwd, num, mem = grp.getgrnam("shadow")
click.echo("Adding current user to shadow group due to PAM authentication backend")
restricted_groups.append(num)
if config.EVENT_SOURCE_PUBLISH:
from certidude.push import EventSourceLogHandler
@ -1254,13 +1262,28 @@ def certidude_serve(port, listen, fork):
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():
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()

View File

@ -1,13 +1,14 @@
import os
import click
import ipaddress
import subprocess
def ip_network(j):
import ipaddress
return ipaddress.ip_network(unicode(j))
def ip_address(j):
import ipaddress
return ipaddress.ip_address(unicode(j))
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_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 = ""
LONG_POLL_PUBLISH = ""
LONG_POLL_SUBSCRIBE = "//nonexistant/lp/sub/%s"

View File

@ -4,26 +4,23 @@ import os
import socket
import sys
RUN_DIR = "/run/certidude"
CONFIG_DIR = os.path.expanduser("~/.certidude") if os.getuid() else "/etc/certidude"
CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf")
CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.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"
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"
# Work around the 'asn1 encoding routines:ASN1_mbstring_ncopy:string too long'
# issue within OpenSSL ASN1 parser while running on Travis
if os.getenv("TRAVIS"):
FQDN = "buildbot"
else:
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)
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:
HOSTNAME, DOMAIN = FQDN.split(".", 1)

View File

@ -6,15 +6,7 @@ import tempfile
from base64 import b64encode
from datetime import datetime, timedelta
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 cryptography import x509
from cryptography.hazmat.backends import default_backend
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
"""
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
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
backend = sql
database = sqlite://{{ directory }}/db.sqlite
database = sqlite://{{ directory }}/meta/db.sqlite
[signature]
# Server certificate is granted to certificate with

View File

@ -1,7 +1,2 @@
click>=6.7
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
install_requires=[
"click",
"cryptography",
"configparser",
"jinja2",
"pyasn1",
"requests",
"requests-kerberos"
"configparser"
],
scripts=[
"misc/certidude"

View File

@ -1,18 +1,9 @@
import os
import requests
import subprocess
import pwd
from falcon import testing
from click.testing import CliRunner
from certidude.cli import entry_point as cli
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
from xattr import setxattr
# pkill py && rm -Rfv ~/.certidude && TRAVIS=1 py.test tests
@ -21,9 +12,16 @@ runner = CliRunner()
@pytest.fixture(scope='module')
def client():
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):
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(
public_exponent=65537,
key_size=1024,
@ -36,17 +34,50 @@ def generate_csr(cn=None):
return buf
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'])
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 <= 0xfffffffffffffffffffffffffffffffffffffff
assert authority.ca_cert.not_valid_before < datetime.now()
assert authority.ca_cert.not_valid_after > datetime.now() + timedelta(days=7000)
# 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
# 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
path, _, _ = authority.get_signed("test2")
from xattr import setxattr
setxattr(path, "user.lease.address", b"127.0.0.1")
setxattr(path, "user.lease.last_seen", b"random")
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)
assert r2.status_code == 200 # token consumed by anyone on unknown device
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