mirror of
https://github.com/laurivosandi/certidude
synced 2024-12-22 16:25:17 +00:00
Allow provisioning as subordinate CA and add offline install docs
This commit is contained in:
parent
c01cd279c3
commit
f4627b3bd6
93
README.rst
93
README.rst
@ -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
|
||||
|
@ -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):
|
||||
|
125
certidude/cli.py
125
certidude/cli.py
@ -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")
|
||||
|
@ -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")
|
||||
|
@ -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/ {
|
||||
|
@ -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/
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user