Align with engineering standards (steps 1-5)

- Rename dist/ -> deploy/ with subdirs examples/, scripts/,
  systemd/ per standard repository layout
- Update .gitignore: gitignore all of dist/ (build output only)
- Makefile: all target is now vet->lint->test->build; add vet,
  proto-lint, devserver targets; CGO_ENABLED=0 for builds
  (modernc.org/sqlite is pure-Go, no C toolchain needed);
  CGO_ENABLED=1 retained for tests (race detector)
- Dockerfile: builder -> golang:1.26-alpine, runtime ->
  alpine:3.21; drop libc6 dep; add /srv/mcias/certs and
  /srv/mcias/backups to image
- deploy/systemd/mcias.service: add RestrictSUIDSGID=true
- deploy/systemd/mcias-backup.service: new oneshot backup unit
- deploy/systemd/mcias-backup.timer: daily 02:00 UTC, 5m jitter
- deploy/scripts/install.sh: install backup units and enable
  timer; create certs/ and backups/ subdirs in /srv/mcias
- buf.yaml: add proto linting config for proto-lint target
- internal/db: add Snapshot and SnapshotDir methods (VACUUM INTO)
- cmd/mciasdb: add snapshot subcommand; no master key required
This commit is contained in:
2026-03-16 20:26:43 -07:00
parent 446b3df52d
commit b0afe3b993
15 changed files with 293 additions and 62 deletions

View File

@@ -0,0 +1,50 @@
# mcias-dev.conf — Local development configuration for mciassrv
#
# Suitable for running mciassrv on a developer workstation.
# DO NOT use this configuration in production:
# - Tokens expire quickly (for rapid test iteration).
# - The master key passphrase is trivial.
# - TLS paths point to local self-signed certificates.
#
# Generate a self-signed certificate for local development:
# openssl req -x509 -newkey ed25519 -days 365 \
# -keyout /tmp/mcias-dev.key -out /tmp/mcias-dev.crt \
# -subj "/CN=localhost" -nodes
#
# Set the master passphrase:
# export MCIAS_MASTER_PASSPHRASE=devpassphrase
#
# Start the server:
# mciassrv -config /path/to/mcias-dev.toml
[server]
listen_addr = "127.0.0.1:8443"
grpc_addr = "127.0.0.1:9443"
tls_cert = "/tmp/mcias-dev.crt"
tls_key = "/tmp/mcias-dev.key"
# trusted_proxy not set — direct local development, no reverse proxy.
[database]
path = "/tmp/mcias-dev.db"
[tokens]
issuer = "https://localhost:8443"
default_expiry = "1h"
admin_expiry = "30m"
service_expiry = "24h"
[argon2]
# OWASP minimums maintained even in dev; do not reduce further.
time = 2
memory = 65536
threads = 4
[master_key]
passphrase_env = "MCIAS_MASTER_PASSPHRASE"
# WebAuthn — passkey authentication for local development.
# rp_origin includes the non-standard port since we're not behind a proxy.
[webauthn]
rp_id = "localhost"
rp_origin = "https://localhost:8443"
display_name = "MCIAS (dev)"

View File

