Gestion des certificats
Les certificats sont gérés avec dehydrated
- /etc/dehydrated/domains.txt
chimrod.com lain.chimrod.com
Renouvellement par dns
Le renouvellement inclus le domaine lain.chimrod.com qui n’est pas publique. Afin d’avoir un certificat sur ce domaine, le challenge dns-01 qui consiste à mettre une chaine de caractère dans l’enregistrement dns lui meme est utilisé.
- /etc/dehydrated/conf.d/dns_gandi.sh
CHALLENGETYPE="dns-01" HOOK="/etc/dehydrated/hook.sh"
- /etc/dehydrated/hook.sh est un hook utilisé pour automatiser la connexion au registrar gandi et ajouter la clef demandée par le challenge.
#!/usr/bin/env bash # Hook script mainly adapted from https://www.aaflalo.me/2017/02/lets-encrypt-with-dehydrated-dns-01/ # set -x if [ -z "${API_KEY}" ]; then echo "Can't find API key. Please export API_KEY environment variable !" exit 1 fi API_ENDPOINT="https://dns.api.gandi.net/api/v5" deploy_challenge() { local HOST="$(echo $1 | rev | cut -d . -f -2 | rev)" local DOMAIN="${1}" RECORD="_acme-challenge.${1}." TOKEN_VALUE=${3} # This hook is called once for every domain that needs to be # validated, including any alternative names you may have listed. # # Parameters: # - DOMAIN # The domain name (CN or subject alternative name) being validated. # - TOKEN_FILENAME # The name of the file containing the token to be served for HTTP # validation. Should be served by your web server as # /.well-known/acme-challenge/${TOKEN_FILENAME}. # - TOKEN_VALUE # The token value that needs to be served for validation. For DNS # validation, this is what you want to put in the _acme-challenge # TXT record. For HTTP validation it is the value that is expected # be found in the $TOKEN_FILENAME file. if [ ! -z "${TOKEN_VALUE}" ]; then echo "Creating DNS TXT field [${RECORD}] with value [${TOKEN_VALUE}]." DATA='{"rrset_name": "'${RECORD}'", "rrset_type": "TXT", "rrset_ttl": 300, "rrset_values": ["'${TOKEN_VALUE}'"]}' curl -s -X POST -d "${DATA}" \ -H "X-Api-Key: ${API_KEY}" \ -H "Content-Type: application/json" \ "${API_ENDPOINT}/domains/${HOST}/records" # For debugging purpose # dig +trace "_acme-challenge.${DOMAIN}" TXT else echo "Something went wrong. Can not find token value to set for record ${RECORD}." exit 1 fi } clean_challenge() { local HOST="$(echo $1 | rev | cut -d . -f -2 | rev)" local DOMAIN="${1}" RECORD="_acme-challenge.${1}." # This hook is called after attempting to validate each domain, # whether or not validation was successful. Here you can delete # files or DNS records that are no longer needed. # # The parameters are the same as for deploy_challenge. echo "Deleting DNS TXT field [${RECORD}] for domain [${DOMAIN}]." curl -X DELETE -H "Content-Type: application/json" \ -H "X-Api-Key: ${API_KEY}" \ "${API_ENDPOINT}/domains/${HOST}/records/${RECORD}" } deploy_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}" # This hook is called once for each certificate that has been # produced. Here you might, for instance, copy your new certificates # to service-specific locations and reload the service. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - KEYFILE # The path of the file containing the private key. # - CERTFILE # The path of the file containing the signed certificate. # - FULLCHAINFILE # The path of the file containing the full certificate chain. # - CHAINFILE # The path of the file containing the intermediate certificate(s). # - TIMESTAMP # Timestamp when the specified certificate was created. echo "Generated files for ${DOMAIN} : key=[${KEYFILE}] - cert=[${CERTFILE}] - fullchain=[${FULLCHAINFILE}] - chain=[${CHAINFILE}] - timestamp=[${TIMESTAMP}]" } unchanged_cert() { local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" # This hook is called once for each certificate that is still # valid and therefore wasn't reissued. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - KEYFILE # The path of the file containing the private key. # - CERTFILE # The path of the file containing the signed certificate. # - FULLCHAINFILE # The path of the file containing the full certificate chain. # - CHAINFILE # The path of the file containing the intermediate certificate(s). echo "Cert info: keyfile=[${KEYFILE}] - certfile=[${CERTFILE}] - fullchain=[${FULLCHAINFILE}] - chain=[${CHAINFILE}]" echo "Cert for domain ${DOMAIN} is still valid. Nothing to do." } invalid_challenge() { local DOMAIN="${1}" RESPONSE="${2}" # This hook is called if the challenge response has failed, so domain # owners can be aware and act accordingly. # # Parameters: # - DOMAIN # The primary domain name, i.e. the certificate common # name (CN). # - RESPONSE # The response that the verification server returned echo "Challenge failed for [${DOMAIN}] - response was [${RESPONSE}]" } request_failure() { local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}" # This hook is called when a HTTP request fails (e.g., when the ACME # server is busy, returns an error, etc). It will be called upon any # response code that does not start with '2'. Useful to alert admins # about problems with requests. # # Parameters: # - STATUSCODE # The HTML status code that originated the error. # - REASON # The specified reason for the error. # - REQTYPE # The kind of request that was made (GET, POST...) echo "Query [${REQTYPE}] failed with error: [${REASON}] - status code: [${STATUSCODE}]" } exit_hook() { # This hook is called at the end of a dehydrated command and can be used # to do some final (cleanup or other) tasks. : } HANDLER="$1"; shift if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then "$HANDLER" "$@" fi
Attention, ce hook nécessite la clef API permettant de se connecter à gandi. Celle-ci est renseignée dans la tache cron hebdomadaire :
- /etc/cron.weekly/dehydrated
#!/bin/sh API_KEY="${API_KEY}" /usr/bin/dehydrated -c service apache2 reload
Utilisation dans apache
Une macro est disponible pour permettre aux site de mettre en place un certificat (celui doit avoir été créé avant)
- /etc/apache2/conf-available/dehydrated_ssl.conf
<Macro dehydrated_ssl $domain> ServerName $domain <IfFile /var/lib/dehydrated/certs/$domain/fullchain.pem> SSLengine ON # enable HTTP/2, if available Protocols h2 http/1.1 # HTTP Strict Transport Security (mod_headers is required) (63072000 seconds) Header always set Strict-Transport-Security "max-age=63072000" # intermediate configuration SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder on SSLSessionTickets off #SSLUseStapling On #SSLStaplingCache "shmcb:logs/ssl_stapling(32768)" SSLCertificateFile /var/lib/dehydrated/certs/$domain/fullchain.pem SSLCertificateKeyFile /var/lib/dehydrated/certs/$domain/privkey.pem </IfFile> </Macro>
La configuration peut etre regénérée à partir de ce site : https://ssl-config.mozilla.org/