services: certbot: Create self-signed certificates before certbot runs.
* gnu/services/certbot.scm (<certificate-configuration>): Add start-self-signed? field. (generate-certificate-gexp): New procedure. (certbot-activation): Generate self-signed certificates when start-self-signed? is #t. * doc/guix.texi (Certificate services): Document start-self-signed?. Change-Id: Icfd85ae0c3e29324acbcde6ba283546cf0e27a1d Signed-off-by: Clément Lassieur <clement@lassieur.org>master
parent
a2b1ef903b
commit
fc0ec9a3cc
|
@ -32690,6 +32690,12 @@ certificates and keys; the shell variable @code{$RENEWED_DOMAINS} will
|
||||||
contain a space-delimited list of renewed certificate domains (for
|
contain a space-delimited list of renewed certificate domains (for
|
||||||
example, @samp{"example.com www.example.com"}.
|
example, @samp{"example.com www.example.com"}.
|
||||||
|
|
||||||
|
@item @code{start-self-signed?} (default: @code{#t})
|
||||||
|
Whether to generate an initial self-signed certificate during system
|
||||||
|
activation. This option is particularly useful to allow @code{nginx} to
|
||||||
|
start before @code{certbot} has run, because @code{certbot} relies on
|
||||||
|
@code{nginx} running to perform HTTP challenges.
|
||||||
|
|
||||||
@end table
|
@end table
|
||||||
@end deftp
|
@end deftp
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#:use-module (guix records)
|
#:use-module (guix records)
|
||||||
#:use-module (guix gexp)
|
#:use-module (guix gexp)
|
||||||
#:use-module (srfi srfi-1)
|
#:use-module (srfi srfi-1)
|
||||||
|
#:use-module (ice-9 format)
|
||||||
#:use-module (ice-9 match)
|
#:use-module (ice-9 match)
|
||||||
#:export (certbot-service-type
|
#:export (certbot-service-type
|
||||||
certbot-configuration
|
certbot-configuration
|
||||||
|
@ -64,7 +65,9 @@
|
||||||
(cleanup-hook certificate-cleanup-hook
|
(cleanup-hook certificate-cleanup-hook
|
||||||
(default #f))
|
(default #f))
|
||||||
(deploy-hook certificate-configuration-deploy-hook
|
(deploy-hook certificate-configuration-deploy-hook
|
||||||
(default #f)))
|
(default #f))
|
||||||
|
(start-self-signed? certificate-configuration-start-self-signed?
|
||||||
|
(default #t)))
|
||||||
|
|
||||||
(define-record-type* <certbot-configuration>
|
(define-record-type* <certbot-configuration>
|
||||||
certbot-configuration make-certbot-configuration
|
certbot-configuration make-certbot-configuration
|
||||||
|
@ -91,7 +94,10 @@
|
||||||
(define (certbot-deploy-hook name deploy-hook-script)
|
(define (certbot-deploy-hook name deploy-hook-script)
|
||||||
"Returns a gexp which creates symlinks for privkey.pem and fullchain.pem
|
"Returns a gexp which creates symlinks for privkey.pem and fullchain.pem
|
||||||
from /etc/certs/NAME to /etc/letsenctypt/live/NAME. If DEPLOY-HOOK-SCRIPT is
|
from /etc/certs/NAME to /etc/letsenctypt/live/NAME. If DEPLOY-HOOK-SCRIPT is
|
||||||
not #f then it is run after the symlinks have been created."
|
not #f then it is run after the symlinks have been created. This wrapping is
|
||||||
|
necessary for certificates with start-self-signed? set to #t, as it will
|
||||||
|
overwrite the initial self-signed certificates upon the first successful
|
||||||
|
deploy."
|
||||||
(program-file
|
(program-file
|
||||||
(string-append name "-deploy-hook")
|
(string-append name "-deploy-hook")
|
||||||
(with-imported-modules '((guix build utils))
|
(with-imported-modules '((guix build utils))
|
||||||
|
@ -108,7 +114,8 @@ not #f then it is run after the symlinks have been created."
|
||||||
"/etc/letsencrypt/live/" name "/fullchain.pem")
|
"/etc/letsencrypt/live/" name "/fullchain.pem")
|
||||||
#$(string-append "/etc/certs/" name "/fullchain.pem.new"))
|
#$(string-append "/etc/certs/" name "/fullchain.pem.new"))
|
||||||
|
|
||||||
;; Rename over the top of the old ones, if there are any.
|
;; Rename over the top of the old ones, just in case they were the
|
||||||
|
;; original self-signed certificates.
|
||||||
(rename-file #$(string-append "/etc/certs/" name "/privkey.pem.new")
|
(rename-file #$(string-append "/etc/certs/" name "/privkey.pem.new")
|
||||||
#$(string-append "/etc/certs/" name "/privkey.pem"))
|
#$(string-append "/etc/certs/" name "/privkey.pem"))
|
||||||
(rename-file #$(string-append "/etc/certs/" name "/fullchain.pem.new")
|
(rename-file #$(string-append "/etc/certs/" name "/fullchain.pem.new")
|
||||||
|
@ -184,6 +191,47 @@ not #f then it is run after the symlinks have been created."
|
||||||
#~(job '(next-minute-from (next-hour '(0 12)) (list (random 60)))
|
#~(job '(next-minute-from (next-hour '(0 12)) (list (random 60)))
|
||||||
#$(certbot-command config))))
|
#$(certbot-command config))))
|
||||||
|
|
||||||
|
(define (generate-certificate-gexp certbot-cert-directory rsa-key-size)
|
||||||
|
(match-lambda
|
||||||
|
(($ <certificate-configuration> name (primary-domain other-domains ...)
|
||||||
|
challenge
|
||||||
|
csr authentication-hook
|
||||||
|
cleanup-hook deploy-hook)
|
||||||
|
(let (;; Arbitrary default subject, with just the
|
||||||
|
;; right domain filled in. These values don't
|
||||||
|
;; have any real significance.
|
||||||
|
(subject (string-append
|
||||||
|
"/C=US/ST=Oregon/L=Portland/O=Company Name/OU=Org/CN="
|
||||||
|
primary-domain))
|
||||||
|
(alt-names (if (null? other-domains)
|
||||||
|
#f
|
||||||
|
(format #f "subjectAltName=~{DNS:~a~^,~}"
|
||||||
|
other-domains)))
|
||||||
|
(directory (string-append "/etc/certs/" (or name primary-domain))))
|
||||||
|
#~(when (not (file-exists? #$directory))
|
||||||
|
;; We generate self-signed certificates in /etc/certs/{domain},
|
||||||
|
;; because certbot is very sensitive to its directory
|
||||||
|
;; structure. It refuses to write over the top of existing files,
|
||||||
|
;; so we need to use a directory outside of its control.
|
||||||
|
;;
|
||||||
|
;; These certificates are overwritten by the certbot deploy hook
|
||||||
|
;; the first time it successfully obtains a letsencrypt-signed
|
||||||
|
;; certificate.
|
||||||
|
(mkdir-p #$directory)
|
||||||
|
(chmod #$directory #o755)
|
||||||
|
(invoke #$(file-append openssl "/bin/openssl")
|
||||||
|
"req" "-x509"
|
||||||
|
"-newkey" #$(string-append "rsa:" (or rsa-key-size "4096"))
|
||||||
|
"-keyout" #$(string-append directory "/privkey.pem")
|
||||||
|
"-out" #$(string-append directory "/fullchain.pem")
|
||||||
|
"-sha256"
|
||||||
|
"-days" "1" ; Only one day, because we expect certbot to run
|
||||||
|
"-nodes"
|
||||||
|
"-subj" #$subject
|
||||||
|
#$@(if alt-names
|
||||||
|
(list "-addext" alt-names)
|
||||||
|
(list))))))))
|
||||||
|
|
||||||
(define (certbot-activation config)
|
(define (certbot-activation config)
|
||||||
(let* ((certbot-directory "/var/lib/certbot")
|
(let* ((certbot-directory "/var/lib/certbot")
|
||||||
(certbot-cert-directory "/etc/letsencrypt/live")
|
(certbot-cert-directory "/etc/letsencrypt/live")
|
||||||
|
@ -198,6 +246,14 @@ not #f then it is run after the symlinks have been created."
|
||||||
(mkdir-p #$webroot)
|
(mkdir-p #$webroot)
|
||||||
(mkdir-p #$certbot-directory)
|
(mkdir-p #$certbot-directory)
|
||||||
(mkdir-p #$certbot-cert-directory)
|
(mkdir-p #$certbot-cert-directory)
|
||||||
|
|
||||||
|
#$@(let ((rsa-key-size (and rsa-key-size
|
||||||
|
(number->string rsa-key-size))))
|
||||||
|
(map (generate-certificate-gexp certbot-cert-directory
|
||||||
|
rsa-key-size)
|
||||||
|
(filter certificate-configuration-start-self-signed?
|
||||||
|
certificates)))
|
||||||
|
|
||||||
(copy-file #$(certbot-command config) #$script)
|
(copy-file #$(certbot-command config) #$script)
|
||||||
(display #$message)))))))
|
(display #$message)))))))
|
||||||
|
|
||||||
|
|
Reference in New Issue