DebOps PKI and ACME / Let's Encrypt integration

Main goal of the integration is to make switch from/to Let's Encrypt certificates and internal/external certificates easy and as painless as possible. The requirements are:

  • On first install, host cannot be authenticated right away to Let's Encrypt, so we need to setup our own internal PKI on Ansible Controller. Current debops.pki role should be modified to allow this.
  • Let's Encrypt certificates are expected to be renewed frequently, configuration should be designed around this. Keeping old certificates on each host past their vailidity period makes not much sense (backups should take care of this), but using only one “production set” of certificates might be prone to errors. The solution might be to use a pair of staging/production certificate sets and rotate them on successful renewal.
  • Let's Encrypt certificates should be available to all services, not just the web servers. The renewal/rotation scripts should allow for hooks that other roles can enable to for example, reload/restart services when necessary.
  • There need to be a specific set of files available for each “realm”:
    • cert.pem - clean signed certificate
    • intermediate.pem - a set of intermediate certificates
    • root.pem - a root CA certificate
    • key.pem - private key of the certificate
    • cert_intermediate.pem - certificate and intermediate CA certificates in one file, symlinked as chain.pem
    • key_chain.pem - private key and certificates in one file
    • intermediate_root.pem - set of intermediate certificates and Root CA certificate for OCSP stapling, symlinked as trusted.pem
  • Interaction with ACME server should be performed by a separate user account, for security reasons. This brings issues, because ACME account shouldn't have access to the private key, which is needed to generate additional sets of files after the certificate is signed. Some kind of signaling between UNIX accounts is needed to synchronize the files together.

PKI realm directory layout

A realm name can be a domain, or a string, or a symlink to a directory.

├── acme
│   ├── account_key.pem
│   ├── cert.pem
│   ├── gnutls.conf
│   ├── intermediate.pem
│   ├── openssl.conf
│   ├── request.pem
│   └── root.pem
├── CA.crt -> public/root.pem
├── default.crt -> public/chain.pem
├── default.key -> private/key.pem
├── default.pem -> private/key_chain.pem
├── external
│   ├── cert.pem
│   ├── gnutls.conf
│   ├── intermediate.pem
│   ├── openssl.conf
│   ├── request.pem
│   ├── root.pem
│   └── script
├── internal
│   ├── cert.pem
│   ├── gnutls.conf
│   ├── intermediate.pem
│   ├── openssl.conf
│   ├── request.pem
│   └── root.pem
├── pki-realm.conf
├── private
│   ├── key_chain.pem
│   └── key.pem
└── public
    ├── cert_intermediate.pem
    ├── cert.pem
    ├── chain.pem -> cert_intermediate.pem
    ├── intermediate.pem
    ├── intermediate_root.pem
    ├── root.pem
    └── trusted.pem -> intermediate_root.pem