doc: Document authentication.

* etc/new-client-cert.scm: Add script.
* doc/cuirass.texi (Authentication): Document it.
* Makefile.am (noinst_SCRIPTS): Register it.

Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Maxim Cournoyer 2023-05-11 00:34:52 -04:00 committed by Ludovic Courtès
parent b76c04199f
commit 7c9fc0645e
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
3 changed files with 208 additions and 1 deletions

View File

@ -25,7 +25,7 @@
bin_SCRIPTS = \
bin/cuirass
noinst_SCRIPTS = pre-inst-env
noinst_SCRIPTS = pre-inst-env etc/new-client-cert.scm
guilesitedir = $(datarootdir)/guile/site/@GUILE_EFFECTIVE_VERSION@
guileobjectdir = $(libdir)/guile/@GUILE_EFFECTIVE_VERSION@/site-ccache

View File

@ -13,6 +13,7 @@ Copyright @copyright{} 2016, 2017 Mathieu Lirzin@*
Copyright @copyright{} 2017, 2020, 2021 Mathieu Othacehe@*
Copyright @copyright{} 2018, 2021, 2023 Ludovic Courtès@*
Copyright @copyright{} 2018 Clément Lassieur
Copyright @copyright{} 2023 Maxim Cournoyer@*
@quotation
Permission is granted to copy, distribute and/or modify this document
@ -57,6 +58,7 @@ Documentation License''.
* Parameters:: Cuirass parameters.
* Build modes:: Build modes.
* Invocation:: How to run Cuirass.
* Authentication:: Configuring TLS authentication.
* Web API:: Description of the Web API.
* Database:: About the database schema.
@ -707,6 +709,90 @@ Display the actual version of @code{cuirass}.
Display an help message that summarize all the options provided.
@end table
@c *********************************************************************
@node Authentication
@chapter Authentication
@cindex authentication
Cuirass does not provide its own authentication mechanism; by default,
any user can do anything via its web interface. To restrict this to
only authorized users, one approach is to proxy the Cuirass web site via
a web server such as Nginx and configure the web server to require
client certificate verification for pages under the @samp{/admin}
prefix. The following minimal Nginx configuration can be used to
accomplish this on a Guix System:
@lisp
(service nginx-service-type
(nginx-configuration
(server-blocks
(list
;; TLS is required for authentication; serve the site via
;; HTTPS only.
(nginx-server-configuration
(listen '("80"))
(raw-content
(list "return 308 https://$host$request_uri;")))
(nginx-server-configuration
(listen '("443 ssl"))
(server-name '("ci.your-host.org"))
(ssl-certificate "/etc/certs/ci.your-host.org.crt")
(ssl-certificate-key "/etc/certs/ci.your-host.org.key")
(locations
(list
;; Proxy the whole Cuirass web site...
(nginx-location-configuration
(uri "/")
(body (list "proxy_pass http://localhost:8081;")))
;; ... but require authentication for the admin pages.
(nginx-location-configuration
(uri "~ ^/admin")
(body
(list "if ($ssl_client_verify != SUCCESS) \
@{ return 403; @} proxy_pass http://localhost:8081;")))))
(raw-content
;; Register your self-generated certificate authority.
(list "ssl_client_certificate /etc/ssl-ca/certs/ca.crt;"
"ssl_verify_client optional;")))))))
@end lisp
Your host TLS certificate could have been obtained via Let's Encrypt or
directly via the @command{openssl} command, among other means. To
create a private certificate authority (CA) that can sign user
certificates, a convenience script is provided. It's main requirement
is to have the @command{guix} command available. It can be invoked
like:
@example
sudo -E ./etc/new-client-cert.scm --generate-ca
@end example
It should generate the @file{/etc/ssl-ca/private/ca.key} private key as
well as the @file{/etc/ssl-ca/certs/ca.crt} certificate authority as
used in the Nginx configuration above.
To issue a new user certificate, run the same script from your home
directory with:
@example
sudo -E ./etc/new-client-cert.scm
@end example
You will be asked to input the password for the CA private key, if any,
and again for your new certificate; save it carefully. The script
requires to run as root to have access to the private certificate
authority key; it outputs the new user certificate files to the current
working directory.
After your new CA-signed user certificate is generated, it needs to be
registered with your web browser. To do so using GNU IceCat, for
example, you can navigate to @samp{Parameters -> Security -> Show
certificates} and then click the @samp{Import...} button and select your
@file{.pk12} personal certificate file. The web interface of Cuirass
should now only allow authenticated users to perform administrative
tasks.
@c *********************************************************************
@node Web API
@chapter Web API

121
etc/new-client-cert.scm Executable file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env -S guix shell guile openssl -- guile \\
--no-auto-compile -e main -s
!#
;;;; cuirass.scm -- Cuirass public interface.
;;; Copyright © 2023 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2023 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;;
;;; This file is part of Cuirass.
;;;
;;; Cuirass is free software: you can redistribute it and/or modify
;;; it under the terms of the GNU General Public License as published by
;;; the Free Software Foundation, either version 3 of the License, or
;;; (at your option) any later version.
;;;
;;; Cuirass is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with Cuirass. If not, see <http://www.gnu.org/licenses/>.
(use-modules (ice-9 format)
(ice-9 match)
(guix build utils))
(define %user (or (getenv "SUDO_USER")
(getenv "USER")))
(define %user-id (passwd:uid (getpwnam %user)))
(define %group-id (passwd:gid (getpwnam %user)))
(define %CA-directory
"/etc/ssl-ca")
(define subject-template
"/C=DE/ST=Berlin/L=Berlin/O=GNU Guix/OU=Cuirass/CN=~a")
(define CA-key
(string-append %CA-directory "/private/ca.key"))
(define CA-cert
(string-append %CA-directory "/certs/ca.crt"))
(define* (output who file)
(string-append (getcwd) "/" who file))
(define (key-file who)
"Return the absolute file name of the key file for WHO."
(output who ".key"))
(define (csr-file who)
"Return the absolute file name of the CSR file for WHO."
(output who ".csr"))
(define (client-cert-file who)
"Return the absolute file name of the client certificate file for
WHO."
(output who ".crt"))
(define (exported-cert-file who)
"Return the absolute file name of the pkcs12 client certificate file
for WHO. This is the file that users should import into their
browsers."
(output who ".p12"))
(define (generate-ca!)
"Generate a private certificate authority (CA) valid for 10 years."
(mkdir-p (dirname CA-key))
(mkdir-p (dirname CA-cert))
(invoke "openssl" "req" "-newkey" "rsa" "-x509" "-days" "3650"
"-noenc" ;no password
"-subj" (format #false "~@?" subject-template "Cuirass CA")
"-keyout" CA-key "-out" CA-cert))
(define (generate-csr! who)
"Generate a new certificate signing request and key for WHO."
(let ((key (key-file who))
(csr (csr-file who)))
(invoke "openssl" "req" "-newkey" "rsa"
"-noenc" ;no password
"-subj" (format #false "~@?" subject-template who)
"-keyout" key
"-out" csr)
(chown key %user-id %group-id)
(chown csr %user-id %group-id)))
(define* (generate-client-certificate! who #:key (expiry 365))
"Generate a client certificate for WHO."
(let ((cert (client-cert-file who)))
(invoke "openssl" "x509" "-req"
"-in" (csr-file who)
"-CA" CA-cert
"-CAkey" CA-key
"-out" cert
"-days" (number->string expiry))
(chown cert %user-id %group-id)))
(define (export-p12! who)
(let ((key (key-file who))
(exported-cert (exported-cert-file who)))
(invoke "openssl" "pkcs12" "-export"
"-in" (client-cert-file who)
"-inkey" key
"-out" exported-cert)
(chown key %user-id %group-id)
(chown exported-cert %user-id %group-id)))
(define (main args)
(match (command-line)
((script)
(set-program-arguments (list script %user))
(apply main args))
((script "--generate-ca")
(generate-ca!))
((script who)
(generate-csr! who)
(generate-client-certificate! who)
(export-p12! who))
((script . rest)
(format (current-error-port) "usage: ~a [--generate-ca|name]~%" script))))