Skip to content
xHain-hackspacePublic

About

Service for managing xHain key access with xDoor

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Valkyrie

Valkyrie manages door access for the xHain hackspace. It syncs members from the xHain account system (Authentik). Admins use it to grant members access to individual doors. For every door it publishes a signed authorized_keys file, and the xDoor devices pull that file to decide whose SSH key can open them.

It's a Phoenix LiveView application built on Ash with a SQLite database.

For how the pieces fit together, see ARCHITECTURE.md.

Features

  • Member list: synced from Authentik on a schedule (every 15 minutes by default) or on demand. Members can be searched and filtered. You can also add manual entries for people without an xHain account.
  • Per-door access: grant or revoke a member's access to each door ("key target") separately.
  • authorized_keys endpoints: plain-text key lists with RSA signatures that the door controllers fetch.
  • Audit log: every change to members, door access and doors is recorded, including who made it.
  • Notifications: members get an email when the SSH key on their account changes.
  • Metrics: a Prometheus endpoint at /metrics.

Getting started

Prerequisites

  • Elixir 1.18 / Erlang/OTP 27+
  • sops, yq and direnv are optional. You only need them to load the encrypted .env.yml automatically.

Environment

config/runtime.exs reads the following variables in every environment (dev included), so they must be set before the app will boot:

Variable Purpose
XHAIN_ACCOUNT_BASE_URL OIDC issuer of the xHain account system
XHAIN_ACCOUNT_CLIENT_ID OIDC client ID
XHAIN_ACCOUNT_CLIENT_SECRET OIDC client secret
XHAIN_ACCOUNT_REDIRECT_URI OIDC callback URL (under /auth) registered with the account system
AUTHENTIK_URL Base URL of the Authentik API (used by the member sync)
AUTHENTIK_TOKEN Authentik API token
AUTHENTIK_MEMBER_GROUP_UUID Group whose members count as active
AUTHENTIK_ADMIN_GROUP (optional) OIDC group name that grants admin rights
XDOOR_SIGNING_KEY PEM RSA private key used to sign authorized_keys

If you have access to the sops key, direnv allow decrypts .env.yml and exports all of these (see .envrc).

Production needs these as well:

Variable Purpose
DATABASE_PATH Path to the SQLite database file
SECRET_KEY_BASE Phoenix secret (mix phx.gen.secret)
TOKEN_SIGNING_SECRET Secret for signing auth tokens
PHX_HOST Public hostname
PORT HTTP port (default 4000)
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD Outgoing mail
SYNC_INTERVAL_MINUTES (optional) Authentik sync interval (default 15)
POOL_SIZE (optional) DB pool size (default 10)

Running locally

mix setup            # deps, database, seeds, assets
iex -S mix phx.server

Then open http://localhost:4000.

In development:

  • The scheduled Authentik sync is disabled. Use the sync button in the UI instead.
  • Emails are kept in memory and shown in the mailbox preview rather than sent. Set dev_mailer in config/dev.exs to :logger to print them to the console, or to :smtp to send them for real (needs the SMTP_* variables).
  • AshAdmin is at /admin, the LiveDashboard at /dev/dashboard, and the mailbox preview at /dev/mailbox.

Working with production data

The Makefile has targets that copy the production database to your machine and anonymize it:

make download_prod_data        # fetch prod DB (briefly stops prod) and obfuscate emails
make setup_dev_data_from_prod  # replace the dev DB with the obfuscated copy and migrate
make smoke_test_keys           # compare local /authorized_keys output with production

These targets need SSH access to the production host.

Local observability stack

dev/observability contains a Docker Compose setup with Alloy, Prometheus and Grafana. It scrapes the /metrics endpoint of a locally running server:

docker compose -f dev/observability/docker-compose.yml up

Grafana then runs at http://localhost:3000.

Testing

mix test
mix precommit   # compile with warnings as errors, unlock unused deps, format, test

Run mix precommit before pushing. CI runs the tests and a Docker build for every pull request.

Deployment

Each push to main and each v*.*.* tag publishes a Docker image to ghcr.io/xhain-hackspace/valkyrie. Tagged releases are also published as stable. The container runs the Mix release (/app/bin/server) and applies pending migrations on startup.

See docker-compose.yml for a minimal example of running the image. make compose_up builds and starts it with the version set from git. It takes the secrets from your shell, which direnv loads from .env.yml. Without direnv, run sops exec-env .env.yml 'make compose_up'. A .env file is optional and only needed for overrides.

Versioning

The version comes from git tags. mix.exs runs git describe --tags --match 'v[0-9]*' and turns the result into SemVer: v0.6.0 becomes 0.6.0, and commits after a tag go into the build metadata (0.6.0+4.g3156ab9, plus .dirty for uncommitted changes). The version is shown in the footer of every page and links to the GitHub release or commit.

The Docker build has no git history, so CI passes the git describe output as the APP_VERSION build arg. APP_VERSION also overrides the version for local builds. To cut a release, push a v*.*.* tag.

In a container, bin/valkyrie version prints the normalized version. The raw git describe output is available as $APP_VERSION and as the org.opencontainers.image.version image label.

Adding a door in production

Doors are managed on the admin-only /doors page, which requires the AUTHENTIK_ADMIN_GROUP group. When you create a door, you can grant it to every existing keyholder at the same time. A door's slug can't be changed later because it's part of the URL (/authorized_keys/<slug>).

About

Service for managing xHain key access with xDoor

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages