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>
This commit is contained in:
		
							parent
							
								
									a2b1ef903b
								
							
						
					
					
						commit
						fc0ec9a3cc
					
				
					 2 changed files with 65 additions and 3 deletions
				
			
		| 
						 | 
					@ -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 a new issue