Allow provisioning as subordinate CA and add offline install docs

This commit is contained in:
Lauri Võsandi 2018-05-07 11:18:29 +00:00
parent c01cd279c3
commit f4627b3bd6
7 changed files with 175 additions and 134 deletions

View File

@ -60,6 +60,7 @@ Features
Common:
* Standard request, sign, revoke workflow via web interface.
* RSA and Elliptic Curve Cryptography both supported, use ``certidude setup authority --elliptic-curve`` for the second
* `OCSP <https://tools.ietf.org/html/rfc4557>`_ and `SCEP <https://tools.ietf.org/html/draft-nourse-scep-23>`_ support.
* PAM and Active Directory compliant authentication backends: Kerberos single sign-on, LDAP simple bind.
* POSIX groups and Active Directory (LDAP) group membership based authorization.
@ -93,12 +94,8 @@ System dependencies for Ubuntu 16.04:
.. code:: bash
apt install -y \
python3-click \
python3-jinja2 python3-markdown \
python3-pip \
python3-mysql.connector python3-requests \
python3-pyxattr
apt install -y python3-click python3-jinja2 python3-markdown \
python3-pip python3-mysql.connector python3-requests python3-pyxattr
System dependencies for Fedora 25+:
@ -122,7 +119,6 @@ You can check it with:
hostname -f
The command should return ``ca.example.com``.
If necessary tweak machine's fully qualified hostname in ``/etc/hosts``:
.. code::
@ -130,8 +126,15 @@ If necessary tweak machine's fully qualified hostname in ``/etc/hosts``:
127.0.0.1 localhost
127.0.1.1 ca.example.com ca
Certidude will submit e-mail notifications to locally running MTA.
Install Postfix and configure it as Satellite system:
.. code:: bash
apt install postfix
Certidude can set up certificate authority relatively easily.
Following will set up certificate authority in ``/var/lib/certidude/hostname.domain.tld``,
Following will set up certificate authority in ``/var/lib/certidude/``,
configure systemd service for your platform,
nginx in ``/etc/nginx/sites-available/certidude.conf``,
cronjobs in ``/etc/cron.hourly/certidude`` and much more:
@ -140,20 +143,13 @@ cronjobs in ``/etc/cron.hourly/certidude`` and much more:
certidude setup authority
Tweak the configuration in ``/etc/certidude/server.conf`` until you meet your requirements
and start the services:
Tweak the configuration in ``/etc/certidude/server.conf`` until you meet your requirements,
to apply changes run:
.. code:: bash
systemctl restart certidude
Certidude will submit e-mail notifications to locally running MTA.
Install Postfix and configure it as Satellite system:
.. code:: bash
apt install postfix
Setting up PAM authentication
-----------------------------
@ -247,7 +243,6 @@ Setting up services
-------------------
Set up services as usual (OpenVPN, Strongswan, etc), when setting up certificates
generate signing request with TLS server flag set.
See Certidude admin interface how to submit CSR-s and retrieve signed certificates.
@ -262,26 +257,31 @@ Configure Certidude client in ``/etc/certidude/client.conf``:
.. code:: ini
[ca.example.com]
insecure = true
trigger = interface up
hostname = $HOSTNAME
Configure services in ``/etc/certidude/services.conf``:
.. code:: bash
[gateway.example.com]
[OpenVPN to gateway.example.com]
authority = ca.example.com
service = network-manager/openvpn
remote = gateway.example.com
[IPSec to gateway.example.com]
authority = ca.example.com
service = network-manager/strongswan
remote = gateway.example.com
To request certificate:
.. code:: bash
certidude request
certidude enroll
The keys, signing requests, certificates and CRL-s are placed under
/var/lib/certidude/ca.example.com/
/etc/certidude/authority/ca.example.com/
The VPN connection should immideately become available under network connections.
@ -293,10 +293,8 @@ To use dependencies from pip:
.. code:: bash
apt install \
build-essential python-dev cython libffi-dev libssl-dev libkrb5-dev \
ldap-utils krb5-user \
libsasl2-modules-gssapi-mit \
apt install build-essential python-dev cython libffi-dev libssl-dev \
libkrb5-dev ldap-utils krb5-user libsasl2-modules-gssapi-mit \
libsasl2-dev libldap2-dev
Clone the repository:
@ -324,7 +322,7 @@ To run tests and measure code coverage grab a clean VM or container:
pip3 install codecov pytest-cov
rm .coverage*
TRAVIS=1 coverage run --parallel-mode --source certidude -m py.test tests
COVERAGE_FILE=/tmp/.coverage TRAVIS=1 coverage run --parallel-mode --source certidude -m py.test tests --capture=sys
coverage combine
coverage report
@ -335,23 +333,34 @@ To uninstall:
pip3 uninstall certidude
Certificate attributes
----------------------
Offline install
---------------
Certificates have a lot of fields that can be filled in.
In any case country, state, locality, organization, organizational unit are not filled in
as this information will already exist in AD and duplicating it in the certificate management
doesn't make sense. Additionally the information will get out of sync if
attributes are changed in AD but certificates won't be updated.
To set up certificate authority in an isolated environment use a
vanilla Ubuntu 16.04 or container to collect the artifacts:
If machine is enrolled, eg by running ``certidude request`` as root on Ubuntu/Fedora/Mac OS X:
.. code:: bash
* If Kerberos credentials are presented machine can be automatically enrolled depending on the ``machine enrollment`` setting
* Common name is set to short ``hostname``
* It is tricky to determine user who is triggering the action so given name, surname and e-mail attributes are not filled in
add-apt-repository -y ppa:nginx/stable
apt-get update -q
rm -fv /var/cache/apt/archives/*.deb /var/cache/certidude/wheels/*.whl
apt install --download-only python3-markdown python3-pyxattr python3-jinja2 python3-cffi software-properties-common libnginx-mod-nchan nginx-full
pip3 wheel --wheel-dir=/var/cache/certidude/wheels -r requirements.txt
pip3 wheel --wheel-dir=/var/cache/certidude/wheels falcon humanize ipaddress simplepam user-agents python-ldap gssapi
pip3 wheel --wheel-dir=/var/cache/certidude/wheels .
tar -cf certidude-assets.tar /var/lib/certidude/assets/ /var/cache/apt/archives/ /var/cache/certidude/wheels
If user enrolls, eg by clicking generate bundle button in the web interface:
Transfer certidude-artifacts.tar to the target machine and execute:
* Common name is either set to ``username`` or ``username@device-identifier`` depending on the ``user enrollment`` setting
* Given name and surname are not filled in because Unicode characters cause issues in OpenVPN Connect app
* E-mail is not filled in because it might change in AD
.. code:: bash
rm -fv /var/cache/apt/archives/*.deb /var/cache/certidude/wheels/*.whl
tar -xvf certidude-artifacts.tar -C /
dpkg -i /var/cache/apt/archives/*.deb
pip3 install --use-wheel --no-index --find-links /var/cache/certidude/wheels/*.whl
Proceed to bootstrap authority without installing packages or assembling assets:
certidude setup authority --skip-packages --skip-assets [--elliptic-curve] [--organization "Mycorp LLC"]
Note it's highly recommended to enable nginx PPA in the target machine

View File

@ -53,17 +53,15 @@ with open(config.AUTHORITY_PRIVATE_KEY_PATH, "rb") as fh:
def self_enroll(skip_notify=False):
assert os.getuid() == 0 and os.getgid() == 0, "Can self-enroll only as root"
from certidude import const
from certidude import const, config
common_name = const.FQDN
directory = os.path.join("/var/lib/certidude", const.FQDN)
self_key_path = os.path.join(directory, "self_key.pem")
try:
path, buf, cert, signed, expires = get_signed(common_name)
self_public_key = asymmetric.load_public_key(path)
private_key = asymmetric.load_private_key(self_key_path)
private_key = asymmetric.load_private_key(config.SELF_KEY_PATH)
except FileNotFoundError: # certificate or private key not found
with open(self_key_path, 'wb') as fh:
with open(config.SELF_KEY_PATH, 'wb') as fh:
if public_key.algorithm == "ec":
self_public_key, private_key = asymmetric.generate_pair("ec", curve=public_key.curve)
elif public_key.algorithm == "rsa":
@ -81,11 +79,11 @@ def self_enroll(skip_notify=False):
request = builder.build(private_key)
pid = os.fork()
if not pid:
from certidude import authority
from certidude import authority, config
from certidude.common import drop_privileges
drop_privileges()
assert os.getuid() != 0 and os.getgid() != 0
path = os.path.join(directory, "requests", common_name + ".pem")
path = os.path.join(config.REQUESTS_DIR, common_name + ".pem")
click.echo("Writing request to %s" % path)
with open(path, "wb") as fh:
fh.write(pem_armor_csr(request)) # Write CSR with certidude permissions
@ -93,10 +91,7 @@ def self_enroll(skip_notify=False):
sys.exit(0)
else:
os.waitpid(pid, 0)
if os.path.exists("/etc/systemd"):
os.system("systemctl reload nginx")
else:
os.system("service nginx reload")
os.system("systemctl reload nginx")
def get_request(common_name):

View File

@ -1001,12 +1001,14 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat
@click.option("--organization", "-o", default=None, help="Company or organization name")
@click.option("--organizational-unit", "-ou", default="Certificate Authority")
@click.option("--push-server", help="Push server, by default http://%s" % const.FQDN)
@click.option("--directory", help="Directory for authority files")
@click.option("--directory", default="/var/lib/certidude", help="Directory for authority files")
@click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN)
@click.option("--skip-assets", is_flag=True, help="Don't attempt to assemble JS/CSS/font assets")
@click.option("--skip-packages", is_flag=True, help="Don't attempt to install apt/pip/npm packages")
@click.option("--elliptic-curve", "-e", is_flag=True, help="Generate EC instead of RSA keypair")
@click.option("--subordinate", is_flag=True, help="Set up subordinate CA instead of root CA")
@fqdn_required
def certidude_setup_authority(username, kerberos_keytab, nginx_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_packages, elliptic_curve):
def certidude_setup_authority(username, kerberos_keytab, nginx_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_assets, skip_packages, elliptic_curve, subordinate):
assert subprocess.check_output(["/usr/bin/lsb_release", "-cs"]) in (b"trusty\n", b"xenial\n", b"bionic\n"), "Only Ubuntu 16.04 supported at the moment"
assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
@ -1052,8 +1054,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates", "profile")
click.echo("Using templates from %s" % template_path)
if not directory:
directory = os.path.join("/var/lib/certidude", common_name)
click.echo("Placing authority files in %s" % directory)
certificate_url = "http://%s/api/certificate/" % common_name
@ -1065,8 +1065,11 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
# Expand variables
assets_dir = os.path.join(directory, "assets")
ca_key = os.path.join(directory, "ca_key.pem")
ca_req = os.path.join(directory, "ca_req.pem")
ca_cert = os.path.join(directory, "ca_cert.pem")
self_key = os.path.join(directory, "self_key.pem")
sqlite_path = os.path.join(directory, "meta", "db.sqlite")
distinguished_name = cn_to_dn("Certidude at %s" % common_name, common_name, o=organization, ou=organizational_unit)
# Builder variables
dhgroup = "ecp384" if elliptic_curve else "modp2048"
@ -1164,35 +1167,38 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
click.echo("Installing JavaScript packages: %s" % cmd)
if os.system(cmd): sys.exit(230)
# Copy fonts
click.echo("Copying fonts...")
if os.system("rsync -avq /usr/local/lib/node_modules/font-awesome/fonts/ %s/fonts/" % assets_dir): sys.exit(229)
if skip_assets:
click.echo("Not attempting to assemble assets as requested...")
else:
# Copy fonts
click.echo("Copying fonts...")
if os.system("rsync -avq /usr/local/lib/node_modules/font-awesome/fonts/ %s/fonts/" % assets_dir): sys.exit(229)
# Compile nunjucks templates
cmd = 'nunjucks-precompile --include ".html$" --include ".ps1$" --include ".sh$" --include ".svg" %s > %s.part' % (static_path, bundle_js)
click.echo("Compiling templates: %s" % cmd)
if os.system(cmd): sys.exit(228)
# Compile nunjucks templates
cmd = 'nunjucks-precompile --include ".html$" --include ".ps1$" --include ".sh$" --include ".svg" %s > %s.part' % (static_path, bundle_js)
click.echo("Compiling templates: %s" % cmd)
if os.system(cmd): sys.exit(228)
# Assemble bundle.js
click.echo("Assembling %s" % bundle_js)
with open(bundle_js + ".part", "a") as fh:
for pkg in "qrcode-svg/dist/qrcode.min.js", "jquery/dist/jquery.min.js", "timeago/*.js", "nunjucks/browser/nunjucks-slim.min.js", "tether/dist/js/*.min.js", "bootstrap/dist/js/*.min.js":
for j in glob(os.path.join("/usr/local/lib/node_modules", pkg)):
click.echo("- Merging: %s" % j)
with open(j) as ih:
fh.write(ih.read())
# Assemble bundle.js
click.echo("Assembling %s" % bundle_js)
with open(bundle_js + ".part", "a") as fh:
for pkg in "qrcode-svg/dist/qrcode.min.js", "jquery/dist/jquery.min.js", "timeago/*.js", "nunjucks/browser/nunjucks-slim.min.js", "tether/dist/js/*.min.js", "bootstrap/dist/js/*.min.js":
for j in glob(os.path.join("/usr/local/lib/node_modules", pkg)):
click.echo("- Merging: %s" % j)
with open(j) as ih:
fh.write(ih.read())
# Assemble bundle.css
click.echo("Assembling %s" % bundle_css)
with open(bundle_css + ".part", "w") as fh:
for pkg in "tether/dist/css/*.min.css", "bootstrap/dist/css/*.min.*css", "font-awesome/css/font-awesome.min.css":
for j in glob(os.path.join("/usr/local/lib/node_modules", pkg)):
click.echo("- Merging: %s" % j)
with open(j) as ih:
fh.write(ih.read())
# Assemble bundle.css
click.echo("Assembling %s" % bundle_css)
with open(bundle_css + ".part", "w") as fh:
for pkg in "tether/dist/css/*.min.css", "bootstrap/dist/css/*.min.*css", "font-awesome/css/font-awesome.min.css":
for j in glob(os.path.join("/usr/local/lib/node_modules", pkg)):
click.echo("- Merging: %s" % j)
with open(j) as ih:
fh.write(ih.read())
os.rename(bundle_css + ".part", bundle_css)
os.rename(bundle_js + ".part", bundle_js)
os.rename(bundle_css + ".part", bundle_css)
os.rename(bundle_js + ".part", bundle_js)
assert os.getuid() == 0 and os.getgid() == 0
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
@ -1203,7 +1209,8 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
click.echo("Creating %s" % const.CONFIG_DIR)
os.makedirs(const.CONFIG_DIR)
os.umask(0o137) # 640
os.umask(0o177) # 600
if os.path.exists(const.SERVER_CONFIG_PATH):
click.echo("Configuration file %s already exists, remove to regenerate" % const.SERVER_CONFIG_PATH)
else:
@ -1250,7 +1257,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
pass
# Generate and sign CA key
if not os.path.exists(ca_key):
if not os.path.exists(ca_key) or subordinate and not os.path.exists(ca_req):
if elliptic_curve:
click.echo("Generating %s EC key for CA ..." % const.CURVE_NAME)
public_key, private_key = asymmetric.generate_pair("ec", curve=const.CURVE_NAME)
@ -1258,12 +1265,38 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
click.echo("Generating %d-bit RSA key for CA ..." % const.KEY_SIZE)
public_key, private_key = asymmetric.generate_pair("rsa", bit_size=const.KEY_SIZE)
# Set permission bits to 600
os.umask(0o177)
with open(ca_key, 'wb') as f:
f.write(asymmetric.dump_private_key(private_key, None))
if subordinate:
builder = CSRBuilder(distinguished_name, public_key)
request = builder.build(private_key)
with open(ca_req + ".part", 'wb') as f:
f.write(pem_armor_csr(request))
os.rename(ca_req + ".part", ca_req)
if not os.path.exists(ca_cert):
if subordinate:
click.echo("Request has been written to %s" % ca_req)
click.echo()
click.echo(open(ca_req).read())
click.echo()
click.echo("Get it signed and insert signed certificate into %s" % ca_cert)
click.echo()
click.echo(" cat > %s" % ca_cert)
click.echo()
click.echo("Paste contents and press Ctrl-D, adjust permissions:")
click.echo()
click.echo(" chown root:root %s" % ca_cert)
click.echo(" chmod 0644 %s" % ca_cert)
click.echo()
click.echo("To finish setup procedure run 'certidude setup authority' again")
sys.exit(1)
# https://technet.microsoft.com/en-us/library/aa998840(v=exchg.141).aspx
builder = CertificateBuilder(
cn_to_dn("Certidude at %s" % common_name, common_name,
o=organization, ou=organizational_unit),
public_key
)
builder = CertificateBuilder(distinguished_name, public_key)
builder.self_signed = True
builder.ca = True
builder.serial_number = random.randint(
@ -1280,22 +1313,20 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, organizat
with open(ca_cert, 'wb') as f:
f.write(pem_armor_certificate(certificate))
# Set permission bits to 600
os.umask(0o177)
with open(ca_key, 'wb') as f:
f.write(asymmetric.dump_private_key(private_key, None))
sys.exit(0) # stop this fork here
assert os.stat(sqlite_path).st_mode == 0o100640
assert os.stat(ca_cert).st_mode == 0o100640
assert os.stat(ca_key).st_mode == 0o100600
assert os.stat("/etc/nginx/sites-available/certidude.conf").st_mode == 0o100640
else:
os.waitpid(bootstrap_pid, 0)
_, exitcode = os.waitpid(bootstrap_pid, 0)
if exitcode:
return 0
from certidude import authority
authority.self_enroll(skip_notify=True)
assert os.getuid() == 0 and os.getgid() == 0, "Enroll contaminated environment"
assert os.stat(sqlite_path).st_mode == 0o100660
assert os.stat(ca_cert).st_mode == 0o100640
assert os.stat(ca_key).st_mode == 0o100600
assert os.stat("/etc/nginx/sites-available/certidude.conf").st_mode == 0o100600
assert os.stat("/etc/certidude/server.conf").st_mode == 0o100600
click.echo("Enabling and starting Certidude backend")
os.system("systemctl enable certidude")
os.system("systemctl restart certidude")

View File

@ -50,6 +50,7 @@ KERBEROS_SUBNETS = set([ipaddress.ip_network(j) for j in
AUTHORITY_DIR = "/var/lib/certidude"
AUTHORITY_PRIVATE_KEY_PATH = cp.get("authority", "private key path")
AUTHORITY_CERTIFICATE_PATH = cp.get("authority", "certificate path")
SELF_KEY_PATH = cp.get("authority", "self key path")
REQUESTS_DIR = cp.get("authority", "requests dir")
SIGNED_DIR = cp.get("authority", "signed dir")
SIGNED_BY_SERIAL_DIR = os.path.join(SIGNED_DIR, "by-serial")

View File

@ -23,8 +23,8 @@ send_timeout 600;
nchan_message_buffer_length 0;
# To use CA-s own certificate for frontend and mutually authenticated connections
ssl_certificate /var/lib/certidude/{{ common_name }}/signed/{{ common_name }}.pem;
ssl_certificate_key /var/lib/certidude/{{common_name}}/self_key.pem;
ssl_certificate {{ directory }}/signed/{{ common_name }}.pem;
ssl_certificate_key {{ directory }}/self_key.pem;
server {
# Section for serving insecure HTTP, note that this is suitable for
@ -138,7 +138,7 @@ server {
# Allow client authentication with certificate,
# backend must still check if certificate was used for TLS handshake
ssl_verify_client optional;
ssl_client_certificate /var/lib/certidude/{{ common_name }}/ca_cert.pem;
ssl_client_certificate {{ directory }}/ca_cert.pem;
# Proxy pass to backend
location /api/ {

View File

@ -25,9 +25,9 @@ kerberos realm = EXAMPLE.LAN
{% if domain %}
# LDAP URI derived from /etc/samba/smb.conf
ldap uri = ldap://dc1.{{ domain }}
ldap uri = ldaps://dc1.{{ domain }}
{% else %}
# LDAP URI
# Placeholder LDAP URI
ldap uri = ldaps://dc1.example.lan
{% endif %}
@ -223,9 +223,14 @@ request submission allowed = false
;user enrollment = single allowed
user enrollment = multiple allowed
# Certificate authority keypair
private key path = {{ ca_key }}
certificate path = {{ ca_cert }}
# Private key used by nginx frontend
self key path = {{ self_key }}
# Directories for requests, signed, revoked and expired certificates
requests dir = {{ directory }}/requests/
signed dir = {{ directory }}/signed/
revoked dir = {{ directory }}/revoked/

View File

@ -96,8 +96,8 @@ def clean_server():
pass
if os.path.exists("/var/lib/certidude/ca.example.lan"):
shutil.rmtree("/var/lib/certidude/ca.example.lan")
if os.path.exists("/var/lib/certidude"):
shutil.rmtree("/var/lib/certidude")
if os.path.exists("/run/certidude"):
shutil.rmtree("/run/certidude")
@ -230,13 +230,13 @@ def test_cli_setup_authority():
assert authority.public_key.algorithm == "ec"
# Generate garbage
with open("/var/lib/certidude/ca.example.lan/bla", "w") as fh:
with open("/var/lib/certidude/bla", "w") as fh:
pass
with open("/var/lib/certidude/ca.example.lan/requests/bla", "w") as fh:
with open("/var/lib/certidude/requests/bla", "w") as fh:
pass
with open("/var/lib/certidude/ca.example.lan/signed/bla", "w") as fh:
with open("/var/lib/certidude/signed/bla", "w") as fh:
pass
with open("/var/lib/certidude/ca.example.lan/revoked/bla", "w") as fh:
with open("/var/lib/certidude/revoked/bla", "w") as fh:
pass
# Start server before any signing operations are performed
@ -255,7 +255,7 @@ def test_cli_setup_authority():
# Test CA certificate fetch
buf = open("/var/lib/certidude/ca.example.lan/ca_cert.pem").read()
buf = open("/var/lib/certidude/ca_cert.pem").read()
r = requests.get("http://ca.example.lan/api/certificate")
assert r.status_code == 200
assert r.headers.get('content-type') == "application/x-x509-ca-cert"
@ -308,7 +308,7 @@ def test_cli_setup_authority():
headers={"content-type":"application/pkcs10"})
assert r.status_code == 202 # success
assert "Stored request " in inbox.pop(), inbox
assert os.path.exists("/var/lib/certidude/ca.example.lan/requests/test.pem")
assert os.path.exists("/var/lib/certidude/requests/test.pem")
# Test request deletion
r = client().simulate_delete("/api/request/test/")
@ -319,7 +319,7 @@ def test_cli_setup_authority():
r = client().simulate_delete("/api/request/test/",
headers={"User-Agent":UA_FEDORA_FIREFOX, "Authorization":admintoken})
assert r.status_code == 403, r.text # CSRF prevented
assert os.path.exists("/var/lib/certidude/ca.example.lan/requests/test.pem")
assert os.path.exists("/var/lib/certidude/requests/test.pem")
r = client().simulate_delete("/api/request/test/",
headers={"Authorization":admintoken})
assert r.status_code == 200, r.text
@ -507,19 +507,19 @@ def test_cli_setup_authority():
r = client().simulate_post("/api/lease/",
query_string = "client=test&inner_address=127.0.0.1&outer_address=8.8.8.8",
headers={"X-SSL-CERT":open("/var/lib/certidude/ca.example.lan/signed/ca.example.lan.pem").read() })
headers={"X-SSL-CERT":open("/var/lib/certidude/signed/ca.example.lan.pem").read() })
assert r.status_code == 200, r.text # lease update ok
# Attempt to fetch and execute default.sh script
from xattr import listxattr, getxattr
assert not [j for j in listxattr("/var/lib/certidude/ca.example.lan/signed/test.pem") if j.startswith(b"user.machine.")]
assert not [j for j in listxattr("/var/lib/certidude/signed/test.pem") if j.startswith(b"user.machine.")]
#os.system("curl http://ca.example.lan/api/signed/test/script | bash")
r = client().simulate_post("/api/signed/test/attr", body="cpu=i5&mem=512M&dist=Ubunt",
headers={"content-type": "application/x-www-form-urlencoded"})
assert r.status_code == 200, r.text
assert getxattr("/var/lib/certidude/ca.example.lan/signed/test.pem", "user.machine.cpu") == b"i5"
assert getxattr("/var/lib/certidude/ca.example.lan/signed/test.pem", "user.machine.mem") == b"512M"
assert getxattr("/var/lib/certidude/ca.example.lan/signed/test.pem", "user.machine.dist") == b"Ubunt"
assert getxattr("/var/lib/certidude/signed/test.pem", "user.machine.cpu") == b"i5"
assert getxattr("/var/lib/certidude/signed/test.pem", "user.machine.mem") == b"512M"
assert getxattr("/var/lib/certidude/signed/test.pem", "user.machine.dist") == b"Ubunt"
# Test tagging integration in scripting framework
r = client().simulate_get("/api/signed/test/script/")
@ -572,11 +572,11 @@ def test_cli_setup_authority():
# Test lease update
r = client().simulate_post("/api/lease/",
query_string = "client=test&inner_address=127.0.0.1&outer_address=8.8.8.8&serial=0",
headers={"X-SSL-CERT":open("/var/lib/certidude/ca.example.lan/signed/ca.example.lan.pem").read() })
headers={"X-SSL-CERT":open("/var/lib/certidude/signed/ca.example.lan.pem").read() })
assert r.status_code == 403, r.text # invalid serial number supplied
r = client().simulate_post("/api/lease/",
query_string = "client=test&inner_address=1.2.3.4&outer_address=8.8.8.8",
headers={"X-SSL-CERT":open("/var/lib/certidude/ca.example.lan/signed/ca.example.lan.pem").read() })
headers={"X-SSL-CERT":open("/var/lib/certidude/signed/ca.example.lan.pem").read() })
assert r.status_code == 200, r.text # lease update ok
@ -717,11 +717,11 @@ def test_cli_setup_authority():
assert not result.exception, result.output
assert "(autosign not requested)" in result.output, result.output
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/vpn.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/vpn.example.lan.pem")
child_pid = os.fork()
if not child_pid:
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/vpn.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/vpn.example.lan.pem")
result = runner.invoke(cli, ["sign", "vpn.example.lan", "--profile", "srv"])
assert not result.exception, result.output
assert "overwrit" not in result.output, result.output
@ -912,20 +912,20 @@ def test_cli_setup_authority():
# Setup gateway
clean_client()
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
result = runner.invoke(cli, ['setup', 'strongswan', 'server', "-cn", "ipsec", "ca.example.lan"])
assert result.exception, result.output # FQDN required
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
result = runner.invoke(cli, ['setup', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"])
assert not result.exception, result.output
assert open("/etc/ipsec.secrets").read() == ": RSA /etc/certidude/authority/ca.example.lan/server_key.pem\n"
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
result = runner.invoke(cli, ['setup', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
with open("/etc/certidude/client.conf", "a") as fh:
fh.write("autosign = false\n")
@ -934,11 +934,11 @@ def test_cli_setup_authority():
assert not result.exception, result.output
assert "(autosign not requested)" in result.output, result.output
assert not os.path.exists("/run/certidude/ca.example.lan.pid"), result.output
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
child_pid = os.fork()
if not child_pid:
assert not os.path.exists("/var/lib/certidude/ca.example.lan/signed/ipsec.example.lan.pem")
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
result = runner.invoke(cli, ["sign", "ipsec.example.lan", "--profile", "srv"])
assert not result.exception, result.output
assert "overwrit" not in result.output, result.output
@ -1024,13 +1024,13 @@ def test_cli_setup_authority():
assert r.status_code == 400
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca.example.lan/ca_cert.pem -CAfile /var/lib/certidude/ca.example.lan/ca_cert.pem -cert /var/lib/certidude/ca.example.lan/signed/roadwarrior2.pem -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp1.log") == 0
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca.example.lan/ca_cert.pem -CAfile /var/lib/certidude/ca.example.lan/ca_cert.pem -cert /var/lib/certidude/ca.example.lan/ca_cert.pem -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp2.log") == 0
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca_cert.pem -CAfile /var/lib/certidude/ca_cert.pem -cert /var/lib/certidude/signed/roadwarrior2.pem -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp1.log") == 0
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca_cert.pem -CAfile /var/lib/certidude/ca_cert.pem -cert /var/lib/certidude/ca_cert.pem -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp2.log") == 0
for filename in os.listdir("/var/lib/certidude/ca.example.lan/revoked"):
for filename in os.listdir("/var/lib/certidude/revoked"):
if not filename.endswith(".pem"):
continue
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca.example.lan/ca_cert.pem -CAfile /var/lib/certidude/ca.example.lan/ca_cert.pem -cert /var/lib/certidude/ca.example.lan/revoked/%s -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp3.log" % filename) == 0
assert os.system("openssl ocsp -issuer /var/lib/certidude/ca_cert.pem -CAfile /var/lib/certidude/ca_cert.pem -cert /var/lib/certidude/revoked/%s -text -url http://ca.example.lan/api/ocsp/ -out /tmp/ocsp3.log" % filename) == 0
break
with open("/tmp/ocsp1.log") as fh:
@ -1108,7 +1108,7 @@ def test_cli_setup_authority():
# Bootstrap authority
assert not os.path.exists("/var/lib/certidude/ca.example.lan/ca_key.pem")
assert not os.path.exists("/var/lib/certidude/ca_key.pem")
assert os.system("certidude setup authority --skip-packages") == 0