CERTGRINDERD(8)

NAME
certgrinderd - Manpage for certgrinderd

The Certgrinder server certgrinderd takes care of getting certificates and OCSP responses on behalf of the calling clients. certgrinderd doesn't run always like a daemon, so it never acts on its own. It only does something when a Certgrinder client runs it, usually over SSH.

The following sections explain the steps you need to setup a Certgrinder server.

INSTALL CERTGRINDER SERVER

Create a VM or Jail or Docker thing or whatever somewhere. This will be your Certgrinder server. Give it a proper public hostname like certgrinder.example.com. You can use real proper IP addresses or port forwarding, whichever you prefer. The relevant ports are TCP/22 (so the Certgrinder clients can reach the Certgrinder server), TCP/53 and UDP/53 if you want to serve DNS-01 challenges locally, and TCP/80 if you use HTTP-01 challenges locally.

Create DNS records for the new hostname (A+AAAA, and an SSHFP record wouldn't hurt) and you should be ready to begin the install.

The hostname of your Certgrinder server will be the hostname your Certgrinder clients use to SSH into (if you use SSH), and also the hostname you use to serve HTTP challenges locally (if you use HTTP-01 challenges).

CREATE USER

Create a dedicated user to run the Certgrinder server, usually the username is just certgrinderd. The user needs sudo access to run the certbot binary, and to set a couple of environment variables. This works:

certgrinderd ALL=(ALL) NOPASSWD: /usr/local/bin/certbot
Defaults env_keep += "ACMEZONE WEBROOT"

INSTALL CERTGRINDERD

You can install certgrinderd from pip with pip install certgrinderd. It will pull in the dependencies it needs automatically. Create a venv for it if you don't want to pollute the global Python env.

You can also checkout the Github repo and install the deps from requirements.txt by hand if you prefer. If you want to install with pip directly from Github the following may help: pip install "git+https://github.com/tykling/certgrinder/#egg=certgrinderd&subdirectory=server"

The Certgrinder server needs to be reachable from the outside world on port 53/80 if you plan to serve DNS/HTTP challenges locally. It also needs to be accessible over SSH from all your Certgrinder clients if you plan to use SSH.

CONFIGURATION

Configuration of certgrinderd can be done using command-line options, or a configuration file, or a combination of the two.

The certgrinderd configuration file is in YAML format. An example config named certgrinderd.conf.dist can be found in the distribution. use --config-file or -f to specify the config file location.

Each config item can be specified either in the YAML config file as a key: value pair, or on the commandline as --key value - the latter overriding the former if both are present. For example, if the configfile has log-level: INFO and the command-line has log-level: DEBUG then the effective log-level would be DEBUG.

This is an alphabetical list of the configurable options:

acme-email
    The email to use for the ACME account creation. Only required for the first run.

    Default: None

acme-server-url
    The URL for the ACME server.

    Default: https://acme-v02.api.letsencrypt.org/directory

acme-zone
    The DNS zone to pass to auth-hook script as environment variable ACMEZONE. Leave this unset to disable DNS-01 challenges.

    Default: None

auth-hook
    The script to run to prepare challenges before running Certbot.

    Default: manual-auth-hook.sh

certbot-command
    The Certbot command to run between the auth hook and the cleanup hook.

    Default: /usr/local/bin/sudo /usr/local/bin/certbot

certbot-config-dir
    The path to pass to Certbot as --config-dir.

    Default: None

certbot-logs-dir
    The path to pass to Certbot as --logs-dir.

    Default: None

certbot-work-dir
    The path to pass to Certbot as --logs-dir.

    Default: None

cleanup-hook
    The script to run to cleanup challenges after running Certbot.

    Default: manual-cleanup-hook.sh

config-file
    The path to the configuration file. The file is in YAML format.

    Default: None

debug
    Enables debug mode. This is the same as setting --log-level to DEBUG. Outputs lots info about the internal workings of certgrinderd.

    Default: False

log-level
    Sets the verbosity level for console and syslog logging. One of DEBUG, INFO, WARNING, ERROR, CRITICAL.

    Default: INFO

pid-dir
    The directory to place the certgrinderd PID file in.

    Default: /tmp

skip-acme-server-cert-verify
    Set to skip verification of the ACME servers TLS certificate. Used for testing, do not use in real world.

    Default: False

staging
    Enable staging mode. To make Certbot use the LetsEncrypt staging servers.

    Default: False

syslog-facility
    Set this and syslog-socket to enable logging to syslog. Must be a value supported by logging.handlers.SysLogHandler like LOG_USER or LOG_LOCAL0.

    Default: None

syslog-socket
    Set this and syslog-facility to enable logging to syslog.

    Default: None

temp-dir
    Set this to the directory to use for temporary files (CSR and certificates). Directory should be owned by the user running certgrinderd. A directory will be created and deleted inside this temp-dir for each run. Leave blank to create one automatically.

    Default: None

web-root
    The path to pass to the auth-hook script as environment variable WEBROOT. Leave this blank to disable HTTP-01 challenges.

    Default: None

    Finally the permitted domains for the current client must be specified as an environment variable (see next section).

RESTRICTING CLIENT HOSTNAMES

To determine whether a Certgrinder client is authorised to get a certificate for a given list of hostnames certgrinderd checks the environment variable named CERTGRINDERD_DOMAINSETS which must contain a semicolon-separated list of comma-separated lists of hostnames permitted for the current client.

For example, if the Certgrinder client was a webserver with two vhosts, one with the name example.net and another vhost with the two names example.com and www.example.com. In this case the environment variable CERTGRINDERD_DOMAINSETS="example.net;example.com,www.example.com" would permit the client to get the two certificates it needs, and nothing else.

The list of hostnames is case insensitive. IDN names can be in either IDNA or unicode format, meaning xn--plse-gra.example and pølse.example will both work. The order of the hostnames in the list does not matter.

CONFIGURE SSH ACCESS

Usually Certgrinder clients connect to the Certgrinder server using SSH, but other connection methods can be used if needed. The rest of this section is about configuring SSH access for clients.

Each Certgrinder client must generate an SSH key which is to be added to ~/.ssh/authorized_keys on the Certgrinder server. Each entry must be restricted with:

•   A from= specifying the IP the Certgrinder client connects from (optional but recommended).

•   An environment= restricting which names it may ask for, see above (required).

•   command= to restrict the command it can run (optional but recommended). Remember $SSH_ORIGINAL_COMMAND so certgrinder can set certgrinderd command-line arguments.

•   The restrict keyword to limit tunneling and forwarding and such (optional but recommended). The restrict option was added to OpenSSH in version 7.4, it might not be available everywhere.

    Something like this works:

    from="2001:DB8::15",environment="CERTGRINDERD_DOMAINSETS=example.com,www.example.com;example.net",command="/path/to/certgrinderd $SSH_ORIGINAL_COMMAND",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOegnR+qnK2FEoaSrVwHgCIxjFkVEbW4VO31/Hd2mAwk ansible-generated on webproxy2.example.com

    To make the environment= foo work the option PermitUserEnvironment=CERTGRINDERD_DOMAINSETS needs to be added to sshd_config.

AUTH AND CLEANUP HOOKS

The configured auth-hook and cleanup-hook scripts can be adapted as needed to update whatever local or remote web- or DNS-server you decide to use to serve challenges.

Both scripts get the same environment variables to work with:

$CERTBOT_DOMAIN
    The domain being authenticated, like www.example.com

$CERTBOT_VALIDATION
    The validation string (the secret which LE looks for)

$CERTBOT_TOKEN
    The filename containing the secret (only relevant for HTTP-01)

$ACMEZONE
    The DNS zone used for challenges (only relevant for DNS-01)

$WEBROOT
    The path to the webroot used for challenges (only relevant for HTTP-01)

    Both scripts must be able to handle the challenge type(s) you use. The same script will be called first for DNS-01 (if enabled), then for HTTP-01 (if enabled).

TESTING

When the server has been configured with hooks you can test from a client using just SSH and a manually generated CSR, with something like: cat mail4.example.com.csr | ssh certgrinderd@certgrinder.example.org -T -- --staging get certificate where -T is to prevent SSH from allocating a TTY on the server, -- is to mark the end of the SSH args, and --staging is to make certgrinderd use the LetsEncrypt staging servers. If all goes well it should output some logging and a certificate chain.

COMMAND LINE USAGE

certgrinderd version 0.17.0-dev. See the manpage certgrinderd(8) or ReadTheDocs for more info.

usage: certgrinderd [-h] [--acme-email ACME-EMAIL]
                    [--acme-server-url ACME-SERVER-URL] [-z ACME-ZONE]
                    [-A AUTH-HOOK] [--certbot-command CERTBOT-COMMAND]
                    [--certbot-config-dir CERTBOT-CONFIG-DIR]
                    [--certbot-logs-dir CERTBOT-LOGS-DIR]
                    [--certbot-work-dir CERTBOT-WORK-DIR] [-C CLEANUP-HOOK]
                    [-c CONFIG-FILE] [--certificate-file CERTIFICATE-FILE]
                    [--csr-file CSR-FILE] [-d]
                    [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-p PID-DIR]
                    [--skip-acme-server-cert-verify] [-s]
                    [--syslog-facility SYSLOG-FACILITY]
                    [--syslog-socket SYSLOG-SOCKET] [-t TEMP-DIR]
                    [-w WEB-ROOT]
                    {get,show,help,ping} ...

  Positional Arguments

Possible choices: get, show, help, ping

    Command (required)

  Named Arguments

The email for the ACME account.

The url for the ACME server to use.

The DNS zone to pass to the auth hook script as env. var. ACMEZONE. For DNS-01 challenges.

The hook script to call to prepare auth challenges before calling Certbot

The Certbot command to call between auth hook and cleanup hook

The path to pass to Certbot as --config-dir

The path to pass to Certbot as --logs-dir

The path to pass to Certbot as --work-dir

The hook script to call to clean up auth challenges after calling Certbot

The path to the certgrinderd config file to use, in YML format.

The path to the PEM formatted certificate chain file to use instead of getting it from stdin.

The path to the PEM formatted CSR file to use instead of getting it from stdin.

Debug mode. Equal to setting --log-level=DEBUG.

Possible choices: DEBUG, INFO, WARNING, ERROR, CRITICAL

    Logging level. One of DEBUG, INFO, WARNING, ERROR, CRITICAL. Defaults to INFO.

The directory to store the PID file in

Do not verify the ACME servers certificate

Staging mode. Equal to setting --acme-server-url https://acme-staging-v02.api.letsencrypt.org/directory

The facility to use for syslog messages

The socket to use for syslog messages

The directory to store temporary files in

The path to pass to the auth hook script as env WEBROOT to use for HTTP-01 challenges.

  Sub-commands:

  get

Use the "get" command to get certificates or OCSP responses

certgrinderd get [-h] {certificate,ocsp} ...

  Positional Arguments

Possible choices: certificate, ocsp

    Specify what to get using one of the available get sub-commands

  Sub-commands:

  certificate

Get a new certificate. Requires a CSR.

certgrinderd get certificate [-h]

  ocsp

Get an OCSP response for the provided certificate.

certgrinderd get ocsp [-h]

  show

Use the "show" command to show configuration, CSR info, or certificate info.

certgrinderd show [-h] {certificate,csr,configuration} ...

  Positional Arguments

Possible choices: certificate, csr, configuration

    Specify what to show using one of the available show sub-commands

  Sub-commands:

  certificate

Tell certgrinder to output information about the provided certificate.

certgrinderd show certificate [-h]

  csr

Tell certgrinder to output information about the provided CSR.

certgrinderd show csr [-h]

  configuration

Tell certgrinder to output the current configuration

certgrinderd show configuration [-h]

  help

The "help" command just outputs the usage help

certgrinderd help [-h]

  ping

The "ping" command is used by the certgrinder client to verify connectivity to the server. It just outputs the word "pong" to stdout.

certgrinderd ping [-h]

CLASS METHODS

class certgrinderd.Certgrinderd(userconfig: Optional[Dict[str, Optional[Union[str, bool]]]] = None)
    The Certgrinderd server class.

__init__(userconfig: Optional[Dict[str, Optional[Union[str, bool]]]] = None) -> None
    Merge userconfig with defaults and configure logging.

Parameters
    userconfig -- A dict of configuration to merge with default config

Returns
    None

static check_csr(csr: cryptography.hazmat.backends.openssl.x509._CertificateSigningRequest) -> bool
    Check that this CSR is valid, all things considered.

    First check that the CSR has exactly one CommonName, and that that CN is also present in the list of SubjectAltNames.

    Then make sure that the environment var CERTGRINDERD_DOMAINSETS exists and contains all the names from the CSR in one of the domainsets.

Parameters
    csr -- The CSR object

Returns
    True if the CSR is OK, False otherwise

classmethod check_ocsp_response(ocsp_request: cryptography.hazmat.backends.openssl.ocsp._OCSPRequest, ocsp_response: cryptography.hazmat.backends.openssl.ocsp._OCSPResponse, certificate: cryptography.x509.base.Certificate, issuer: cryptography.x509.base.Certificate) -> bool
    Check that the OCSP response is valid for the OCSP request and cert/issuer.

    Return True if the OCSP response is good, regardless of the certificate revocation status. Implements all the checks in RFC2560 3.2.

Parameters

•   ocsp_request -- The OCSP request object to check

•   ocsp_response -- The OCSP response object to check

•   certificate -- The certificate the OCSP request is for

•   issuer -- The issuer of the certificate

Returns
    True if the OCSP response is valid, False if not

static check_ocsp_response_issuer(ocsp_request: cryptography.hazmat.backends.openssl.ocsp._OCSPRequest, ocsp_response: cryptography.hazmat.backends.openssl.ocsp._OCSPResponse) -> bool
    Check that the response matches the request.

Parameters

•   ocsp_request -- The OCSP request object

•   ocsp_response -- The OCSP response object

Returns
    Boolean - True if all is well, False if a problem was found

classmethod check_ocsp_response_signature(ocsp_response: cryptography.hazmat.backends.openssl.ocsp._OCSPResponse, issuer: cryptography.x509.base.Certificate) -> bool
    Check the signature of the OCSP response.

Parameters
    ocsp_response -- The OCSP response to check

Returns
    Boolean - True if all is well, False if a problem was found

static check_ocsp_response_timing(ocsp_response: cryptography.hazmat.backends.openssl.ocsp._OCSPResponse) -> bool
    Check the timestamps of the OCSP response.

Parameters
    ocsp_response -- The OCSP response object to check

Returns
    Boolean - True if all is well, False if a problem was found

classmethod create_ocsp_request(certificate: cryptography.x509.base.Certificate, issuer: cryptography.x509.base.Certificate) -> cryptography.hazmat.backends.openssl.ocsp._OCSPRequest
    Create and return an OCSP request based on the cert+issuer.

Parameters

•   certificate -- The certificate to create an OCSP request for

•   issuer -- The issuer of the certificate

Returns
    The OCSP request

get_certbot_command(challengetype: str, csrpath: str, fullchainpath: str, certpath: str, chainpath: str) -> List[str]
    Put the certbot command together.

    Start with self.conf["certbot-command"] and append all the needed options.

    Optionally add --email and a bunch of certbot settings as needed.

Parameters

•   challengetype -- The type of challenge, dns or http

•   csrpath -- The path to the CSR

•   fullchainpath -- The path to save the certificate+issuer

•   certpath -- The path to save the certificate (without issuer)

•   chainpath -- The path to save the issuer (without certificate)

Returns
    The certbot command as a list

get_certificate(csrpath: str) -> None
    Get a cert using DNS-01 or HTTP-01 by calling self.run_certbot() for each.

    If self.conf["acme-zone"] is set then DNS-01 is attempted. Return if it results in a new certificate.

    If self.conf["web-root"] is set then HTTP-01 is attempted. Return if it results in a new certificate.

    If there is still no certificate log an error and return anyway.

Parameters
    csrpath -- The path to the CSR

Returns
    None

get_certificate_command() -> None
    This method is called when the get certificate subcommand is used.

Parameters
    None --

Returns
    None

get_ocsp_command() -> None
    This method is called when the get ocsp subcommand is used.

    It simply prints the DER formatted OCSP response to stdout if we get one.

Parameters
    None --

Returns
    None

get_ocsp_response(certpath: Optional[str]) -> cryptography.hazmat.backends.openssl.ocsp._OCSPResponse
    Parse certificate, get and return OCSP response.

Parameters
    certpath -- The path of the certificate chain to get OCSP response for (optional)

Returns
    The OCSPRequest object

classmethod parse_certificate_chain(certpath: Optional[str]) -> Tuple[cryptography.x509.base.Certificate, cryptography.x509.base.Certificate]
    Parse certificate chain from path or stdin.

Parameters
    certpath -- The path of the certificate chain to parse (optional)

Returns
    A tuple of (certificate, issuer) cryptography.x509.Certificate objects

static parse_csr(csrstring: str = '') -> cryptography.hazmat.backends.openssl.x509._CertificateSigningRequest
    Parse CSR with cryptography.x509.load_pem_x509_csr(), return CSR object.

    Takes the CSR data from sys.stdin if the csrstring argument is empty.

Parameters
    csrstring -- The PEM formatted CSR as a string (optional)

Returns
    The CSR object

static ping_command() -> None
    Reply to the ping command by outputting the string 'pong' to stdout.

    Args: None Returns: None

process_csr(csrpath: str = '') -> None
    Load the CSR, use it to get a certificate, and cleanup.

    Calls self.parse_csr() followed by self.check_csr(), and then exists if any problems are found with the CSR.

    Then self.get_certificate() is called, which in turn calls Certbot, which writes the certificate to stdout.

    Finally the CSR is deleted.

Parameters
    None --

Returns
    None

run_certbot(command: List[str], env: Dict[str, str], fullchainpath: str) -> bool
    Call certbot, check exitcode, output cert, return bool success.

Parameters

•   command -- A list of certbot command elements

•   env -- A dictionary of the environment to pass to subprocess.run()

•   fullchainpath -- The path to read the certificate+chain from after Certbot runs

Returns
    True if Certbot command exitcode was 0, False otherwise

static save_csr(csr: cryptography.hazmat.backends.openssl.x509._CertificateSigningRequest, path: str) -> None
    Save the CSR object to the path in PEM format.

Parameters

•   csr -- The CSR object

•   path -- The path to save it in

Returns
    None

static split_pem_chain(pem_chain_bytes: bytes) -> List[bytes]
    Split a PEM chain into a list of bytes of the individual PEM certificates.

Parameters
    pem_chain_bytes -- The bytes representing the PEM chain

Returns
    A list of 0 or more bytes chunks representing each certificate

static verify_signature(pubkey: Union[cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey], signature: bytes, payload: bytes, hashalgo: cryptography.hazmat.primitives.hashes.HashAlgorithm) -> bool
    Verify a signature on a payload using the provided public key and hash algorithm.

    Supports RSA and EC public keys. Assumes PKCS1v15 padding for RSA keys.

Parameters

•   pubkey -- The public key

•   signature -- The bytes representing the signature

•   payload -- The bytes representing the signed data

•   hashalgo -- The hashing algorithm used for the signature

AUTHOR
Thomas Steen Rasmussen

COPYRIGHT
2020, Thomas Steen Rasmussen