1
0
mirror of https://github.com/laurivosandi/certidude synced 2024-12-22 08:15:18 +00:00

Grand unified snippets

This commit is contained in:
Lauri Võsandi 2018-05-29 09:06:07 +00:00
parent da689ad96f
commit 2b86a5c2c7
56 changed files with 308 additions and 173 deletions

View File

@ -60,7 +60,7 @@ Features
Common: Common:
* Standard request, sign, revoke workflow via web interface. * Standard request, sign, revoke workflow via web interface.
* RSA and Elliptic Curve Cryptography both supported, use ``certidude setup authority --elliptic-curve`` for the second * RSA and Elliptic Curve Cryptography both supported, use ``certidude provision 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. * `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. * PAM and Active Directory compliant authentication backends: Kerberos single sign-on, LDAP simple bind.
* POSIX groups and Active Directory (LDAP) group membership based authorization. * POSIX groups and Active Directory (LDAP) group membership based authorization.
@ -74,14 +74,14 @@ Common:
Virtual private networking: Virtual private networking:
* Send VPN profile URL tokens via e-mail, for simplified VPN adoption on Android, iOS, Windows, Mac OS X and Ubuntu. * Send VPN profile URL tokens via e-mail, for simplified VPN adoption on Android, iOS, Windows, Mac OS X and Ubuntu.
* OpenVPN gateway and roadwarrior integration, check out ``certidude setup openvpn server`` and ``certidude setup openvpn client``. * OpenVPN gateway and roadwarrior integration, check out ``certidude provision openvpn server`` and ``certidude provision openvpn client``.
* StrongSwan gateway and roadwarrior integration, check out ``certidude setup strongswan server`` and ``certidude setup strongswan client``. * StrongSwan gateway and roadwarrior integration, check out ``certidude provision strongswan server`` and ``certidude provision strongswan client``.
* NetworkManager integration for Ubuntu and Fedora, check out ``certidude setup openvpn networkmanager`` and ``certidude setup strongswan networkmanager``. * NetworkManager integration for Ubuntu and Fedora, check out ``certidude provision openvpn networkmanager`` and ``certidude provision strongswan networkmanager``.
HTTPS: HTTPS:
* P12 bundle generation for web browsers, seems to work well with Android * P12 bundle generation for web browsers, seems to work well with Android
* HTTPS server setup with client verification, check out ``certidude setup nginx`` * HTTPS server setup with client verification, check out ``certidude provision nginx``
Install Install
@ -141,7 +141,7 @@ cronjobs in ``/etc/cron.hourly/certidude`` and much more:
.. code:: bash .. code:: bash
certidude setup authority certidude provision authority
Tweak the configuration in ``/etc/certidude/server.conf`` until you meet your requirements, Tweak the configuration in ``/etc/certidude/server.conf`` until you meet your requirements,
to apply changes run: to apply changes run:
@ -170,7 +170,7 @@ Python modules:
pip3 install simplepam pip3 install simplepam
The default configuration generated by ``certidude setup`` should make use of the The default configuration generated by ``certidude provision`` should make use of the
PAM. PAM.
Setting up Active Directory authentication Setting up Active Directory authentication
@ -335,6 +335,17 @@ To uninstall:
pip3 uninstall certidude pip3 uninstall certidude
Docker
------
.. code:: bash
git clone https://github.com/laurivosandi/certidude
cd certidude
docker build .
docker run --name ca --hostname ca.example.lan
Offline install Offline install
--------------- ---------------
@ -367,6 +378,6 @@ Proceed to bootstrap authority without installing packages or assembling assets:
.. code:: bash .. code:: bash
certidude setup authority --skip-packages --skip-assets [--elliptic-curve] [--organization "Mycorp LLC"] certidude provision authority --skip-packages --skip-assets [--elliptic-curve] [--organization "Mycorp LLC"]
Note it's highly recommended to enable nginx PPA in the target machine Note it's highly recommended to enable nginx PPA in the target machine

View File

@ -117,3 +117,6 @@ class OCSPResource(AuthorityHandler):
} }
}).dump() }).dump()
# Interestingly openssl's OCSP code doesn't care about content type
resp.append_header("Content-Type", "application/ocsp-response")

View File

@ -43,6 +43,7 @@ def self_enroll(skip_notify=False):
from certidude import const, config from certidude import const, config
common_name = const.FQDN common_name = const.FQDN
os.umask(0o0177)
try: try:
path, buf, cert, signed, expires = get_signed(common_name) path, buf, cert, signed, expires = get_signed(common_name)

View File

@ -59,7 +59,7 @@ esac
cat << EOF > $OVERLAY/etc/certidude/authority/$AUTHORITY/updown cat << EOF > $OVERLAY/etc/certidude/authority/$AUTHORITY/updown
#!/bin/sh #!/bin/sh
CURL="curl -m 3 -f --key /etc/certidude/authority/$AUTHORITY/host_key.pem --cert /etc/certidude/authority/$AUTHORITY/host_cert.pem --cacert /etc/certidude/authority/$AUTHORITY/ca_cert.pem" CURL="curl -m 3 -f --key /etc/certidude/authority/$AUTHORITY/host_key.pem --cert /etc/certidude/authority/$AUTHORITY/host_cert.pem --cacert /etc/certidude/authority/$AUTHORITY/ca_cert.pem --cert-status"
URL="https://$AUTHORITY:8443/api/signed/\$(uci get system.@system[0].hostname)/script/" URL="https://$AUTHORITY:8443/api/signed/\$(uci get system.@system[0].hostname)/script/"
case \$PLUTO_VERB in case \$PLUTO_VERB in

View File

@ -96,7 +96,7 @@ fi
logger -t certidude -s "Request md5sum is $(md5sum -b $REQUEST_PATH)" logger -t certidude -s "Request md5sum is $(md5sum -b $REQUEST_PATH)"
curl -f -L \ curl --cert-status -f -L \
-H "Content-Type: application/pkcs10" \ -H "Content-Type: application/pkcs10" \
--cacert $AUTHORITY_PATH \ --cacert $AUTHORITY_PATH \
--data-binary @$REQUEST_PATH \ --data-binary @$REQUEST_PATH \

View File

@ -10,7 +10,7 @@ KEY_PATH=$DIR/host_key.pem
# TODO: fix Accepted 202 here # TODO: fix Accepted 202 here
curl -f -L \ curl --cert-status -f -L \
-H "Content-Type: application/pkcs10" \ -H "Content-Type: application/pkcs10" \
--data-binary @$REQUEST_PATH \ --data-binary @$REQUEST_PATH \
--cacert $AUTHORITY_PATH \ --cacert $AUTHORITY_PATH \

View File

@ -684,7 +684,7 @@ def certidude_enroll(fork, renew, no_wait, kerberos, skip_self):
help="OpenVPN configuration file") help="OpenVPN configuration file")
@fqdn_required @fqdn_required
@setup_client(prefix="server_", dh=True) @setup_client(prefix="server_", dh=True)
def certidude_setup_openvpn_server(authority, common_name, config, subnet, route, local, proto, port, **paths): def certidude_provision_openvpn_server(authority, common_name, config, subnet, route, local, proto, port, **paths):
# Install dependencies # Install dependencies
apt("openvpn") apt("openvpn")
rpm("openvpn") rpm("openvpn")
@ -745,7 +745,7 @@ def certidude_setup_openvpn_server(authority, common_name, config, subnet, route
@click.option("--verify-client", "-vc", default="optional", type=click.Choice(['optional', 'on', 'off'])) @click.option("--verify-client", "-vc", default="optional", type=click.Choice(['optional', 'on', 'off']))
@fqdn_required @fqdn_required
@setup_client(prefix="server_", dh=True) @setup_client(prefix="server_", dh=True)
def certidude_setup_nginx(authority, common_name, site_config, tls_config, verify_client, **paths): def certidude_provision_nginx(authority, common_name, site_config, tls_config, verify_client, **paths):
apt("nginx") apt("nginx")
rpm("nginx") rpm("nginx")
@ -759,16 +759,15 @@ def certidude_setup_nginx(authority, common_name, site_config, tls_config, verif
if os.path.exists(site_config.name): if os.path.exists(site_config.name):
click.echo("Configuration file %s already exists, not overwriting" % site_config.name) click.echo("Configuration file %s already exists, not overwriting" % site_config.name)
else: else:
site_config.write(env.get_template("nginx-https-site.conf").render(context)) site_config.write(env.get_template("snippets/nginx-https-site.conf").render(context))
click.echo("Generated %s" % site_config.name) click.echo("Generated %s" % site_config.name)
if os.path.exists(tls_config.name): if os.path.exists(tls_config.name):
click.echo("Configuration file %s already exists, not overwriting" % tls_config.name) click.echo("Configuration file %s already exists, not overwriting" % tls_config.name)
else: else:
tls_config.write(env.get_template("nginx-tls.conf").render(context)) tls_config.write(env.get_template("snippets/nginx-tls.conf").render(context))
click.echo("Generated %s" % tls_config.name) click.echo("Generated %s" % tls_config.name)
click.echo() click.echo()
click.echo("Inspect configuration files, enable it and start nginx service:") click.echo("Inspect configuration files, enable it and start nginx service:")
click.echo() click.echo()
@ -789,7 +788,7 @@ def certidude_setup_nginx(authority, common_name, site_config, tls_config, verif
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")
@setup_client() @setup_client()
def certidude_setup_openvpn_client(authority, remote, common_name, config, proto, **paths): def certidude_provision_openvpn_client(authority, remote, common_name, config, proto, **paths):
# Install dependencies # Install dependencies
apt("openvpn") apt("openvpn")
rpm("openvpn") rpm("openvpn")
@ -843,7 +842,7 @@ def certidude_setup_openvpn_client(authority, remote, common_name, config, proto
@click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed") @click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed")
@fqdn_required @fqdn_required
@setup_client(prefix="server_") @setup_client(prefix="server_")
def certidude_setup_strongswan_server(authority, common_name, subnet, route, **paths): def certidude_provision_strongswan_server(authority, common_name, subnet, route, **paths):
# Install dependencies # Install dependencies
apt("strongswan") apt("strongswan")
rpm("strongswan") rpm("strongswan")
@ -892,7 +891,7 @@ def certidude_setup_strongswan_server(authority, common_name, subnet, route, **p
@click.argument("remote") @click.argument("remote")
@click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME)
@setup_client() @setup_client()
def certidude_setup_strongswan_client(authority, remote, common_name, **paths): def certidude_provision_strongswan_client(authority, remote, common_name, **paths):
# Install dependencies # Install dependencies
apt("strongswan") or rpm("strongswan") apt("strongswan") or rpm("strongswan")
@ -948,7 +947,7 @@ def certidude_setup_strongswan_client(authority, remote, common_name, **paths):
@click.argument("remote") # StrongSwan gateway @click.argument("remote") # StrongSwan gateway
@click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME)
@setup_client() @setup_client()
def certidude_setup_strongswan_networkmanager(authority, remote, common_name, **paths): def certidude_provision_strongswan_networkmanager(authority, remote, common_name, **paths):
# Install dependencies # Install dependencies
apt("network-manager strongswan-nm") apt("network-manager strongswan-nm")
rpm("NetworkManager NetworkManager-tui NetworkManager-strongswan-gnome") rpm("NetworkManager NetworkManager-tui NetworkManager-strongswan-gnome")
@ -976,7 +975,7 @@ def certidude_setup_strongswan_networkmanager(authority, remote, common_name, **
@click.argument("remote") # OpenVPN gateway @click.argument("remote") # OpenVPN gateway
@click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME) @click.option("--common-name", "-cn", default=const.HOSTNAME, help="Common name, %s by default" % const.HOSTNAME)
@setup_client() @setup_client()
def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **paths): def certidude_provision_openvpn_networkmanager(authority, remote, common_name, **paths):
apt("network-manager network-manager-openvpn-gnome") apt("network-manager network-manager-openvpn-gnome")
rpm("NetworkManager NetworkManager-tui NetworkManager-openvpn-gnome") rpm("NetworkManager NetworkManager-tui NetworkManager-openvpn-gnome")
@ -997,7 +996,7 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat
click.echo("Section %s added to /etc/certidude/client.conf" % endpoint) click.echo("Section %s added to /etc/certidude/client.conf" % endpoint)
@click.command("authority", help="Set up Certificate Authority in a directory") @click.command("authority", help="Set up Certificate Authority")
@click.option("--username", default="certidude", help="Service user account, created if necessary, 'certidude' by default") @click.option("--username", default="certidude", help="Service user account, created if necessary, 'certidude' by default")
@click.option("--kerberos-keytab", default="/etc/certidude/server.keytab", help="Kerberos keytab for using 'kerberos' authentication backend, /etc/certidude/server.keytab by default") @click.option("--kerberos-keytab", default="/etc/certidude/server.keytab", help="Kerberos keytab for using 'kerberos' authentication backend, /etc/certidude/server.keytab by default")
@click.option("--nginx-config", "-n", @click.option("--nginx-config", "-n",
@ -1021,10 +1020,25 @@ def certidude_setup_openvpn_networkmanager(authority, remote, common_name, **pat
@click.option("--packages-only", is_flag=True, help="Install only apt/pip/npm packages") @click.option("--packages-only", is_flag=True, help="Install only apt/pip/npm packages")
@click.option("--elliptic-curve", "-e", is_flag=True, help="Generate EC instead of RSA keypair") @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") @click.option("--subordinate", is_flag=True, help="Set up subordinate CA instead of root CA")
def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_assets, skip_packages, elliptic_curve, subordinate, packages_only): def certidude_provision_authority(username, kerberos_keytab, nginx_config, tls_config, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, title, skip_assets, skip_packages, elliptic_curve, subordinate, packages_only):
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 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" assert os.getuid() == 0 and os.getgid() == 0, "Authority can be set up only by root"
def verbose_symlink(name, target):
if not os.path.islink(name):
click.echo("Symlinking %s to %s" % (name, target))
os.symlink(target, name)
else:
click.echo("Symlink %s already exist, remove to relink" % name)
def verbose_makedirs(path):
if not os.path.exists(path):
click.echo("Creating directory %s" % path)
os.makedirs(path)
else:
click.echo("Directory %s already exists" % path)
import pwd import pwd
from jinja2 import Environment, PackageLoader from jinja2 import Environment, PackageLoader
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True) env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
@ -1070,8 +1084,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
if os.system(cmd): if os.system(cmd):
raise click.ClickException("Failed to install JavaScript packages") raise click.ClickException("Failed to install JavaScript packages")
if not os.path.exists("/usr/bin/node"): verbose_symlink("/usr/bin/node", "/usr/bin/nodejs")
os.symlink("/usr/bin/nodejs", "/usr/bin/node")
if packages_only: if packages_only:
return return
@ -1100,7 +1113,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
assets_dir = os.path.join(directory, "assets") assets_dir = os.path.join(directory, "assets")
ca_key = os.path.join(directory, "ca_key.pem") ca_key = os.path.join(directory, "ca_key.pem")
ca_req = os.path.join(directory, "ca_req.pem") ca_req = os.path.join(directory, "ca_req.pem")
ca_cert = os.path.join(directory, "ca_cert.pem") authority_path = os.path.join(directory, "ca_cert.pem")
self_key = os.path.join(directory, "self_key.pem") self_key = os.path.join(directory, "self_key.pem")
sqlite_path = os.path.join(directory, "meta", "db.sqlite") sqlite_path = os.path.join(directory, "meta", "db.sqlite")
distinguished_name = cn_to_dn(title, common_name, o=organization, ou=organizational_unit) distinguished_name = cn_to_dn(title, common_name, o=organization, ou=organizational_unit)
@ -1120,7 +1133,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
if os.path.exists(kerberos_keytab): if os.path.exists(kerberos_keytab):
click.echo("Service principal keytab found in '%s'" % kerberos_keytab) click.echo("Service principal keytab found in '%s'" % kerberos_keytab)
else: else:
click.echo("To use 'kerberos' authentication backend join the domain , create service principal and provision authority again:") click.echo("To use 'kerberos' authentication backend join the domain, create service principal and provision authority again:")
click.echo() click.echo()
click.echo(" kinit administrator@EXAMPLE.LAN") click.echo(" kinit administrator@EXAMPLE.LAN")
click.echo(" net ads join -k") click.echo(" net ads join -k")
@ -1128,17 +1141,9 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
click.echo(" kdestroy") click.echo(" kdestroy")
click.echo(" chown %s %s" % (username, kerberos_keytab)) click.echo(" chown %s %s" % (username, kerberos_keytab))
click.echo(" mv /etc/certidude/server.conf /etc/certidude/server.backup") click.echo(" mv /etc/certidude/server.conf /etc/certidude/server.backup")
click.echo(" certidude setup authority") click.echo(" certidude provision authority")
click.echo() click.echo()
for interval in ("hourly", "daily"):
if not os.path.exists("/etc/cron.%s/certidude" % interval):
with open("/etc/cron.%s/certidude" % interval, "w") as fh:
fh.write("#!/bin/bash\nLANG=C.UTF-8 certidude cron %s\n" % interval)
os.chmod("/etc/cron.%s/certidude" % interval, 0o755)
click.echo("Created /etc/cron.%s/certidude" % interval)
if os.path.exists("/etc/krb5.keytab") and os.path.exists("/etc/samba/smb.conf"): if os.path.exists("/etc/krb5.keytab") and os.path.exists("/etc/samba/smb.conf"):
# Fetch Kerberos ticket for system account # Fetch Kerberos ticket for system account
cp = ConfigParser() cp = ConfigParser()
@ -1148,6 +1153,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
name = cp.get("global", "netbios name") name = cp.get("global", "netbios name")
base = ",".join(["dc=" + j for j in domain.split(".")]) base = ",".join(["dc=" + j for j in domain.split(".")])
else: else:
realm = None
click.echo("Warning: /etc/krb5.keytab or /etc/samba/smb.conf not found, Kerberos unconfigured") click.echo("Warning: /etc/krb5.keytab or /etc/samba/smb.conf not found, Kerberos unconfigured")
letsencrypt_fullchain = "/etc/letsencrypt/live/%s/fullchain.pem" % common_name letsencrypt_fullchain = "/etc/letsencrypt/live/%s/fullchain.pem" % common_name
@ -1157,25 +1163,35 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
builder_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "builder") builder_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "builder")
script_dir = os.path.join(os.path.realpath(os.path.dirname(__file__)), "templates", "script") script_dir = os.path.join(os.path.realpath(os.path.dirname(__file__)), "templates", "script")
templates_dir = os.path.join(os.path.realpath(os.path.dirname(__file__)), "templates")
static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static") static_path = os.path.join(os.path.realpath(os.path.dirname(__file__)), "static")
certidude_path = sys.argv[0] certidude_path = sys.argv[0]
click.echo("Generating: %s" % nginx_config.name) click.echo("Generating: %s" % nginx_config.name)
nginx_config.write(env.get_template("server/nginx.conf").render(vars())) nginx_config.write(env.get_template("server/nginx.conf").render(vars()))
nginx_config.close() nginx_config.close()
if not os.path.exists("/etc/nginx/sites-enabled/certidude.conf"):
os.symlink("../sites-available/certidude.conf", "/etc/nginx/sites-enabled/certidude.conf") def verbose_render_systemd_service(template, target, context):
click.echo("Symlinked %s -> /etc/nginx/sites-enabled/" % nginx_config.name) target_path = "/etc/systemd/system/%s" % target
if os.path.exists(target_path):
click.echo("File %s already exists, remove to regenerate" % target_path)
else:
buf = env.get_template(template).render(context)
with open(target_path, "w") as fh:
fh.write(buf)
click.echo("File %s created" % target_path)
os.system("systemctl daemon-reload")
verbose_symlink("/etc/nginx/sites-enabled/certidude.conf", "../sites-available/certidude.conf")
if os.path.exists("/etc/nginx/sites-enabled/default"): if os.path.exists("/etc/nginx/sites-enabled/default"):
os.unlink("/etc/nginx/sites-enabled/default") os.unlink("/etc/nginx/sites-enabled/default")
if os.path.exists("/etc/systemd"): if os.path.exists("/etc/systemd"):
if os.path.exists("/etc/systemd/system/certidude.service"): verbose_render_systemd_service("server/backend.service", "certidude-backend.service", vars())
click.echo("File /etc/systemd/system/certidude.service already exists, remove to regenerate") verbose_render_systemd_service("server/ldap-kinit.service", "certidude-ldap-kinit.service", vars())
else: verbose_render_systemd_service("server/ldap-kinit.timer", "certidude-ldap-kinit.timer", vars())
with open("/etc/systemd/system/certidude.service", "w") as fh: verbose_render_systemd_service("snippets/nginx-ocsp-cache.service", "certidude-ocsp-cache.service", vars())
fh.write(env.get_template("server/systemd.service").render(vars())) verbose_render_systemd_service("snippets/nginx-ocsp-cache.timer", "certidude-ocsp-cache.timer", vars())
click.echo("File /etc/systemd/system/certidude.service created")
os.system("systemctl daemon-reload")
else: else:
raise NotImplementedError("Not systemd based OS, don't know how to set up initscripts") raise NotImplementedError("Not systemd based OS, don't know how to set up initscripts")
@ -1209,7 +1225,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
raise click.ClickException("Failed to copy fonts") raise click.ClickException("Failed to copy fonts")
# Compile nunjucks templates # Compile nunjucks templates
cmd = 'nunjucks-precompile --include "\.html$" --include "\.ps1$" --include "\.sh$" --include "\.svg$" --include "\.yml$" --include "\.conf$" --include "\.mobileconfig$" %s > %s.part' % (static_path, bundle_js) cmd = 'nunjucks-precompile --include snippets --include views %s > %s.part' % (templates_dir, bundle_js)
click.echo("Compiling templates: %s" % cmd) click.echo("Compiling templates: %s" % cmd)
if os.system(cmd): if os.system(cmd):
raise click.ClickException("Failed to compile nunjucks templates") raise click.ClickException("Failed to compile nunjucks templates")
@ -1256,7 +1272,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
if os.path.exists(tls_config.name): if os.path.exists(tls_config.name):
click.echo("Configuration file %s already exists, not overwriting" % tls_config.name) click.echo("Configuration file %s already exists, not overwriting" % tls_config.name)
else: else:
tls_config.write(env.get_template("nginx-tls.conf").render(locals())) tls_config.write(env.get_template("snippets/nginx-tls.conf").render(locals()))
click.echo("Generated %s" % tls_config.name) click.echo("Generated %s" % tls_config.name)
if os.path.exists(const.SERVER_CONFIG_PATH): if os.path.exists(const.SERVER_CONFIG_PATH):
@ -1295,11 +1311,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
os.umask(0o007) os.umask(0o007)
for subdir in ("signed", "signed/by-serial", "requests", "revoked", "expired", "meta", "builder"): for subdir in ("signed", "signed/by-serial", "requests", "revoked", "expired", "meta", "builder"):
path = os.path.join(directory, subdir) path = os.path.join(directory, subdir)
if not os.path.exists(path): verbose_makedirs(path)
click.echo("Creating directory %s" % path)
os.mkdir(path)
else:
click.echo("Directory already exists %s" % path)
assert os.stat(path).st_mode == 0o40770, path assert os.stat(path).st_mode == 0o40770, path
# Create SQLite database file with correct permissions # Create SQLite database file with correct permissions
@ -1308,6 +1320,21 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
with open(sqlite_path, "wb") as fh: with open(sqlite_path, "wb") as fh:
pass pass
# Create symlink for self certificate
verbose_symlink("/var/lib/certidude/self_cert.pem", "signed/" + common_name + ".pem")
# Symlink client paths
verbose_makedirs("/etc/certidude/authority/%s" % common_name)
verbose_symlink(
"/etc/certidude/authority/%s/host_cert.pem" % common_name,
"/var/lib/certidude/self_cert.pem")
verbose_symlink(
"/etc/certidude/authority/%s/host_key.pem" % common_name,
"/var/lib/certidude/self_key.pem")
verbose_symlink(
"/etc/certidude/authority/%s/ca_cert.pem" % common_name,
"/var/lib/certidude/ca_cert.pem")
# Generate and sign CA key # Generate and sign CA key
if not os.path.exists(ca_key) or subordinate and not os.path.exists(ca_req): if not os.path.exists(ca_key) or subordinate and not os.path.exists(ca_req):
if elliptic_curve: if elliptic_curve:
@ -1329,22 +1356,22 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
f.write(pem_armor_csr(request)) f.write(pem_armor_csr(request))
os.rename(ca_req + ".part", ca_req) os.rename(ca_req + ".part", ca_req)
if not os.path.exists(ca_cert): if not os.path.exists(authority_path):
if subordinate: if subordinate:
click.echo("Request has been written to %s" % ca_req) click.echo("Request has been written to %s" % ca_req)
click.echo() click.echo()
click.echo(open(ca_req).read()) click.echo(open(ca_req).read())
click.echo() click.echo()
click.echo("Get it signed and insert signed certificate into %s" % ca_cert) click.echo("Get it signed and insert signed certificate into %s" % authority_path)
click.echo() click.echo()
click.echo(" cat > %s" % ca_cert) click.echo(" cat > %s" % authority_path)
click.echo() click.echo()
click.echo("Paste contents and press Ctrl-D, adjust permissions:") click.echo("Paste contents and press Ctrl-D, adjust permissions:")
click.echo() click.echo()
click.echo(" chown root:root %s" % ca_cert) click.echo(" chown root:root %s" % authority_path)
click.echo(" chmod 0644 %s" % ca_cert) click.echo(" chmod 0644 %s" % authority_path)
click.echo() click.echo()
click.echo("To finish setup procedure run 'certidude setup authority' again") click.echo("To finish setup procedure run 'certidude provision authority' again")
sys.exit(1) # stop this fork here with error sys.exit(1) # stop this fork here with error
# https://technet.microsoft.com/en-us/library/aa998840(v=exchg.141).aspx # https://technet.microsoft.com/en-us/library/aa998840(v=exchg.141).aspx
@ -1360,9 +1387,9 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
# Set permission bits to 640 # Set permission bits to 640
os.umask(0o137) os.umask(0o137)
with open(ca_cert, 'wb') as f: with open(authority_path, 'wb') as f:
f.write(pem_armor_certificate(certificate)) f.write(pem_armor_certificate(certificate))
click.echo("Authority certificate written to: %s" % ca_cert) click.echo("Authority certificate written to: %s" % authority_path)
sys.exit(0) # stop this fork here sys.exit(0) # stop this fork here
else: else:
@ -1376,18 +1403,27 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
assert os.path.exists(os.path.join(directory, "signed", common_name) + ".pem") assert os.path.exists(os.path.join(directory, "signed", common_name) + ".pem")
assert os.getuid() == 0 and os.getgid() == 0, "Enroll contaminated environment" assert os.getuid() == 0 and os.getgid() == 0, "Enroll contaminated environment"
assert os.stat(sqlite_path).st_mode == 0o100660 assert os.stat(sqlite_path).st_mode == 0o100660
assert os.stat(ca_cert).st_mode == 0o100640 assert os.stat(authority_path).st_mode == 0o100640
assert os.stat(ca_key).st_mode == 0o100600 assert os.stat(ca_key).st_mode == 0o100600
assert os.stat(self_key).st_mode == 0o100600
assert os.stat("/etc/nginx/sites-available/certidude.conf").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 assert os.stat("/etc/certidude/server.conf").st_mode == 0o100600
# Disable legacy garbage
if os.path.exists("/etc/cron.hourly/certidude"):
os.unlink("/etc/cron.hourly/certidude")
if os.path.exists("/etc/cron.daily/certidude"):
os.unlink("/etc/cron.daily/certidude")
if os.path.exists("/etc/systemd/system/certidude.service"):
os.unlink("/etc/systemd/system/certidude.service")
click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH) click.echo("To enable e-mail notifications install Postfix as sattelite system and set mailer address in %s" % const.SERVER_CONFIG_PATH)
click.echo() click.echo()
click.echo("Use following commands to inspect the newly created files:") click.echo("Use following commands to inspect the newly created files:")
click.echo() click.echo()
click.echo(" openssl x509 -text -noout -in %s | less" % ca_cert) click.echo(" openssl x509 -text -noout -in %s | less" % authority_path)
click.echo(" openssl rsa -check -in %s" % ca_key) click.echo(" openssl rsa -check -in %s" % ca_key)
click.echo(" openssl verify -CAfile %s %s" % (ca_cert, ca_cert)) click.echo(" openssl verify -CAfile %s %s" % (authority_path, authority_path))
click.echo() click.echo()
click.echo("To inspect logs and issued tokens:") click.echo("To inspect logs and issued tokens:")
click.echo() click.echo()
@ -1395,12 +1431,19 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, tls_confi
click.echo(" echo 'select * from token;' | sqlite3 /var/lib/certidude/meta/db.sqlite") click.echo(" echo 'select * from token;' | sqlite3 /var/lib/certidude/meta/db.sqlite")
click.echo() click.echo()
click.echo("Enabling Certidude backend and nginx...") click.echo("Enabling Certidude backend and nginx...")
os.system("systemctl enable certidude") os.system("systemctl enable certidude-backend.service")
os.system("systemctl enable nginx") os.system("systemctl enable nginx")
os.system("systemctl enable certidude-ocsp-cache.timer")
os.system("systemctl start certidude-ocsp-cache.timer")
if realm:
os.system("systemctl enable certidude-ldap-kinit.timer")
os.system("systemctl start certidude-ldap-kinit.timer")
os.system("systemctl start certidude-ldap-kinit.service")
click.echo("To (re)start services:") click.echo("To (re)start services:")
click.echo() click.echo()
click.echo(" systemctl restart certidude") click.echo(" systemctl restart certidude-backend")
click.echo(" systemctl restart nginx") click.echo(" systemctl restart nginx")
click.echo(" systemctl start certidude-ocsp-cache.service")
click.echo() click.echo()
return 0 return 0
@ -1517,8 +1560,8 @@ def certidude_revoke(common_name, reason):
authority.revoke(common_name, reason) authority.revoke(common_name, reason)
@click.command("hourly", help="Hourly housekeeping tasks") @click.command("kinit", help="Initialize Kerberos credential cache for LDAP")
def certidude_cron_hourly(): def certidude_housekeeping_kinit():
from certidude import config from certidude import config
# Update LDAP service ticket if Certidude is joined to domain # Update LDAP service ticket if Certidude is joined to domain
@ -1535,8 +1578,8 @@ def certidude_cron_hourly():
os.rename("/run/certidude/krb5cc.part", "/run/certidude/krb5cc") os.rename("/run/certidude/krb5cc.part", "/run/certidude/krb5cc")
@click.command("daily", help="Daily housekeeping tasks") @click.command("daily", help="Send notifications about expired certificates")
def certidude_cron_daily(): def certidude_housekeeping_expiration():
from certidude import authority, config, mailer from certidude import authority, config, mailer
threshold_move = datetime.utcnow() - const.CLOCK_SKEW_TOLERANCE threshold_move = datetime.utcnow() - const.CLOCK_SKEW_TOLERANCE
threshold_notify = datetime.utcnow() + timedelta(hours=48) threshold_notify = datetime.utcnow() + timedelta(hours=48)
@ -1633,11 +1676,6 @@ def certidude_serve(port, listen, fork):
Drop privileges Drop privileges
""" """
# Initialize LDAP service ticket
if os.path.exists("/etc/cron.hourly/certidude"):
os.system("/etc/cron.hourly/certidude")
from certidude.push import EventSourceLogHandler from certidude.push import EventSourceLogHandler
log_handlers.append(EventSourceLogHandler()) log_handlers.append(EventSourceLogHandler())
@ -1671,7 +1709,7 @@ def certidude_serve(port, listen, fork):
@click.option("-p", "--pin", default="123456", help="Slot pincode, 123456 by default") @click.option("-p", "--pin", default="123456", help="Slot pincode, 123456 by default")
@click.option("-s", "--slot", default="9a", help="Yubikey slot to use, 9a by default") @click.option("-s", "--slot", default="9a", help="Yubikey slot to use, 9a by default")
@click.option("-u", "--username", default=os.getenv("USER"), help="Username to use, %s by default" % os.getenv("USER")) @click.option("-u", "--username", default=os.getenv("USER"), help="Username to use, %s by default" % os.getenv("USER"))
def certidude_setup_yubikey(authority, slot, username, pin): def certidude_provision_yubikey(authority, slot, username, pin):
import requests import requests
cmd = "ykinfo", "-q", "-s" cmd = "ykinfo", "-q", "-s"
click.echo("Executing: %s" % " ".join(cmd)) click.echo("Executing: %s" % " ".join(cmd))
@ -1750,47 +1788,48 @@ def certidude_token_issue(subject, subject_mail):
@click.group("strongswan", help="strongSwan helpers") @click.group("strongswan", help="strongSwan helpers")
def certidude_setup_strongswan(): pass def certidude_provision_strongswan(): pass
@click.group("openvpn", help="OpenVPN helpers") @click.group("openvpn", help="OpenVPN helpers")
def certidude_setup_openvpn(): pass def certidude_provision_openvpn(): pass
@click.group("setup", help="Getting started section") @click.group("provision", help="Getting started section")
def certidude_setup(): pass def certidude_provision(): pass
@click.group("housekeeping", help="Housekeeping tasks")
def certidude_housekeeping(): pass
@click.group("token", help="Token management") @click.group("token", help="Token management")
def certidude_token(): pass def certidude_token(): pass
@click.group("cron", help="Housekeeping tasks")
def certidude_cron(): pass
@click.group() @click.group()
def entry_point(): pass def entry_point(): pass
certidude_setup_strongswan.add_command(certidude_setup_strongswan_server)
certidude_setup_strongswan.add_command(certidude_setup_strongswan_client) certidude_provision_strongswan.add_command(certidude_provision_strongswan_server)
certidude_setup_strongswan.add_command(certidude_setup_strongswan_networkmanager) certidude_provision_strongswan.add_command(certidude_provision_strongswan_client)
certidude_setup_openvpn.add_command(certidude_setup_openvpn_server) certidude_provision_strongswan.add_command(certidude_provision_strongswan_networkmanager)
certidude_setup_openvpn.add_command(certidude_setup_openvpn_client) certidude_provision_openvpn.add_command(certidude_provision_openvpn_server)
certidude_setup_openvpn.add_command(certidude_setup_openvpn_networkmanager) certidude_provision_openvpn.add_command(certidude_provision_openvpn_client)
certidude_setup.add_command(certidude_setup_authority) certidude_provision_openvpn.add_command(certidude_provision_openvpn_networkmanager)
certidude_setup.add_command(certidude_setup_openvpn) certidude_provision.add_command(certidude_provision_authority)
certidude_setup.add_command(certidude_setup_strongswan) certidude_provision.add_command(certidude_provision_openvpn)
certidude_setup.add_command(certidude_setup_nginx) certidude_provision.add_command(certidude_provision_strongswan)
certidude_setup.add_command(certidude_setup_yubikey) certidude_provision.add_command(certidude_provision_nginx)
certidude_provision.add_command(certidude_provision_yubikey)
certidude_token.add_command(certidude_token_list) certidude_token.add_command(certidude_token_list)
certidude_token.add_command(certidude_token_purge) certidude_token.add_command(certidude_token_purge)
certidude_token.add_command(certidude_token_issue) certidude_token.add_command(certidude_token_issue)
certidude_cron.add_command(certidude_cron_hourly) certidude_housekeeping.add_command(certidude_housekeeping_kinit)
certidude_cron.add_command(certidude_cron_daily) certidude_housekeeping.add_command(certidude_housekeeping_expiration)
entry_point.add_command(certidude_token) entry_point.add_command(certidude_token)
entry_point.add_command(certidude_setup) entry_point.add_command(certidude_provision)
entry_point.add_command(certidude_serve) entry_point.add_command(certidude_serve)
entry_point.add_command(certidude_enroll) entry_point.add_command(certidude_enroll)
entry_point.add_command(certidude_sign) entry_point.add_command(certidude_sign)
entry_point.add_command(certidude_revoke) entry_point.add_command(certidude_revoke)
entry_point.add_command(certidude_list) entry_point.add_command(certidude_list)
entry_point.add_command(certidude_cron) entry_point.add_command(certidude_housekeeping)
entry_point.add_command(certidude_users) entry_point.add_command(certidude_users)
entry_point.add_command(certidude_test) entry_point.add_command(certidude_test)

View File

@ -546,7 +546,15 @@ function loadAuthority(query) {
**/ **/
$("#view-dashboard").html(env.render('views/authority.html', { $("#view-dashboard").html(env.render('views/authority.html', {
session: session, session: session,
window: window window: window,
// Parameters for unified snippets
dhparam_path: "/etc/ssl/dhparam.pem",
key_path: "/etc/certidude/authority/" + session.authority.hostname + "/host_key.pem",
certificate_path: "/etc/certidude/authority/" + session.authority.hostname + "/host_cert.pem",
authority_path: "/etc/certidude/authority/" + session.authority.hostname + "/ca_cert.pem",
revocations_path: "/etc/certidude/authority/" + session.authority.hostname + "/crl.pem",
common_name: "$NAME"
})); }));
$("time").timeago(); $("time").timeago();

View File

@ -32,7 +32,7 @@ else
fi fi
# Submit some stats to CA # Submit some stats to CA
curl https://{{ authority_name }}:8443/api/signed/{{ common_name }}/attr \ curl --cert-status https://{{ authority_name }}:8443/api/signed/{{ common_name }}/attr \
--cacert /etc/certidude/authority/{{ authority_name }}/ca_cert.pem \ --cacert /etc/certidude/authority/{{ authority_name }}/ca_cert.pem \
--key /etc/certidude/authority/{{ authority_name }}/host_key.pem \ --key /etc/certidude/authority/{{ authority_name }}/host_key.pem \
--cert /etc/certidude/authority/{{ authority_name }}/host_cert.pem \ --cert /etc/certidude/authority/{{ authority_name }}/host_cert.pem \

View File

@ -0,0 +1,17 @@
[Unit]
Description=Certidude server
After=network.target
[Service]
Type=forking
EnvironmentFile=/etc/environment
Environment=LANG=C.UTF-8
Environment=PYTHON_EGG_CACHE=/tmp/.cache
PIDFile=/run/certidude/server.pid
KillSignal=SIGINT
ExecStart={{ certidude_path }} serve --fork
TimeoutSec=15
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,7 @@
[Unit]
Description=Initialize Kerberos credential cache for LDAP connections of Certidude
[Service]
Type=oneshot
ExecStart={{ certidude_path }} housekeeping kinit
Before=certidude-backend.service

View File

@ -0,0 +1,3 @@
[Timer]
OnCalendar=00/8:30
Persistent=true

View File

@ -137,10 +137,16 @@ server {
server_name {{ common_name }}; server_name {{ common_name }};
listen 8443 ssl http2; listen 8443 ssl http2;
# Enforce OCSP stapling for the server certificate
# Note that even nginx 1.14.0 doesn't immideately populate the OCSP cache
# You need to run separate cronjob to populate the OCSP response cache
ssl_stapling on;
ssl_stapling_verify on;
# Allow client authentication with certificate, # Allow client authentication with certificate,
# backend must still check if certificate was used for TLS handshake # backend must still check if certificate was used for TLS handshake
ssl_verify_client optional; ssl_verify_client optional;
ssl_client_certificate {{ directory }}/ca_cert.pem; ssl_client_certificate {{ authority_path }};
# Proxy pass to backend # Proxy pass to backend
location /api/ { location /api/ {

View File

@ -218,7 +218,7 @@ user enrollment = multiple allowed
# Certificate authority keypair # Certificate authority keypair
private key path = {{ ca_key }} private key path = {{ ca_key }}
certificate path = {{ ca_cert }} certificate path = {{ authority_path }}
# Private key used by nginx frontend # Private key used by nginx frontend
self key path = {{ self_key }} self key path = {{ self_key }}

View File

@ -2,7 +2,7 @@
cat <<\EOF > /etc/certidude/authority/{{ session.authority.hostname }}/updown cat <<\EOF > /etc/certidude/authority/{{ session.authority.hostname }}/updown
#!/bin/sh #!/bin/sh
CURL="curl -m 3 -f --key /etc/certidude/authority/{{ session.authority.hostname }}/host_key.pem --cert /etc/certidude/authority/{{ session.authority.hostname }}/host_cert.pem --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem https://{{ session.authority.hostname }}:8443/api/lease/" CURL="curl --cert-status -m 3 -f --key /etc/certidude/authority/{{ session.authority.hostname }}/host_key.pem --cert /etc/certidude/authority/{{ session.authority.hostname }}/host_cert.pem --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem https://{{ session.authority.hostname }}:8443/api/lease/"
case $PLUTO_VERB in case $PLUTO_VERB in
up-client) $CURL --data-urlencode "outer_address=$PLUTO_PEER" --data-urlencode "inner_address=$PLUTO_PEER_SOURCEIP" --data-urlencode "client=$PLUTO_PEER_ID" ;; up-client) $CURL --data-urlencode "outer_address=$PLUTO_PEER" --data-urlencode "inner_address=$PLUTO_PEER_SOURCEIP" --data-urlencode "client=$PLUTO_PEER_ID" ;;

View File

@ -1,8 +1,8 @@
server { server {
listen 80; listen 80;
server_name {{common_name}}; server_name {{ common_name }};
rewrite ^ https://{{common_name}}$request_uri?; rewrite ^ https://{{ common_name }}\$request_uri?;
} }
server { server {
@ -10,19 +10,21 @@ server {
add_header X-Frame-Options "DENY"; add_header X-Frame-Options "DENY";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
listen 443 ssl; listen 443 ssl;
server_name {{common_name}}; server_name $NAME;
client_max_body_size 10G; client_max_body_size 10G;
ssl_certificate {{certificate_path}}; ssl_certificate {{certificate_path}};
ssl_certificate_key {{key_path}}; ssl_certificate_key {{key_path}};
ssl_client_certificate {{authority_path}}; ssl_client_certificate {{authority_path}};
ssl_crl {{revocations_path}};
ssl_verify_client {{verify_client}};
location ~ \.php$ { # Uncomment following to enable mutual authentication with certificates
#ssl_crl {{revocations_path}};
#ssl_verify_client on;
location ~ \.php\$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php5-fpm.sock; fastcgi_pass unix:/run/php5-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
fastcgi_param REMOTE_USER $ssl_client_s_dn_cn; fastcgi_param REMOTE_USER \$ssl_client_s_dn_cn;
include fastcgi_params; include fastcgi_params;
} }
} }

View File

@ -0,0 +1,7 @@
[Unit]
Description=Cache OCSP responses for nginx OCSP stapling
[Service]
Type=oneshot
Requires=nginx.service
ExecStart=-/usr/bin/curl --cert-status https://{{ common_name }}:8443/ --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem

View File

@ -0,0 +1,3 @@
[Timer]
OnCalendar=*:0/15
Persistent=true

View File

@ -8,9 +8,6 @@ ssl_ecdh_curve secp384r1;
ssl_session_timeout 10m; ssl_session_timeout 10m;
ssl_session_cache shared:SSL:10m; ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; ssl_session_tickets off;
ssl_trusted_certificate {{ ca_cert }}; # OCSP responder trust chain
ssl_stapling on;
ssl_stapling_verify on;
add_header X-Frame-Options DENY; add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff; add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block"; add_header X-XSS-Protection "1; mode=block";
@ -23,6 +20,6 @@ add_header X-Robots-Tag none;
# Add SSLUserName SSL_CLIENT_S_DN_CN style parameter support # Add SSLUserName SSL_CLIENT_S_DN_CN style parameter support
map $ssl_client_s_dn $ssl_client_s_dn_cn { map $ssl_client_s_dn $ssl_client_s_dn_cn {
default ""; default "";
~/CN=(?<CN>[^/]+) $CN; ~/CN=([^/]+) $1;
} }

View File

@ -0,0 +1,7 @@
# Use fully qualified name
test -e /sbin/uci && NAME=$(nslookup $(uci get network.wan.ipaddr) | grep "name =" | head -n1 | cut -d "=" -f 2 | xargs)
test -e /bin/hostname && NAME=$(hostname -f)
test -n "$NAME" || NAME=$(cat /proc/sys/kernel/hostname)
{% include "snippets/request-common.sh" %}
{% include "snippets/submit-request-wait.sh" %}

View File

@ -0,0 +1,11 @@
# See more on http://unmitigatedrisk.com/?p=241 why we're doing this
cat << EOF > /etc/systemd/system/nginx-ocsp-cache.service
{% include "snippets/nginx-ocsp-cache.service" %}EOF
cat << EOF > /etc/systemd/system/nginx-ocsp-cache.timer
{% include "snippets/nginx-ocsp-cache.timer" %}EOF
systemctl enable nginx-ocsp-cache.service
systemctl enable nginx-ocsp-cache.timer
systemctl start nginx-ocsp-cache.service
systemctl start nginx-ocsp-cache.timer

View File

@ -1,9 +1,3 @@
# Use fully qualified name
test -e /sbin/uci && NAME=$(nslookup $(uci get network.wan.ipaddr) | grep "name =" | head -n1 | cut -d "=" -f 2 | xargs)
test -e /bin/hostname && NAME=$(hostname -f)
test -n "$NAME" || NAME=$(cat /proc/sys/kernel/hostname)
{% include "snippets/request-common.sh" %}
# Submit CSR and save signed certificate # Submit CSR and save signed certificate
curl --cert-status -f -L -H "Content-type: application/pkcs10" \ curl --cert-status -f -L -H "Content-type: application/pkcs10" \
--cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem \ --cacert /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem \

View File

@ -5,12 +5,14 @@ test -e /etc/pki/ca-trust/source/anchors \
# Insert into Ubuntu trust store, only applies to curl # Insert into Ubuntu trust store, only applies to curl
test -e /usr/local/share/ca-certificates/ \ test -e /usr/local/share/ca-certificates/ \
&& ln -s /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem /usr/local/share/ca-certificates/{{ session.authority.hostname }}.crt \ && ln -f -s /etc/certidude/authority/{{ session.authority.hostname }}/ca_cert.pem /usr/local/share/ca-certificates/{{ session.authority.hostname }}.crt \
&& update-ca-certificates && update-ca-certificates
# Patch Firefox trust store on Ubuntu # Patch Firefox trust store on Ubuntu
if [ ! -h /usr/lib/firefox/libnssckbi.so ]; then if [ -d /usr/lib/firefox ]; then
apt install p11-kit p11-kit-modules if [ ! -h /usr/lib/firefox/libnssckbi.so ]; then
apt install -y p11-kit p11-kit-modules
mv /usr/lib/firefox/libnssckbi.so /usr/lib/firefox/libnssckbi.so.bak mv /usr/lib/firefox/libnssckbi.so /usr/lib/firefox/libnssckbi.so.bak
ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox/libnssckbi.so ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so /usr/lib/firefox/libnssckbi.so
fi
fi fi

View File

@ -19,6 +19,10 @@
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#snippet-unix" role="tab" aria-controls="unix" aria-selected="false">UNIX</a> <a class="nav-link" id="contact-tab" data-toggle="tab" href="#snippet-unix" role="tab" aria-controls="unix" aria-selected="false">UNIX</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#snippet-nginx" role="tab" aria-controls="nginx" aria-selected="false">nginx</a>
</li>
{% if "openvpn" in session.service.protocols %} {% if "openvpn" in session.service.protocols %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#snippet-openvpn" role="tab" aria-controls="openvpn" aria-selected="false">OpenVPN</a> <a class="nav-link" id="contact-tab" data-toggle="tab" href="#snippet-openvpn" role="tab" aria-controls="openvpn" aria-selected="false">OpenVPN</a>
@ -69,19 +73,43 @@
<!-- UNIX-like --> <!-- UNIX-like -->
<div class="tab-pane fade" id="snippet-unix" role="tabpanel" aria-labelledby="unix"> <div class="tab-pane fade" id="snippet-unix" role="tabpanel" aria-labelledby="unix">
<p>For client certificates generate key pair and submit the signing request with common name set to short hostname:</p> <p>For client certificates generate key pair and submit the signing request with common name set to short hostname:</p>
<div class="highlight"> <div class="highlight">
<pre class="code"><code>{% include "snippets/request-client.sh" %}</code></pre> <pre class="code"><code>{% include "snippets/request-client.sh" %}</code></pre>
</div> </div>
<p>To renew:</p>
<p>For server certificates use fully qualified hostname as common name and sign request accordingly:</p>
<div class="highlight"> <div class="highlight">
<pre class="code"><code>{% include "snippets/request-server.sh" %}</code></pre> <pre class="code"><code>{% include "snippets/renew.sh" %}</code></pre>
</div>
</div> </div>
<p>To renew:</p> <!-- nginx -->
<div class="tab-pane fade" id="snippet-nginx" role="tabpanel" aria-labelledby="nginx">
<p>For server certificates use fully qualified hostname as common name and sign request accordingly:</p>
<div class="highlight">
<pre class="code"><code>apt install -y nginx curl
NAME=$(hostname -f)
{% include "snippets/request-common.sh" %}
{% include "snippets/submit-request-wait.sh" %}
{% include "snippets/setup-ocsp-caching.sh" %}
cat << \EOF > /etc/nginx/conf.d/tls.conf
{% include "snippets/nginx-tls.conf" %}EOF
cat << EOF > /etc/nginx/sites-available/https.conf
{% include "snippets/nginx-https-site.conf" %}EOF
test -h /etc/nginx/sites-enabled/https.conf \
|| ln -s ../sites-available/https.conf /etc/nginx/sites-enabled/https.conf
rm -fv /etc/nginx/sites-enabled/default
test -e {{ dhparam_path }} \
|| openssl dhparam -out {{ dhparam_path }} 2048
systemctl reload nginx
</code></pre>
</div>
<p>To renew create cron weekly job:</p>
<div class="highlight"> <div class="highlight">
<pre class="code"><code>{% include "snippets/renew.sh" %}</code></pre> <pre class="code"><code>{% include "snippets/renew.sh" %}</code></pre>
</div> </div>

View File

@ -5,4 +5,4 @@ at
<a target="_blank" href="http://{{ certificate.lease.inner_address }}">{{ certificate.lease.inner_address }}</a>{% if certificate.lease.outer_address %} <a target="_blank" href="http://{{ certificate.lease.inner_address }}">{{ certificate.lease.inner_address }}</a>{% if certificate.lease.outer_address %}
from from
<a target="{{ certificate.lease.outer_address }}" href="https://geoiplookup.net/ip/{{ certificate.lease.outer_address }}">{{ certificate.lease.outer_address }}</a>{% endif %}. <a target="{{ certificate.lease.outer_address }}" href="https://geoiplookup.net/ip/{{ certificate.lease.outer_address }}">{{ certificate.lease.outer_address }}</a>{% endif %}.
See some stats <a href="http://172.20.1.19:19999/host/{{ certificate.common_name }}/" target="_blank">here</a>.

View File

@ -75,7 +75,7 @@
<div class="bd-example"> <div class="bd-example">
<pre><code class="language-sh" data-lang="sh">wget <a href="/api/signed/{{ certificate.common_name }}/">http://{{ session.authority.hostname }}/api/signed/{{ certificate.common_name }}/</a> <pre><code class="language-sh" data-lang="sh">wget <a href="/api/signed/{{ certificate.common_name }}/">http://{{ session.authority.hostname }}/api/signed/{{ certificate.common_name }}/</a>
curl --cert-status http://{{ session.authority.hostname }}/api/signed/{{ certificate.common_name }}/ \ curl http://{{ session.authority.hostname }}/api/signed/{{ certificate.common_name }}/ \
| openssl x509 -text -noout</code></pre> | openssl x509 -text -noout</code></pre>
</div> </div>

View File

@ -273,15 +273,16 @@ def test_cli_setup_authority():
# - SCEP disabled # - SCEP disabled
# - CRL enabled # - CRL enabled
assert os.system("certidude setup authority --elliptic-curve") == 0 assert os.system("certidude provision authority --elliptic-curve") == 0
assert_cleanliness() assert_cleanliness()
assert os.path.exists("/var/lib/certidude/signed/ca.example.lan.pem"), "provisioning failed" assert os.path.exists("/var/lib/certidude/signed/ca.example.lan.pem"), "provisioning failed"
# Make sure nginx is running # Make sure nginx is running
os.system("systemctl restart certidude-backend")
os.system("systemctl start certidude-ocsp-cache.service")
assert os.system("nginx -t") == 0, "invalid nginx configuration" assert os.system("nginx -t") == 0, "invalid nginx configuration"
os.system("systemctl restart certidude")
os.system("systemctl restart nginx") os.system("systemctl restart nginx")
assert os.path.exists("/run/nginx.pid"), "nginx wasn't started up properly" assert os.path.exists("/run/nginx.pid"), "nginx wasn't started up properly"
@ -638,7 +639,7 @@ def test_cli_setup_authority():
# Test tagging integration in scripting framework # Test tagging integration in scripting framework
r = client().simulate_get("/api/signed/test/script/") r = client().simulate_get("/api/signed/test/script/")
assert r.status_code == 200, r.text # script render ok assert r.status_code == 200, r.text # script render ok
assert "curl https://ca.example.lan:8443/api/signed/test/attr " in r.text, r.text assert "curl --cert-status https://ca.example.lan:8443/api/signed/test/attr " in r.text, r.text
assert "Tartu" in r.text, r.text assert "Tartu" in r.text, r.text
r = client().simulate_post("/api/signed/test/tag/", r = client().simulate_post("/api/signed/test/tag/",
@ -751,13 +752,13 @@ def test_cli_setup_authority():
clean_client() clean_client()
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www", "ca.example.lan"]) result = runner.invoke(cli, ["provision", "nginx", "-cn", "www", "ca.example.lan"])
assert result.exception assert result.exception
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www.example.lan", "ca.example.lan"]) result = runner.invoke(cli, ["provision", "nginx", "-cn", "www.example.lan", "ca.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ["setup", "nginx", "-cn", "www.example.lan", "ca.example.lan"]) result = runner.invoke(cli, ["provision", "nginx", "-cn", "www.example.lan", "ca.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate assert not result.exception, result.output # client conf already exists, remove to regenerate
with open("/etc/certidude/client.conf", "a") as fh: with open("/etc/certidude/client.conf", "a") as fh:
@ -806,13 +807,13 @@ def test_cli_setup_authority():
if not os.path.exists("/etc/openvpn/keys"): if not os.path.exists("/etc/openvpn/keys"):
os.makedirs("/etc/openvpn/keys") os.makedirs("/etc/openvpn/keys")
result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn", "ca.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'server', "-cn", "vpn", "ca.example.lan"])
assert result.exception, result.output assert result.exception, result.output
result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate assert not result.exception, result.output # client conf already exists, remove to regenerate
with open("/etc/certidude/client.conf", "a") as fh: with open("/etc/certidude/client.conf", "a") as fh:
@ -849,10 +850,10 @@ def test_cli_setup_authority():
os.unlink("/etc/certidude/client.conf") os.unlink("/etc/certidude/client.conf")
os.unlink("/etc/certidude/services.conf") os.unlink("/etc/certidude/services.conf")
result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'client', "-cn", "roadwarrior1", "ca.example.lan", "vpn.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate assert not result.exception, result.output # client conf already exists, remove to regenerate
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
@ -866,7 +867,7 @@ def test_cli_setup_authority():
clean_client() clean_client()
result = runner.invoke(cli, ['setup', 'openvpn', 'networkmanager', "-cn", "roadwarrior3", "ca.example.lan", "vpn.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'networkmanager', "-cn", "roadwarrior3", "ca.example.lan", "vpn.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
@ -1116,16 +1117,16 @@ def test_cli_setup_authority():
clean_client() clean_client()
assert not os.path.exists("/var/lib/certidude/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"]) result = runner.invoke(cli, ['provision', 'strongswan', 'server', "-cn", "ipsec", "ca.example.lan"])
assert result.exception, result.output # FQDN required assert result.exception, result.output # FQDN required
assert not os.path.exists("/var/lib/certidude/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"]) result = runner.invoke(cli, ['provision', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
assert open("/etc/ipsec.secrets").read() == ": RSA /etc/certidude/authority/ca.example.lan/server_key.pem\n" 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/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"]) result = runner.invoke(cli, ['provision', 'strongswan', 'server', "-cn", "ipsec.example.lan", "ca.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate assert not result.exception, result.output # client conf already exists, remove to regenerate
assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem") assert not os.path.exists("/var/lib/certidude/signed/ipsec.example.lan.pem")
@ -1162,10 +1163,10 @@ def test_cli_setup_authority():
os.unlink("/etc/certidude/client.conf") os.unlink("/etc/certidude/client.conf")
os.unlink("/etc/certidude/services.conf") os.unlink("/etc/certidude/services.conf")
result = runner.invoke(cli, ['setup', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"]) result = runner.invoke(cli, ['provision', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ['setup', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"]) result = runner.invoke(cli, ['provision', 'strongswan', 'client', "-cn", "roadwarrior2", "ca.example.lan", "ipsec.example.lan"])
assert not result.exception, result.output # client conf already exists, remove to regenerate assert not result.exception, result.output # client conf already exists, remove to regenerate
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
@ -1178,7 +1179,7 @@ def test_cli_setup_authority():
clean_client() clean_client()
result = runner.invoke(cli, ['setup', 'strongswan', 'networkmanager', "-cn", "roadwarrior4", "ca.example.lan", "ipsec.example.lan"]) result = runner.invoke(cli, ['provision', 'strongswan', 'networkmanager', "-cn", "roadwarrior4", "ca.example.lan", "ipsec.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait"])
@ -1257,7 +1258,7 @@ def test_cli_setup_authority():
assert os.path.exists("/run/certidude/server.pid") assert os.path.exists("/run/certidude/server.pid")
pid_certidude = int(open("/run/certidude/server.pid").read()) pid_certidude = int(open("/run/certidude/server.pid").read())
os.system("systemctl stop certidude") os.system("systemctl stop certidude-backend")
assert not os.path.exists("/run/certidude/server.pid") assert not os.path.exists("/run/certidude/server.pid")
# Install packages # Install packages
@ -1321,16 +1322,13 @@ def test_cli_setup_authority():
# Bootstrap authority again with: # Bootstrap authority again with:
# - RSA certificates # - RSA certificates
# - Kerberos auth # - Kerberos auth
# - OCSP disabled
# - SCEP enabled # - SCEP enabled
# - CRL disabled # - CRL disabled
assert not os.path.exists("/var/lib/certidude/ca_key.pem") assert not os.path.exists("/var/lib/certidude/ca_key.pem")
assert os.system("certidude setup authority --skip-packages -o 'Demola LLC'") == 0 os.unlink("/etc/certidude/authority/ca.example.lan/ca_cert.pem")
assert os.system("certidude provision authority --skip-packages -o 'Demola LLC'") == 0
assert os.path.exists("/var/lib/certidude/ca_key.pem") assert os.path.exists("/var/lib/certidude/ca_key.pem")
assert os.path.exists("/etc/cron.daily/certidude")
assert os.path.exists("/etc/cron.hourly/certidude")
# Make modifications to /etc/certidude/server.conf so # Make modifications to /etc/certidude/server.conf so
# Certidude would auth against domain controller # Certidude would auth against domain controller
@ -1339,18 +1337,17 @@ def test_cli_setup_authority():
assert os.system("sed -e 's/autosign subnets =.*/autosign subnets =/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/autosign subnets =.*/autosign subnets =/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/machine enrollment subnets =.*/machine enrollment subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/machine enrollment subnets =.*/machine enrollment subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/scep subnets =.*/scep subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/scep subnets =.*/scep subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/ocsp subnets =.*/ocsp subnets =/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/crl subnets =.*/crl subnets =/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/crl subnets =.*/crl subnets =/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/address = certificates@example.lan/address =/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/address = certificates@example.lan/address =/g' -i /etc/certidude/server.conf") == 0
assert os.system("sed -e 's/kerberos subnets =.*/kerberos subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0 assert os.system("sed -e 's/kerberos subnets =.*/kerberos subnets = 0.0.0.0\\/0/g' -i /etc/certidude/server.conf") == 0
# Update server credential cache # Update server credential cache
assert os.system("/etc/cron.hourly/certidude") == 0 assert os.system("systemctl start certidude-ldap-kinit") == 0
assert os.path.exists("/run/certidude/krb5cc") assert os.path.exists("/run/certidude/krb5cc")
assert os.stat("/run/certidude/krb5cc").st_uid != 0, "Incorrect persmissions for /run/certidude/krb5cc" assert os.stat("/run/certidude/krb5cc").st_uid != 0, "Incorrect persmissions for /run/certidude/krb5cc"
# Start certidude backend # Start certidude backend
assert os.system("systemctl restart certidude") == 0 assert os.system("systemctl restart certidude-backend") == 0
cov_finished = False cov_finished = False
for path in os.listdir("/tmp/"): for path in os.listdir("/tmp/"):
@ -1393,14 +1390,6 @@ def test_cli_setup_authority():
r = requests.post("http://ca.example.lan/api/scep/") r = requests.post("http://ca.example.lan/api/scep/")
assert r.status_code == 405 assert r.status_code == 405
# OCSP should be disabled now
r = requests.get("http://ca.example.lan/api/ocsp/")
assert r.status_code == 404
r = requests.post("http://ca.example.lan/api/ocsp/")
assert r.status_code == 404
##################### #####################
### Kerberos auth ### ### Kerberos auth ###
@ -1460,7 +1449,7 @@ def test_cli_setup_authority():
clean_client() clean_client()
# Test non-matching CN # Test non-matching CN
result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "somethingelse", "ca.example.lan", "vpn.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'client', "-cn", "somethingelse", "ca.example.lan", "vpn.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait", "--kerberos"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait", "--kerberos"])
@ -1469,7 +1458,7 @@ def test_cli_setup_authority():
# With matching CN it should work # With matching CN it should work
clean_client() clean_client()
result = runner.invoke(cli, ['setup', 'openvpn', 'client', "-cn", "ca", "ca.example.lan", "vpn.example.lan"]) result = runner.invoke(cli, ['provision', 'openvpn', 'client', "-cn", "ca", "ca.example.lan", "vpn.example.lan"])
assert not result.exception, result.output assert not result.exception, result.output
result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait", "--kerberos"]) result = runner.invoke(cli, ["enroll", "--skip-self", "--no-wait", "--kerberos"])
@ -1509,7 +1498,7 @@ def test_cli_setup_authority():
assert not result.exception, result.output assert not result.exception, result.output
pid_certidude = int(open("/run/certidude/server.pid").read()) pid_certidude = int(open("/run/certidude/server.pid").read())
assert os.system("systemctl stop certidude") == 0 assert os.system("systemctl stop certidude-backend") == 0
cov_finished = False cov_finished = False
for path in os.listdir("/tmp/"): for path in os.listdir("/tmp/"):