/speakeasy

Use passkeys in front of your homelab

Primary LanguageClojureMIT LicenseMIT

speakeasy

The primary goal of speakeasy is to reduce the exposed attack surface for self hosted software. Replacing the full surface of the backing server with the smaller surface of a reverse proxy and authentication server.

Considerations

  1. It should be fairly easy to put this auth server in front of any other self-hosted server.
  2. The services protected by speakeasy aren’t being intentionally, directly targeted. They may be scanned and attacked by bots or other automated tools.
  3. The expected volume of requests is pretty low. A few to a few dozen users are expected.
  4. There is less reason to identify each user since the server speakeasy is in front of will still want to identify the user.
  5. Short lived JWTs (for some value of “short lived”) reduce the need for revocations.
  6. There is no admin. Anyone who can access the registration endpoints can register another key.
  7. A long lived request can stay open after the token is invalid because auth is checked once per request.
  8. Authenticator attestation is invasive. Any capable authenticator is probably fine.

docker compose demo

  1. Start the servers with docker compose up -d
  2. Visit http://localhost:3000/speakeasy to register a key
    1. Trying to register from http://localhost/speakeasy should result in error/failure
  3. Visit http://localhost/ and see the backend (example.com)
  4. Delete the cookie speakeasy-token
  5. Visit http://localhost/ and see the speakeasy page
  6. Authenticate
  7. Visit http://localhost/ and see the backend

Setup

Firewall setup

Allow traffic in on ports 80 & 443 from any address. Allow traffic in to your ssh port from only an internal subnet if possible. Optionally block all outbound traffic that isn’t going to the expected backend ip(s).

Nginx setup

Include the speakeasy.conf at the top of each server block. Then add auth_request /speakeasy/check; and auth_request_set $auth_status $upstream_status in each location that should require authentication.

If you’re using SELinux you may need setsebool -P httpd_can_network_connect 1. Check sudo cat /var/log/audit/audit.log | grep nginx | grep denied for errors.

Let’s Encrypt setup

Redis setup

Install redis on the same host as speakeasy, use protected-mode and only bind to localhost.

speakeasy setup (systemd)

  1. Install java and clojure
    1. https://openjdk.org/
    2. https://clojure.org/guides/install_clojure
  2. build.sh to prepare an uberjar
  3. Copy systemd/speakeasy.service to /etc/systemd/system/
  4. sudo systemctl daemon-reload
  5. sudo systemctl enable speakeasy
  6. sudo systemctl start speakeasy

Usage

Register the first key

  1. Remove the auth_* lines from the location block that has /speakeasy/register in nginx.conf and reload nginx
  2. Register a key by visiting /speakeasy
  3. Revert the nginx.conf change and reload nginx

Subsequent keys can be registered by any authenticated user visiting /speakeasy and using the registration button.

Authentication

The auth page should automatically show up for anyone who isn’t authenticated. After authenticating reloading the page should take them to the application.