automatic TLS certs

This commit is contained in:
secretspecter 2023-08-02 02:00:59 -06:00
parent f6f64b1ce8
commit 07bb76867b
5 changed files with 83 additions and 71 deletions

View file

@ -27,7 +27,7 @@ install: bin/geml-server
--owner geml \ --owner geml \
--group geml \ --group geml \
--mode 644 \ --mode 644 \
geml.ini geml.example.ini
uninstall: uninstall:
id -u geml &>/dev/null && userdel geml id -u geml &>/dev/null && userdel geml

View file

@ -4,53 +4,40 @@ Gemini server written in Common Lisp
## /etc/geml/geml.ini ## /etc/geml/geml.ini
geml requires `cert` and `key` to be configured before it will run. And will geml will have nothing to serve until you configure at least one domain and
have nothing to serve until you configure at least one domain and root. See root.
[`geml.ini`](./geml.ini) as an example.
``` ```
cert = /var/lib/geml/localhost.crt
key = /var/lib/geml/localhost.key
[my.gmi.capsule] [my.gmi.capsule]
root = /srv/gmi root = /srv/gmi
``` ```
### Generate Self-Signed SSL Certificate
- [ ] include/write helper script for this
```sh
openssl req -x509 \
-out localhost.crt \
-keyout localhost.key \
-newkey rsa:2048 \
-nodes \
-sha256 \
-subj '/CN=localhost' \
-extensions EXT \
-config <(printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
```
## Usage ## Usage
### Standalone Executable
To get an executable `bin/geml-server` run `make`. To get an executable `bin/geml-server` run `make`.
```sh ```sh
make && bin/geml-server -h make && bin/geml-server -h
``` ```
### Systemd ### Systemd
Install the build to `/usr/local/bin/geml-server` with To install the build to `/usr/local/bin/geml-server` use `sudo make install`
`sudo make install` which will also wire up systemd so you can `sudo systemctl which will also wire up systemd so you can `sudo systemctl enable --now geml`
enable --now geml`
You'll need to `sudo systemctl restart geml` whenever you update You'll need to `sudo systemctl restart geml` whenever you update
`/etc/geml/geml.ini`... you did do that didn't you? `/etc/geml/geml.ini`... you did do that didn't you?
```sh
sudo cp /etc/geml/geml.example.ini /etc/geml/geml.ini
```
`make install` also sets up a geml system user and you may want to add yourself
to that group.
```sh
usermod -a -G geml $USER
```
### yeet it ### yeet it
To undo all that run these: To undo all that run these:
@ -59,12 +46,5 @@ To undo all that run these:
sudo make uninstall sudo make uninstall
make clean make clean
``` ```
**NOTE**: The automatically created TLS certs will still be at `/var/lib/geml`
### SBCL you can (re)use those elsewhere or also just delete them.
Start sbcl with proper readline support: `rlwrap sbcl`
```lisp
(ql:quickload "geml")
(gemini.server:start-server "/etc/geml/geml.ini")
```

View file

@ -2,9 +2,7 @@
;; see usocket: https://github.com/usocket/usocket#api-definition ;; see usocket: https://github.com/usocket/usocket#api-definition
;; host = 0.0.0.0 ;; host = 0.0.0.0
;; port = 1965 ;; port = 1965
;; certs-dir = /var/lib/geml/
cert = /var/lib/geml/localhost.crt
key = /var/lib/geml/localhost.key
[localhost] [localhost]
root = /srv/gmi root = /srv/gmi

View file

