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
likeLOG_USER
orLOG_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
socertgrinder
can setcertgrinderd
command-line arguments.The
restrict
keyword to limit tunneling and forwarding and such (optional but recommended). Therestrict
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.18.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]
[--preferred-chain PREFERRED-CHAIN]
[--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
- command
Possible choices: get, show, help, ping
Command (required)
Named Arguments
- --acme-email
The email for the ACME account.
- --acme-server-url
The url for the ACME server to use.
- -z, --acme-zone
The DNS zone to pass to the auth hook script as env. var. ACMEZONE. For DNS-01 challenges.
- -A, --auth-hook
The hook script to call to prepare auth challenges before calling Certbot
- --certbot-command
The Certbot command to call between auth hook and cleanup hook
- --certbot-config-dir
The path to pass to Certbot as –config-dir
- --certbot-logs-dir
The path to pass to Certbot as –logs-dir
- --certbot-work-dir
The path to pass to Certbot as –work-dir
- -C, --cleanup-hook
The hook script to call to clean up auth challenges after calling Certbot
- -c, --config-file
The path to the certgrinderd config file to use, in YML format.
- --certificate-file
The path to the PEM formatted certificate chain file to use instead of getting it from stdin.
- --csr-file
The path to the PEM formatted CSR file to use instead of getting it from stdin.
- -d, --debug
Debug mode. Equal to setting –log-level=DEBUG.
- -l, --log-level
Possible choices: DEBUG, INFO, WARNING, ERROR, CRITICAL
Logging level. One of DEBUG, INFO, WARNING, ERROR, CRITICAL. Defaults to INFO.
- -p, --pid-dir
The directory to store the PID file in
- --preferred-chain
The preferred chain to use. Adds –preferred-chain to the Certbot command. Use to pick preferred signing chain when alternatives are available. Replace spaces with underscores in the chain name, so DST_Root_CA_X3 or ISRG_Root_X1 for prod or Fake_LE_Root_X1 or Fake_LE_Root_X2 for staging.
- --skip-acme-server-cert-verify
Do not verify the ACME servers certificate
- -s, --staging
Staging mode. Equal to setting –acme-server-url https://acme-staging-v02.api.letsencrypt.org/directory
- --syslog-facility
The facility to use for syslog messages
- --syslog-socket
The socket to use for syslog messages
- -t, --temp-dir
The directory to store temporary files in
- -w, --web-root
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
- subcommand
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
- subcommand
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 ofSubjectAltNames
.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, issuers: List[cryptography.x509.base.Certificate]) bool
Check the signature of the OCSP response.
- Parameters
ocsp_response – The OCSP response to check
issuers – A list of issuer(s)
- 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
orhttp
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
orHTTP-01
by callingself.run_certbot()
for each.If
self.conf["acme-zone"]
is set thenDNS-01
is attempted. Return if it results in a new certificate.If
self.conf["web-root"]
is set thenHTTP-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(certificate_bytes: bytes) cryptography.x509.base.Certificate
Parse and return individual certificate, or calls sys.exit(1) if something goes wrong.
- Parameters
certificate_bytes – A chunk of bytes representing a PEM certificate
- Returns
A cryptography.x509.Certificate object.
- classmethod parse_certificate_chain(certpath: Optional[str], expected_length: Optional[int] = None) List[cryptography.x509.base.Certificate]
Parse certificate chain from path or stdin.
- Parameters
certpath – The path of the certificate chain to parse (optional), chainbytes are taken from stdin if not provided.
expected_length – The number of certificates to expect. Optional.
- Returns
A list of cryptography.x509.Certificate objects in the order they appear in the input.
- 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 thecsrstring
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 byself.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