- Add [webauthn] section to all config examples - Add active WebAuthn config to run/mcias.conf - Update Dockerfile to use /srv/mcias single mount - Add WebAuthn and TOTP sections to RUNBOOK.md - Fix TOTP QR display (template.URL type) - Add --force-rm to docker build in Makefile Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
542 lines
13 KiB
Markdown
542 lines
13 KiB
Markdown
# MCIAS Runbook
|
|
|
|
Operational procedures for running and maintaining the MCIAS authentication
|
|
server. All required files live under `/srv/mcias`.
|
|
|
|
---
|
|
|
|
## Directory Layout
|
|
|
|
```
|
|
/srv/mcias/
|
|
mcias.toml — server configuration (TOML)
|
|
server.crt — TLS certificate (PEM)
|
|
server.key — TLS private key (PEM, mode 0640)
|
|
mcias.db — SQLite database (WAL mode creates .db-wal and .db-shm)
|
|
env — environment file: MCIAS_MASTER_PASSPHRASE (mode 0640)
|
|
master.key — optional raw AES-256 key file (mode 0640, alternative to env)
|
|
```
|
|
|
|
All files are owned by the `mcias` system user and group (`mcias:mcias`).
|
|
The directory itself is mode `0750`.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
Run as root from the repository root after `make build`:
|
|
|
|
```sh
|
|
sh dist/install.sh
|
|
```
|
|
|
|
This script is idempotent. It:
|
|
1. Creates the `mcias` system user and group if they do not exist.
|
|
2. Installs binaries to `/usr/local/bin/`.
|
|
3. Creates `/srv/mcias/` with correct ownership and permissions.
|
|
4. Installs the systemd service unit to `/etc/systemd/system/mcias.service`.
|
|
5. Installs example config files to `/srv/mcias/` (will not overwrite existing files).
|
|
|
|
After installation, complete the steps below before starting the service.
|
|
|
|
---
|
|
|
|
## First-Run Setup
|
|
|
|
### 1. Generate a TLS certificate
|
|
|
|
**Self-signed (personal/development use):**
|
|
|
|
```sh
|
|
openssl req -x509 -newkey ed25519 -days 3650 \
|
|
-keyout /srv/mcias/server.key \
|
|
-out /srv/mcias/server.crt \
|
|
-subj "/CN=auth.example.com" \
|
|
-nodes
|
|
chmod 0640 /srv/mcias/server.key
|
|
chown mcias:mcias /srv/mcias/server.key /srv/mcias/server.crt
|
|
```
|
|
|
|
**Using the `cert` tool:**
|
|
|
|
```sh
|
|
go install github.com/kisom/cert@latest
|
|
|
|
cat > /tmp/request.yaml <<EOF
|
|
subject:
|
|
common_name: auth.example.com
|
|
hosts:
|
|
- auth.example.com
|
|
key:
|
|
algo: ecdsa
|
|
size: 521
|
|
ca:
|
|
expiry: 87600h
|
|
EOF
|
|
|
|
cert genkey -a ec -s 521 > /srv/mcias/server.key
|
|
cert selfsign -p /srv/mcias/server.key -f /tmp/request.yaml > /srv/mcias/server.crt
|
|
chmod 0640 /srv/mcias/server.key
|
|
chown mcias:mcias /srv/mcias/server.key /srv/mcias/server.crt
|
|
rm /tmp/request.yaml
|
|
```
|
|
|
|
### 2. Write the configuration file
|
|
|
|
```sh
|
|
cp /srv/mcias/mcias.conf.example /srv/mcias/mcias.toml
|
|
$EDITOR /srv/mcias/mcias.toml
|
|
chmod 0640 /srv/mcias/mcias.toml
|
|
chown mcias:mcias /srv/mcias/mcias.toml
|
|
```
|
|
|
|
Minimum required settings:
|
|
|
|
```toml
|
|
[server]
|
|
listen_addr = "0.0.0.0:8443"
|
|
tls_cert = "/srv/mcias/server.crt"
|
|
tls_key = "/srv/mcias/server.key"
|
|
|
|
[database]
|
|
path = "/srv/mcias/mcias.db"
|
|
|
|
[tokens]
|
|
issuer = "https://auth.example.com"
|
|
|
|
[master_key]
|
|
passphrase_env = "MCIAS_MASTER_PASSPHRASE"
|
|
```
|
|
|
|
See `dist/mcias.conf.example` for the full annotated reference.
|
|
|
|
### 3. Set the master key passphrase
|
|
|
|
```sh
|
|
cp /srv/mcias/mcias.env.example /srv/mcias/env
|
|
$EDITOR /srv/mcias/env # set MCIAS_MASTER_PASSPHRASE to a long random value
|
|
chmod 0640 /srv/mcias/env
|
|
chown mcias:mcias /srv/mcias/env
|
|
```
|
|
|
|
Generate a strong passphrase:
|
|
|
|
```sh
|
|
openssl rand -base64 32
|
|
```
|
|
|
|
> **IMPORTANT:** Back up the passphrase to a secure offline location.
|
|
> Losing it permanently destroys access to all encrypted data in the database.
|
|
|
|
### 4. Create the first admin account
|
|
|
|
```sh
|
|
export MCIAS_MASTER_PASSPHRASE=your-passphrase
|
|
|
|
mciasdb --config /srv/mcias/mcias.toml account create \
|
|
--username admin --type human
|
|
# note the UUID printed
|
|
|
|
mciasdb --config /srv/mcias/mcias.toml account set-password --id <UUID>
|
|
mciasdb --config /srv/mcias/mcias.toml role grant --id <UUID> --role admin
|
|
```
|
|
|
|
### 5. Enable and start the service
|
|
|
|
```sh
|
|
systemctl enable mcias
|
|
systemctl start mcias
|
|
systemctl status mcias
|
|
```
|
|
|
|
### 6. Verify
|
|
|
|
```sh
|
|
curl -k https://auth.example.com:8443/v1/health
|
|
# {"status":"ok"}
|
|
```
|
|
|
|
---
|
|
|
|
## Routine Operations
|
|
|
|
### Start / stop / restart
|
|
|
|
```sh
|
|
systemctl start mcias
|
|
systemctl stop mcias
|
|
systemctl restart mcias
|
|
```
|
|
|
|
### View logs
|
|
|
|
```sh
|
|
journalctl -u mcias -f
|
|
journalctl -u mcias --since "1 hour ago"
|
|
```
|
|
|
|
### Check service status
|
|
|
|
```sh
|
|
systemctl status mcias
|
|
```
|
|
|
|
### Reload configuration
|
|
|
|
The server reads its configuration at startup only. To apply config changes:
|
|
|
|
```sh
|
|
systemctl restart mcias
|
|
```
|
|
|
|
---
|
|
|
|
## Account Management
|
|
|
|
All account management can be done via `mciasctl` (REST API) when the server
|
|
is running, or `mciasdb` for offline/break-glass operations.
|
|
|
|
```sh
|
|
# Set env for offline tool
|
|
export MCIAS_MASTER_PASSPHRASE=your-passphrase
|
|
CONF="--config /srv/mcias/mcias.toml"
|
|
|
|
# List accounts
|
|
mciasdb $CONF account list
|
|
|
|
# Create account
|
|
mciasdb $CONF account create --username alice --type human
|
|
|
|
# Set password (prompts interactively)
|
|
mciasdb $CONF account set-password --id <UUID>
|
|
|
|
# Grant or revoke a role
|
|
mciasdb $CONF role grant --id <UUID> --role admin
|
|
mciasdb $CONF role revoke --id <UUID> --role admin
|
|
|
|
# Disable account
|
|
mciasdb $CONF account set-status --id <UUID> --status inactive
|
|
|
|
# Delete account
|
|
mciasdb $CONF account set-status --id <UUID> --status deleted
|
|
```
|
|
|
|
---
|
|
|
|
## Token Management
|
|
|
|
```sh
|
|
CONF="--config /srv/mcias/mcias.toml"
|
|
|
|
# List active tokens for an account
|
|
mciasdb $CONF token list --id <UUID>
|
|
|
|
# Revoke a specific token by JTI
|
|
mciasdb $CONF token revoke --jti <JTI>
|
|
|
|
# Revoke all tokens for an account (e.g., suspected compromise)
|
|
mciasdb $CONF token revoke-all --id <UUID>
|
|
|
|
# Prune expired tokens from the database
|
|
mciasdb $CONF prune tokens
|
|
```
|
|
|
|
---
|
|
|
|
## Database Maintenance
|
|
|
|
### Verify schema
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml schema verify
|
|
```
|
|
|
|
### Run pending migrations
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml schema migrate
|
|
```
|
|
|
|
### Force schema version (break-glass)
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml schema force --version N
|
|
```
|
|
|
|
Use only when `schema migrate` reports a dirty version after a failed migration.
|
|
|
|
### Backup the database
|
|
|
|
SQLite WAL mode creates three files. Back up all three atomically using the
|
|
SQLite backup API or by stopping the server first:
|
|
|
|
```sh
|
|
# Online backup (preferred — no downtime):
|
|
sqlite3 /srv/mcias/mcias.db ".backup /path/to/backup/mcias-$(date +%F).db"
|
|
|
|
# Offline backup:
|
|
systemctl stop mcias
|
|
cp /srv/mcias/mcias.db /path/to/backup/mcias-$(date +%F).db
|
|
systemctl start mcias
|
|
```
|
|
|
|
Store backups alongside a copy of the master key passphrase in a secure
|
|
offline location. A database backup without the passphrase is unrecoverable.
|
|
|
|
---
|
|
|
|
## Audit Log
|
|
|
|
```sh
|
|
CONF="--config /srv/mcias/mcias.toml"
|
|
|
|
# Show last 50 audit events
|
|
mciasdb $CONF audit tail --n 50
|
|
|
|
# Query by account
|
|
mciasdb $CONF audit query --account <UUID>
|
|
|
|
# Query by event type since a given time
|
|
mciasdb $CONF audit query --type login_failure --since 2026-01-01T00:00:00Z
|
|
|
|
# Output as JSON (for log shipping)
|
|
mciasdb $CONF audit query --json
|
|
```
|
|
|
|
---
|
|
|
|
## Upgrading
|
|
|
|
1. Build the new binaries: `make build`
|
|
2. Stop the service: `systemctl stop mcias`
|
|
3. Install new binaries: `sh dist/install.sh`
|
|
- The script will not overwrite existing config files.
|
|
- New example files are placed with a `.new` suffix for review.
|
|
4. Review any `.new` config files in `/srv/mcias/` and merge changes manually.
|
|
5. Run schema migrations if required:
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml schema migrate
|
|
```
|
|
6. Start the service: `systemctl start mcias`
|
|
7. Verify: `curl -k https://auth.example.com:8443/v1/health`
|
|
|
|
---
|
|
|
|
## WebAuthn / Passkey Configuration
|
|
|
|
WebAuthn enables passwordless passkey login and hardware security key 2FA.
|
|
It is **disabled by default** — to enable it, add a `[webauthn]` section to
|
|
`mcias.toml` with the relying party ID and origin.
|
|
|
|
### Enable WebAuthn
|
|
|
|
Add to `/srv/mcias/mcias.toml`:
|
|
|
|
```toml
|
|
[webauthn]
|
|
rp_id = "auth.example.com"
|
|
rp_origin = "https://auth.example.com"
|
|
display_name = "MCIAS"
|
|
```
|
|
|
|
- **`rp_id`** — The domain name (no scheme or port). Must match the domain
|
|
users see in their browser address bar.
|
|
- **`rp_origin`** — The full HTTPS origin. Include the port if non-standard
|
|
(e.g., `https://localhost:8443` for development).
|
|
- **`display_name`** — Shown to users during browser passkey prompts. Defaults
|
|
to "MCIAS" if omitted.
|
|
|
|
Restart the server after changing the config:
|
|
|
|
```sh
|
|
systemctl restart mcias
|
|
```
|
|
|
|
Once enabled, the **Passkeys** section appears on the user's Profile page
|
|
(self-service enrollment) and on the admin Account Detail page (credential
|
|
management).
|
|
|
|
### Passkey enrollment
|
|
|
|
Passkey enrollment is self-service only. Users add passkeys from their
|
|
**Profile → Passkeys** section. Admins can view and remove passkeys from
|
|
the Account Detail page but cannot enroll on behalf of users (passkey
|
|
registration requires the authenticator device to be present).
|
|
|
|
### Disable WebAuthn
|
|
|
|
Remove or comment out the `[webauthn]` section and restart. Existing
|
|
credentials remain in the database but are unused. Passkey UI sections
|
|
will be hidden.
|
|
|
|
### Remove all passkeys for an account (break-glass)
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml account reset-webauthn --id <UUID>
|
|
```
|
|
|
|
---
|
|
|
|
## TOTP Two-Factor Authentication
|
|
|
|
TOTP enrollment is self-service via the **Profile → Two-Factor Authentication**
|
|
section. Users enter their current password to begin enrollment, scan the QR
|
|
code with an authenticator app, and confirm with a 6-digit code.
|
|
|
|
### Admin: Remove TOTP for an account
|
|
|
|
From the web UI: navigate to the account's detail page and click **Remove**
|
|
next to the TOTP status.
|
|
|
|
From the CLI:
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml account reset-totp --id <UUID>
|
|
```
|
|
|
|
This clears the TOTP secret and disables the 2FA requirement. The user can
|
|
re-enroll from their Profile page.
|
|
|
|
---
|
|
|
|
## Master Key Rotation
|
|
|
|
> This operation is not yet automated. Until a rotation command is
|
|
> implemented, rotation requires a full re-encryption of the database.
|
|
> Contact the project maintainer for the current procedure.
|
|
|
|
---
|
|
|
|
## TLS Certificate Renewal
|
|
|
|
Replace the certificate and key files, then restart the server:
|
|
|
|
```sh
|
|
# Generate or obtain new cert/key, then:
|
|
cp new-server.crt /srv/mcias/server.crt
|
|
cp new-server.key /srv/mcias/server.key
|
|
chmod 0640 /srv/mcias/server.key
|
|
chown mcias:mcias /srv/mcias/server.crt /srv/mcias/server.key
|
|
systemctl restart mcias
|
|
```
|
|
|
|
For Let's Encrypt with Certbot, add a deploy hook:
|
|
|
|
```sh
|
|
# /etc/letsencrypt/renewal-hooks/deploy/mcias.sh
|
|
#!/bin/sh
|
|
cp /etc/letsencrypt/live/auth.example.com/fullchain.pem /srv/mcias/server.crt
|
|
cp /etc/letsencrypt/live/auth.example.com/privkey.pem /srv/mcias/server.key
|
|
chmod 0640 /srv/mcias/server.key
|
|
chown mcias:mcias /srv/mcias/server.crt /srv/mcias/server.key
|
|
systemctl restart mcias
|
|
```
|
|
|
|
---
|
|
|
|
## Docker Deployment
|
|
|
|
```sh
|
|
make docker
|
|
|
|
mkdir -p /srv/mcias
|
|
cp dist/mcias.conf.docker.example /srv/mcias/mcias.toml
|
|
$EDITOR /srv/mcias/mcias.toml
|
|
|
|
# Place TLS cert and key under /srv/mcias/
|
|
# Set ownership so uid 10001 (container mcias user) can read them.
|
|
chown -R 10001:10001 /srv/mcias
|
|
|
|
docker run -d \
|
|
--name mcias \
|
|
-v /srv/mcias:/srv/mcias \
|
|
-e MCIAS_MASTER_PASSPHRASE=your-passphrase \
|
|
-p 8443:8443 \
|
|
-p 9443:9443 \
|
|
--restart unless-stopped \
|
|
mcias:latest
|
|
```
|
|
|
|
See `dist/mcias.conf.docker.example` for the full annotated Docker config.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Server fails to start: "open database"
|
|
|
|
Check that `/srv/mcias/` is writable by the `mcias` user:
|
|
|
|
```sh
|
|
ls -la /srv/mcias/
|
|
stat /srv/mcias/mcias.db # if it already exists
|
|
```
|
|
|
|
Fix: `chown mcias:mcias /srv/mcias`
|
|
|
|
### Server fails to start: "environment variable ... is not set"
|
|
|
|
The `MCIAS_MASTER_PASSPHRASE` env var is missing. Ensure `/srv/mcias/env`
|
|
exists, is readable by the mcias user, and contains the correct variable:
|
|
|
|
```sh
|
|
grep MCIAS_MASTER_PASSPHRASE /srv/mcias/env
|
|
```
|
|
|
|
Also confirm the systemd unit loads it:
|
|
|
|
```sh
|
|
systemctl cat mcias | grep EnvironmentFile
|
|
```
|
|
|
|
### Server fails to start: "decrypt signing key"
|
|
|
|
The master key passphrase has changed or is wrong. The passphrase must match
|
|
the one used when the database was first initialized (the KDF salt is stored
|
|
in the database). Restore the correct passphrase from your offline backup.
|
|
|
|
### TLS errors in client connections
|
|
|
|
Verify the certificate is valid and covers the correct hostname:
|
|
|
|
```sh
|
|
openssl x509 -in /srv/mcias/server.crt -noout -text | grep -E "Subject|DNS"
|
|
openssl x509 -in /srv/mcias/server.crt -noout -dates
|
|
```
|
|
|
|
### Database locked / WAL not cleaning up
|
|
|
|
Check for lingering `mcias.db-wal` and `mcias.db-shm` files after an unclean
|
|
shutdown. These are safe to leave in place — SQLite will recover on next open.
|
|
Do not delete them while the server is running.
|
|
|
|
### Schema dirty after failed migration
|
|
|
|
```sh
|
|
mciasdb --config /srv/mcias/mcias.toml schema verify
|
|
mciasdb --config /srv/mcias/mcias.toml schema force --version N
|
|
mciasdb --config /srv/mcias/mcias.toml schema migrate
|
|
```
|
|
|
|
Replace `N` with the last successfully applied version number.
|
|
|
|
---
|
|
|
|
## File Permissions Reference
|
|
|
|
| Path | Mode | Owner |
|
|
|------|------|-------|
|
|
| `/srv/mcias/` | `0750` | `mcias:mcias` |
|
|
| `/srv/mcias/mcias.toml` | `0640` | `mcias:mcias` |
|
|
| `/srv/mcias/server.crt` | `0644` | `mcias:mcias` |
|
|
| `/srv/mcias/server.key` | `0640` | `mcias:mcias` |
|
|
| `/srv/mcias/mcias.db` | `0640` | `mcias:mcias` |
|
|
| `/srv/mcias/env` | `0640` | `mcias:mcias` |
|
|
| `/srv/mcias/master.key` | `0640` | `mcias:mcias` |
|
|
|
|
Verify permissions:
|
|
|
|
```sh
|
|
ls -la /srv/mcias/
|
|
```
|