@ -24,19 +24,22 @@
(defun bin () (defun bin ()
(multiple-value-bind (options free-args) (multiple-value-bind (options free-args)
(handler-case (handler-case
(handler-bind ((opts:unknown-option #'unknown-option)) (handler-bind ((unix-opts:unknown-option #'unknown-option))
(opts:get-opts)) (unix-opts:get-opts))
(opts:missing-arg (condition) (unix-opts:missing-arg (condition)
(format t "option ~s needs an argument!~%" (format t "option ~s needs an argument!~%"
(opts:option condition))) (unix-opts:option condition)))
(opts:arg-parser-failed (condition) (unix-opts:arg-parser-failed (condition)
(format t "cannot parse ~s as argument of ~s~%" (format t "cannot parse ~s as argument of ~s~%"
(opts:raw-arg condition) (unix-opts:raw-arg condition)
(opts:option condition))) (unix-opts:option condition)))
(opts:missing-required-option (con) (unix-opts:missing-required-option (con)
(format t "~a~%" con) (format t "~a~%" con)
(opts:exit 1))) (unix-opts:exit 1)))
(cond (cond
((getf options :help) (opts:describe :usage-of "geml-server")) ((getf options :help) (unix-opts:describe :usage-of "geml-server"))
((getf options :version) (format t "~a~%" (asdf:component-version (asdf:find-system 'geml)))) ((getf options :version)
(t (start-server (getf options :config)))))) (format t "~a~%" (asdf:component-version (asdf:find-system 'geml))))
(t (let ((settings (cl-ini:parse-ini (getf options :config))))
(start-server settings))))))

View file

@ -26,8 +26,6 @@
:root :root
:section (intern (string-upcase domain) :section (intern (string-upcase domain)
:keyword))) :keyword)))
;; TODO use path library?
;; http://www.ai.mit.edu/projects/iiip/doc/CommonLISP/HyperSpec/Body/sec_the_filen_s_dictionary.html
(filename (probe-file (concatenate 'string (filename (probe-file (concatenate 'string
root root
(resolve-index path))))) (resolve-index path)))))
@ -38,29 +36,62 @@
(format conn "51 ~a does not exist for ~a~%" path domain))) (format conn "51 ~a does not exist for ~a~%" path domain)))
(force-output conn)) (force-output conn))
(defun start-server (settings-file) (defun ensure-certs (settings certs-dir)
(let* ((settings (cl-ini:parse-ini settings-file)) (mapcar #'(lambda (setting)
(host (or (cl-ini:ini-value settings :host) (unless (eq (car setting) :global)
(let* ((domain (string-downcase (symbol-name (car setting))))
(key (concatenate 'string
(namestring certs-dir)
domain ".key"))
(cert (concatenate 'string
(namestring certs-dir)
domain ".crt"))
(missing-certs (or (not (probe-file key))
(not (probe-file cert))))
(openssl-cmd (format nil
"openssl req -x509 ~
-newkey rsa:4096 ~
-sha256 ~
-days 3650 -nodes ~
-subj \"/CN=~a\" ~
-keyout ~a ~
-out ~a"
;; TODO -addext \"subjectAltName=DNS:~a\""
domain
key
cert)))
(cond
(missing-certs
(progn (format t "geml is creating TLS certs for ~a (~a)~%"
domain openssl-cmd)
(uiop:run-program openssl-cmd)))
(t
(format t "geml found TLS certs for ~a~%" domain)))
setting)))
settings))
(defun start-server (settings)
(let* ((host (or (cl-ini:ini-value settings :host)
"0.0.0.0")) "0.0.0.0"))
(port (or (cl-ini:ini-value settings :port) (port (or (cl-ini:ini-value settings :port)
1965)) 1965))
(key (probe-file (cl-ini:ini-value settings :key))) (certs-dir (probe-file (or (cl-ini:ini-value settings :certs-dir)
(cert (probe-file (cl-ini:ini-value settings :cert)))) "/var/lib/geml"))))
(ensure-certs settings certs-dir)
(cond (cond
((not cert) (format t "geml cannot read cert using path defined in ~a~%" settings-file))
((not key) (format t "geml cannot read key using path defined in ~a~%" settings-file))
(t (let ((server (usocket:socket-listen host port))) (t (let ((server (usocket:socket-listen host port)))
(format t "geml booted on ~a:~a (~a)~%" (format t "geml booted on ~a:~a~%"
host host
port port)
settings-file)
(unwind-protect (unwind-protect
(loop (let* ((socket (usocket:socket-accept server)) (loop (let* ((socket (usocket:socket-accept server))
(cert (concatenate 'string certs-dir domain ".crt"))
(key (concatenate 'string certs-dir domain ".key"))
(conn (cl+ssl:make-ssl-server-stream (conn (cl+ssl:make-ssl-server-stream
(usocket:socket-stream socket) (usocket:socket-stream socket)
:external-format '(:utf-8 :eol-style :crlf) :external-format '(:utf-8 :eol-style :crlf)
:certificate (namestring cert) :certificate cert
:key (namestring key)))) :key key)))
(unwind-protect (handle-request conn settings) (unwind-protect (handle-request conn settings)
(close conn)))) (close conn))))
(format t "geml is shutting down...~%") (format t "geml is shutting down...~%")