@@ -0,0 +1,61 @@
# mcias.conf.docker.example — Config template for container deployment
#
# Mount this file into the container at /srv/mcias/mcias.toml:
#
# docker run -d \
# --name mcias \
# -v /srv/mcias:/srv/mcias \
# -e MCIAS_MASTER_PASSPHRASE=your-passphrase \
# -p 8443:8443 \
# -p 9443:9443 \
# mcias:latest
#
# The container runs as uid 10001 (mcias). Ensure that:
# - /srv/mcias is writable by uid 10001
# - TLS cert and key are readable by uid 10001
#
# TLS: The server performs TLS termination inside the container; there is no
# plain-text mode. Place your certificate and key under /srv/mcias/.
# For Let's Encrypt certificates, mount the live/ directory read-only.
[server]
listen_addr = "0.0.0.0:8443"
grpc_addr = "0.0.0.0:9443"
tls_cert = "/srv/mcias/server.crt"
tls_key = "/srv/mcias/server.key"
# If a reverse proxy (nginx, Caddy, Traefik) sits in front of this container,
# set trusted_proxy to its container IP so real client IPs are used for rate
# limiting and audit logging. Leave commented out for direct exposure.
# trusted_proxy = "172.17.0.1"
[database]
# All data lives under /srv/mcias for a single-volume deployment.
path = "/srv/mcias/mcias.db"
[tokens]
issuer = "https://auth.example.com"
default_expiry = "168h"
admin_expiry = "8h"
service_expiry = "8760h"
[argon2]
time = 3
memory = 65536
threads = 4
[master_key]
# Pass the passphrase via the MCIAS_MASTER_PASSPHRASE environment variable.
# Set it with: docker run -e MCIAS_MASTER_PASSPHRASE=your-passphrase ...
# or with a Docker secret / Kubernetes secret.
passphrase_env = "MCIAS_MASTER_PASSPHRASE"
# ---------------------------------------------------------------------------
# [webauthn] — FIDO2/WebAuthn passkey authentication (OPTIONAL)
# ---------------------------------------------------------------------------
# Uncomment to enable passwordless passkey login. Set rp_id to your domain
# and rp_origin to the full HTTPS origin users access in their browser.
#
# [webauthn]
# rp_id = "auth.example.com"
# rp_origin = "https://auth.example.com"
# display_name = "MCIAS"

View File

