mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 16:25:17 +00:00
Attempt to run client as part of unittests
This commit is contained in:
parent
cc4f13086e
commit
b0683b268d
14
.travis.yml
14
.travis.yml
@ -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
|
|
||||||
|
@ -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
|
||||||
|
149
certidude/cli.py
149
certidude/cli.py
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
@ -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():
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
|
7
setup.py
7
setup.py
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user