aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnu/local.mk1
-rw-r--r--gnu/services/ca.scm163
2 files changed, 164 insertions, 0 deletions
diff --git a/gnu/local.mk b/gnu/local.mk
index 8d8c552a4d..3c64946679 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -706,6 +706,7 @@ GNU_SYSTEM_MODULES = \
%D%/services/avahi.scm \
%D%/services/base.scm \
%D%/services/backup.scm \
+ %D%/services/ca.scm \
%D%/services/certbot.scm \
%D%/services/cgit.scm \
%D%/services/ci.scm \
diff --git a/gnu/services/ca.scm b/gnu/services/ca.scm
new file mode 100644
index 0000000000..1b10ef3e1c
--- /dev/null
+++ b/gnu/services/ca.scm
@@ -0,0 +1,163 @@
+;;; SPDX-License-Identifier: CC0-1.0
+;;;
+;;; Copyright © 2023, 2024 Wojtek Kosior <koszko@koszko.org>
+
+(define-module (gnu services ca)
+ #:use-module ((srfi srfi-1) #:select (concatenate))
+ #:use-module ((guix records) #:select (define-record-type* match-record))
+ #:use-module ((guix gexp) #:select (gexp file-append))
+ #:use-module ((gnu services) #:select
+ (service-extension activation-service-type service-type))
+ #:use-module ((gnu packages tls) #:select (openssl))
+ #:export (snakeoil-service-type
+ snakeoil-configuration
+ snakeoil-configuration?
+ snakeoil-cert-configuration))
+
+(define-record-type* <snakeoil-cert-configuration> snakeoil-cert-configuration
+ make-snakeoil-cert-configuration snakeoil-cert-configuration?
+ (name snakeoil-cert-configuration-name
+ (default #f))
+ (domains snakeoil-cert-configuration-domains
+ (default '()))
+ (key-group-owner snakeoil-cert-configuration-key-group-owner
+ (default #f)))
+
+(define-record-type* <snakeoil-configuration> snakeoil-configuration
+ make-snakeoil-configuration snakeoil-configuration?
+ (openssl snakeoil-configuration-openssl
+ (default openssl))
+ (owner snakeoil-configuration-owner
+ (default "root"))
+ (group-owner snakeoil-configuration-group-owner
+ (default #f))
+ (rsa-key-size snakeoil-configuration-rsa-key-size
+ (default 4096))
+ (certificates snakeoil-configuration-certificates
+ (default '())))
+
+(define (snakeoil-activation config)
+ (match-record config <snakeoil-configuration>
+ (openssl owner group-owner rsa-key-size certificates)
+ #~(begin
+ (use-modules (ice-9 format)
+ (ice-9 textual-ports))
+
+ (define openssl
+ #$(file-append openssl "/bin/openssl"))
+
+ (define pwnam
+ (getpw #$owner))
+
+ (define grnam
+ (and=> #$group-owner getgr))
+
+ (define (chown-file filename)
+ (chown filename (passwd:uid pwnam) (or (and=> grnam group:gid) -1)))
+
+ (define (generate-private-key path)
+ (let ((initial-umask (umask))
+ (tmp-path (string-append path ".tmp.pem")))
+ (umask #o027)
+ (system* openssl "genrsa" "-out" tmp-path
+ #$(number->string rsa-key-size))
+ (rename-file tmp-path path)
+ (umask initial-umask)))
+
+ (mkdir-p "/etc/snakeoil/certs")
+
+ (unless (false-if-exception
+ (stat "/etc/snakeoil/private/key.pem"))
+ (mkdir-p "/etc/snakeoil/private/")
+ (generate-private-key "/etc/snakeoil/private/key.pem"))
+
+ (unless (false-if-exception
+ (stat "/etc/snakeoil/root-cert.pem"))
+ ;; Self-sign a local certificate authority.
+ (system* openssl "req"
+ "-x509" "-new" "-nodes" "-sha256"
+ "-days" (number->string (* 20 365))
+ "-out" "/etc/snakeoil/root-cert.pem"
+ "-key" "/etc/snakeoil/private/key.pem"
+ "-subj" "/CN=snakeoil-root"))
+
+ (chown-file "/etc/snakeoil")
+ (chown-file "/etc/snakeoil/certs")
+ (chown-file "/etc/snakeoil/private")
+ (chown-file "/etc/snakeoil/private/key.pem")
+ (chown-file "/etc/snakeoil/root-cert.pem")
+
+ (define (issue-cert cert-name domains key-group-owner)
+ (let* ((cert-name* (or cert-name (car domains)))
+ (cert-dir (format #f "/etc/snakeoil/certs/~a" cert-name*))
+ (tmp-dir (format #f "/etc/snakeoil/certs-tmp/~a" cert-name*))
+ (cert-path (format #f "~a/cert.pem" tmp-dir))
+ (chain-link-path (format #f "~a/chain.pem" tmp-dir))
+ (key-path (format #f "~a/privkey.pem" tmp-dir))
+ (fullchain-path (format #f "~a/fullchain.pem" tmp-dir))
+ (csr-path (format #f "~a/csr.pem" tmp-dir)))
+ (unless (false-if-exception (stat cert-dir))
+ (system* "rm" "-rf" tmp-dir)
+ (mkdir-p tmp-dir)
+
+ (generate-private-key key-path)
+
+ (system* openssl "req"
+ "-new" "-nodes" "-out" csr-path
+ "-key" key-path
+ "-subj" (format #f "/CN=~a" (car domains))
+ "-addext" (format #f "subjectAltName=~{DNS:~a~^,~}"
+ domains))
+
+ (system* openssl "x509"
+ "-req" "-sha256" "-CAcreateserial"
+ "-days" (number->string (* 20 365))
+ "-copy_extensions=copyall"
+ "-in" csr-path
+ "-CA" "/etc/snakeoil/root-cert.pem"
+ "-CAkey" "/etc/snakeoil/private/key.pem"
+ "-out" cert-path "-days" (number->string (* 20 365)))
+
+ (symlink "../../../snakeoil/root-cert.pem" chain-link-path)
+
+ ;; Concatenate cert.pem and chain.pem into fullchain.pem.
+ (with-output-to-file fullchain-path
+ (lambda _
+ (for-each (lambda (part)
+ (call-with-input-file part
+ (compose display get-string-all)))
+ (list cert-path chain-link-path))))
+
+ (delete-file csr-path)
+
+ (map chown-file
+ (list tmp-dir cert-path fullchain-path))
+
+ (when key-group-owner
+ (chown key-path -1 (if (integer? key-group-owner)
+ key-group-owner
+ (group:gid (getgr key-group-owner))))
+ (chmod key-path #o640))
+
+ (rename-file tmp-dir cert-dir))))
+
+ (map issue-cert
+ '#$(map snakeoil-cert-configuration-name certificates)
+ '#$(map snakeoil-cert-configuration-domains certificates)
+ '#$(map snakeoil-cert-configuration-key-group-owner
+ certificates)))))
+
+(define snakeoil-service-type
+ (service-type
+ (name 'snakeoil)
+ (extensions
+ (list (service-extension activation-service-type snakeoil-activation)))
+ (compose concatenate)
+ (extend (lambda (config extra-certificates)
+ (snakeoil-configuration
+ (inherit config)
+ (certificates (append
+ (snakeoil-configuration-certificates config)
+ extra-certificates)))))
+ (default-value (snakeoil-configuration))
+ (description "Generate self-issued TLS certificates.")))