@@ -0,0 +1,146 @@
# mcias.conf — Reference configuration for mciassrv
#
# Copy this file to /srv/mcias/mcias.toml and adjust the values for your
# deployment. All fields marked REQUIRED must be set before the server will
# start. Fields marked OPTIONAL can be omitted to use defaults.
#
# File permissions: mode 0640, owner root:mcias.
# chmod 0640 /srv/mcias/mcias.toml
# chown root:mcias /srv/mcias/mcias.toml
# ---------------------------------------------------------------------------
# [server] — Network listener configuration
# ---------------------------------------------------------------------------
[server]
# REQUIRED. Address and port for the HTTPS REST listener.
# Format: "host:port". Use "0.0.0.0" to listen on all interfaces.
# Ports > 1024 do not require elevated privileges.
listen_addr = "0.0.0.0:8443"
# OPTIONAL. Address and port for the gRPC/TLS listener.
# If omitted, the gRPC listener is disabled and only REST is served.
# Format: "host:port".
# grpc_addr = "0.0.0.0:9443"
# REQUIRED. Path to the TLS certificate (PEM format).
# Self-signed certificates work fine for personal deployments; for
# public-facing deployments consider a certificate from Let's Encrypt.
tls_cert = "/srv/mcias/server.crt"
# REQUIRED. Path to the TLS private key (PEM format).
# Permissions: mode 0640, owner root:mcias.
tls_key = "/srv/mcias/server.key"
# OPTIONAL. IP address of a trusted reverse proxy (e.g. nginx, Caddy, HAProxy).
# When set, the rate limiter and audit log extract the real client IP from the
# X-Real-IP or X-Forwarded-For header, but ONLY for requests whose TCP source
# address matches this exact IP. All other requests use RemoteAddr directly,
# preventing IP spoofing by external clients.
#
# Must be an IP address, not a hostname or CIDR range.
# Omit when running without a reverse proxy (direct Internet exposure).
#
# Example — local nginx proxy:
# trusted_proxy = "127.0.0.1"
#
# Example — Docker network gateway:
# trusted_proxy = "172.17.0.1"
# ---------------------------------------------------------------------------
# [database] — SQLite database
# ---------------------------------------------------------------------------
[database]
# REQUIRED. Path to the SQLite database file.
# The directory must be writable by the mcias user. WAL mode is enabled
# automatically; expect three files: mcias.db, mcias.db-wal, mcias.db-shm.
path = "/srv/mcias/mcias.db"
# ---------------------------------------------------------------------------
# [tokens] — JWT issuance policy
# ---------------------------------------------------------------------------
[tokens]
# REQUIRED. Issuer claim embedded in every JWT. Relying parties should
# validate this claim matches the expected value.
# Use the base URL of your MCIAS server (without trailing slash).
issuer = "https://auth.example.com"
# OPTIONAL. Default token expiry for interactive (human) logins.
# Go duration string: "h" hours, "m" minutes, "s" seconds.
# Default: 168h (7 days). The maximum allowed value is 720h (30 days).
default_expiry = "168h"
# OPTIONAL. Expiry for admin tokens (tokens with the "admin" role).
# Should be shorter than default_expiry to limit the blast radius of
# a leaked admin credential.
# Default: 8h.
admin_expiry = "8h"
# OPTIONAL. Expiry for system account tokens (machine-to-machine).
# System accounts have no interactive login; their tokens are long-lived.
# Default: 8760h (365 days).
service_expiry = "8760h"
# ---------------------------------------------------------------------------
# [argon2] — Password hashing parameters (Argon2id)
# ---------------------------------------------------------------------------
[argon2]
# OWASP 2023 minimums: time >= 2, memory >= 65536 KiB (64 MB).
# Increasing these values improves resistance to brute-force attacks but
# increases CPU and memory usage at login time.
# OPTIONAL. Time cost (number of passes over memory). Default: 3.
time = 3
# OPTIONAL. Memory cost in KiB. Default: 65536 (64 MB).
memory = 65536
# OPTIONAL. Parallelism (number of threads). Default: 4.
threads = 4
# ---------------------------------------------------------------------------
# [master_key] — AES-256 master key derivation
# ---------------------------------------------------------------------------
[master_key]
# REQUIRED. Exactly ONE of passphrase_env or keyfile must be set.
# Option A: Passphrase mode. The passphrase is read from the named environment
# variable at startup, then cleared. The Argon2id KDF salt is stored in the
# database on first run and reused on subsequent runs so the same passphrase
# always produces the same master key.
#
# Set the passphrase in /srv/mcias/env (loaded by the systemd EnvironmentFile
# directive). See dist/mcias.env.example for the template.
passphrase_env = "MCIAS_MASTER_PASSPHRASE"
# Option B: Key file mode. The file must contain exactly 32 bytes of raw key
# material (AES-256). Generate with: openssl rand -out /srv/mcias/master.key 32
# Permissions: mode 0640, owner root:mcias.
#
# Uncomment and comment out passphrase_env to switch modes.
# keyfile = "/srv/mcias/master.key"
# ---------------------------------------------------------------------------
# [webauthn] — FIDO2/WebAuthn passkey authentication (OPTIONAL)
# ---------------------------------------------------------------------------
# Enables passwordless passkey login and hardware security key 2FA.
# If this section is omitted or rp_id/rp_origin are empty, WebAuthn is
# disabled and passkey options will not appear in the UI.
#
# [webauthn]
#
# REQUIRED (if enabling). The Relying Party ID — typically the domain name
# (without port or scheme). Must match the domain users see in their browser.
# rp_id = "auth.example.com"
#
# REQUIRED (if enabling). The Relying Party Origin — the full origin URL
# including scheme. Must be HTTPS. Include the port if non-standard (not 443).
# rp_origin = "https://auth.example.com"
#
# OPTIONAL. Display name shown to users during passkey registration prompts.
# Default: "MCIAS".
# display_name = "MCIAS"

View File

@@ -0,0 +1,17 @@
# /srv/mcias/env — Environment file for mciassrv (systemd EnvironmentFile).
#
# This file is loaded by the mcias.service unit before the server starts.
# It must be readable only by root and the mcias service account:
#
# chmod 0640 /srv/mcias/env
# chown root:mcias /srv/mcias/env
#
# SECURITY: This file contains the master key passphrase. Treat it with
# the same care as a private key. Do not commit it to version control.
# Back it up to a secure offline location — losing this passphrase means
# losing access to all encrypted data in the database.
# Master key passphrase. Used to derive the AES-256 master key via Argon2id.
# Choose a long, random passphrase (e.g., generated by `openssl rand -base64 32`).
# This must match the passphrase_env setting in mcias.conf.
MCIAS_MASTER_PASSPHRASE=change-me-to-a-long-random-passphrase