Skip to content
This repository was archived by the owner on Feb 4, 2025. It is now read-only.

minvws/nl-uzi-acme-ca-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ACME CA Server

This project was initially forked from this repository. Here we made some changes to setup a PoC for our custom ACME challenge (PR), improve the code quality and add some documentation. This ACME server should not be used in a production environment.

Since the project was developed in a PoC phase, we only tested the changes manually. This means Certbot can not be used to test the custom challenge.

Features

  • ACME Server implementation (http-01 challenge)
  • Builtin CA to sign/revoke certificates (can be replaced with an external CA), CA rollover is supported
  • Notification Mails (account created, certificate will expire soon, certificate is expired) with customizable templates
  • Web UI (certificate log) with customizable templates

Tested with Certbot, Traefik, Caddy, uacme, acme.sh.

Setup

1. Generate a CA root certificate (or use an existing cert)

$ openssl genrsa -out ca.key 4096
$ openssl req -new -x509 -nodes -days 3650 -subj "/C=DE/O=Demo" -key ca.key -out ca.pem

2. Deploy the container

Docker Compose snippet:

version: '2.4'
services:

  acme-ca-server:
    image: knrdl/acme-ca-server
    restart: always
    environment:
      EXTERNAL_URL: http://localhost:8080
      DB_DSN: postgresql://postgres:secret@db/postgres
    # ports:
    #   - "8080:8080"
    networks:
      - net
    volumes:
      - ./ca.key:/import/ca.key:ro # needed once to import new ca
      - ./ca.pem:/import/ca.pem:ro # needed once to import new ca
    mem_limit: 250m

  db:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_PASSWORD: secret
    networks:
      - net
    volumes:
      - ./db:/var/lib/postgresql/data
    mem_limit: 250m

networks:
  net:

3. Reverse proxy

Serve the app behind a TLS terminating reverse proxy, e.g. as https://acme.mydomain.org

The app listens on port 8080 for http traffic.

4. Test with certbot

docker run -it --rm certbot/certbot certonly --server https://acme.mydomain.org/acme/directory --standalone --no-eff-email --email [email protected] -v --domains test1.mydomain.org

Customizations

Environment Variables

Env Var Default Description
EXTERNAL_URL The HTTPS address the server will be reachable from, e.g. https://acme.mydomain.org
DB_DSN Postgres connection string, e.g. postgresql://username:password@host/dbname (database will be initialized on startup)
ACME_TERMS_OF_SERVICE_URL None Optional URL which the ACME client can show when the user has to accept the terms of service, e.g. https://acme.mydomain.org/terms
ACME_MAIL_TARGET_REGEX any mail address restrict the email address which must be provided to the ACME client by the user. E.g. [^@]+@mydomain\.org only allows mail addresses from mydomain.org
ACME_TARGET_DOMAIN_REGEX any non-wildcard domain name restrict the domain names for which certificates can be requested via ACME. E.g. [^\*]+\.mydomain\.org only allows domain names from mydomain.org
CA_ENABLED True whether the internal CA is enabled, set this to false when providing a custom CA implementation
CA_CERT_LIFETIME 60 days (60d) how often certs must be replaced by the ACME client
CA_CRL_LIFETIME 7 days (7d) how often the certificate revocation list will be rebuilt (despite rebuild on every certificate revocation)
CA_ENCRYPTION_KEY will be generated if not provided the key to protect the CA private keys on rest (always stored encrypted in the database)
MAIL_ENABLED False if sending emails is enabled
MAIL_HOST None smtp host
MAIL_PORT None smtp port (default depends on encryption method)
MAIL_USERNAME None smtp auth username
MAIL_PASSWORD None smtp auth password
MAIL_ENCRYPTION tls transport encryption method: tls (recommended), starttls or plain (unencrypted)
MAIL_SENDER None the email address shown when sending mails, e.g. [email protected]
MAIL_NOTIFY_ON_ACCOUNT_CREATION True whether to send a mail when the user runs ACME for the first time
MAIL_WARN_BEFORE_CERT_EXPIRES 20 days (20d) when to warn the user via mail that a certificate has not been renewed in time (can be disabled by providing false as value)
MAIL_NOTIFY_WHEN_CERT_EXPIRED True whether to inform the user that a certificate finally expired which has not been renewed in time
WEB_ENABLED True whether to also provide UI endpoints or just the ACME functionality
WEB_ENABLE_PUBLIC_LOG False whether to show a transparency log of all certificates generated via ACME
WEB_APP_TITLE ACME CA Server title shown in web and mails
WEB_APP_DESCRIPTION Self hosted ACME CA Server description shown in web and mails

Customize templates

Mail

Templates consist of subject.txt and body.html (see here). Overwrite the following files:

  • /app/mail/templates/cert-expired-info/{subject.txt,body.html}
  • /app/mail/templates/cert-expires-warning/{subject.txt,body.html}
  • /app/mail/templates/new-account-info/{subject.txt,body.html}

Template parameters:

  • app_title: str application title from WEB_APP_TITLE
  • app_desc: str application description from WEB_APP_DESCRIPTION
  • web_url: str web index url from EXTERNAL_URL
  • acme_url: str acme directory url
  • domains: list[str] list of expiring domains
  • expires_at: datetime domain expiration date
  • expires_in_days: int days until cert will expire
  • serial_number: str expiring certs serial number (hex)

Web UI

Custom files to be served by the http server can be placed in /app/web/www.

Overwrite templates (see here):

  • /app/web/templates/cert-log.html (Certificate Listing)
  • /app/web/templates/domain-log.html (Domain Listing)
  • /app/web/templates/index.html (Startpage)

Template parameters:

  • app_title: str application title from WEB_APP_TITLE
  • app_desc: str application description from WEB_APP_DESCRIPTION
  • web_url: str web index url from EXTERNAL_URL
  • acme_url: str acme directory url
  • certs: list list of certs for cert-log.html
  • domains: list list of domains for domain-log.html

Provide a custom CA implementation

First set env var CA_ENABLED=False. Then overwrite the file /app/ca/service.py (see here) in the docker image. It must provide two functions:

1. sign_csr()

async def sign_csr(csr: x509.CertificateSigningRequest, subject_domain: str, san_domains: list[str]) -> SignedCertInfo:
    ...
  • csr: a x509.CertificateSigningRequest object
  • subject_domain: the main domain name for the certificate
  • san_domains: subject alternative names, all domain names (including subject_domain) for the certificate
  • returns: instance of SignedCertInfo()
class SignedCertInfo:
    cert: x509.Certificate
    cert_chain_pem: str
  • cert: a x509.Certificate object
  • cert_chain_pem: a PEM-encoded text file containing the created cert as well as the root or also intermediate cert. This file will be used by the ACME client

2. revoke_cert()

async def revoke_cert(serial_number: str, revocations: set[tuple[str, datetime]]) -> None:
    ...
  • serial_number: certificate serial number to revoke as hex value
  • revocations: all revoked certificates including the one specified by serial_number. It's a set of tuples containing (serial_number, revocation_date)
  • returns: no error on success, throw exception otherwise

A custom CA backend must also handle the CRL (certificate revocation list) distribution.

Internals

Entities

flowchart LR
    accounts -->|1:0..n| orders
    orders -->|1:1..n| authorizations
    authorizations -->|1:1| challenges
    orders -->|1:0..1| certificates
Loading