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 \
--group geml \
--mode 644 \
geml.ini
geml.example.ini
uninstall:
id -u geml &>/dev/null && userdel geml

View file

@ -4,53 +4,40 @@ Gemini server written in Common Lisp
## /etc/geml/geml.ini
geml requires `cert` and `key` to be configured before it will run. And will
have nothing to serve until you configure at least one domain and root. See
[`geml.ini`](./geml.ini) as an example.
geml will have nothing to serve until you configure at least one domain and
root.
```
cert = /var/lib/geml/localhost.crt
key = /var/lib/geml/localhost.key
[my.gmi.capsule]
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
### Standalone Executable
To get an executable `bin/geml-server` run `make`.
```sh
make && bin/geml-server -h
```
### Systemd
Install the build to `/usr/local/bin/geml-server` with
`sudo make install` which will also wire up systemd so you can `sudo systemctl
enable --now geml`
To install the build to `/usr/local/bin/geml-server` use `sudo make install`
which will also wire up systemd so you can `sudo systemctl enable --now geml`
You'll need to `sudo systemctl restart geml` whenever you update
`/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
To undo all that run these:
@ -59,12 +46,5 @@ To undo all that run these:
sudo make uninstall
make clean
```
### SBCL
Start sbcl with proper readline support: `rlwrap sbcl`
```lisp
(ql:quickload "geml")
(gemini.server:start-server "/etc/geml/geml.ini")
```
**NOTE**: The automatically created TLS certs will still be at `/var/lib/geml`
you can (re)use those elsewhere or also just delete them.

View file

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

View file

@ -24,19 +24,22 @@
(defun bin ()
(multiple-value-bind (options free-args)
(handler-case
(handler-bind ((opts:unknown-option #'unknown-option))
(opts:get-opts))
(opts:missing-arg (condition)
(format t "option ~s needs an argument!~%"
(opts:option condition)))
(opts:arg-parser-failed (condition)
(format t "cannot parse ~s as argument of ~s~%"
(opts:raw-arg condition)
(opts:option condition)))
(opts:missing-required-option (con)
(format t "~a~%" con)
(opts:exit 1)))
(cond
((getf options :help) (opts:describe :usage-of "geml-server"))
((getf options :version) (format t "~a~%" (asdf:component-version (asdf:find-system 'geml))))
(t (start-server (getf options :config))))))
(handler-bind ((unix-opts:unknown-option #'unknown-option))
(unix-opts:get-opts))
(unix-opts:missing-arg (condition)
(format t "option ~s needs an argument!~%"
(unix-opts:option condition)))
(unix-opts:arg-parser-failed (condition)
(format t "cannot parse ~s as argument of ~s~%"
(unix-opts:raw-arg condition)
(unix-opts:option condition)))
(unix-opts:missing-required-option (con)
(format t "~a~%" con)
(unix-opts:exit 1)))
(cond
((getf options :help) (unix-opts:describe :usage-of "geml-server"))
((getf options :version)
(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
:section (intern (string-upcase domain)
: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
root
(resolve-index path)))))
@ -38,29 +36,62 @@
(format conn "51 ~a does not exist for ~a~%" path domain)))
(force-output conn))
(defun start-server (settings-file)
(let* ((settings (cl-ini:parse-ini settings-file))
(host (or (cl-ini:ini-value settings :host)
(defun ensure-certs (settings certs-dir)
(mapcar #'(lambda (setting)
(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"))
(port (or (cl-ini:ini-value settings :port)
1965))
(key (probe-file (cl-ini:ini-value settings :key)))
(cert (probe-file (cl-ini:ini-value settings :cert))))
(certs-dir (probe-file (or (cl-ini:ini-value settings :certs-dir)
"/var/lib/geml"))))
(ensure-certs settings certs-dir)
(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)))
(format t "geml booted on ~a:~a (~a)~%"
(format t "geml booted on ~a:~a~%"
host
port
settings-file)
port)
(unwind-protect
(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
(usocket:socket-stream socket)
:external-format '(:utf-8 :eol-style :crlf)
:certificate (namestring cert)
:key (namestring key))))
:certificate cert
:key key)))
(unwind-protect (handle-request conn settings)
(close conn))))
(format t "geml is shutting down...~%")