Implement Phase 8: operational artifacts

- Makefile: build/test/lint/generate/man/install/clean/dist/docker;
  CGO_ENABLED=1 throughout; VERSION from git describe --tags --always
- Dockerfile: multi-stage (golang:1.26-bookworm builder ->
  debian:bookworm-slim runtime); non-root uid 10001 (mcias),
  VOLUME /data, EXPOSE 8443/9443; no toolchain in final image
- dist/mcias.service: hardened systemd unit (ProtectSystem=strict,
  ProtectHome, PrivateTmp, NoNewPrivileges, MemoryDenyWriteExecute,
  CapabilityBoundingSet= empty, EnvironmentFile, LimitNOFILE=65536)
- dist/mcias.env.example: passphrase env file template
- dist/mcias.conf.example: fully-commented production TOML config
- dist/mcias-dev.conf.example: local dev config (/tmp, short expiry)
- dist/mcias.conf.docker.example: container config template
- dist/install.sh: POSIX sh idempotent installer; creates mcias
  user/group, installs binaries, /etc/mcias, /var/lib/mcias,
  systemd unit, man pages; prints post-install instructions
- man/man1/mciassrv.1: mdoc synopsis/config/API/signals/files
- man/man1/mciasctl.1: mdoc all subcommands/env/examples
- man/man1/mciasdb.1: mdoc trust model/safety/all subcommands
- man/man1/mciasgrpcctl.1: mdoc gRPC commands/grpcurl example
- README.md: user-facing quick-start, first-run setup, build
  instructions, CLI references, Docker deployment, security notes
- .gitignore: added /bin/, dist/mcias_*.tar.gz, man/man1/*.gz
This commit is contained in:
2026-03-11 15:11:36 -07:00
parent 8f706f10ec
commit 941c71f2d1
15 changed files with 1715 additions and 54 deletions

14
.gitignore vendored
View File

@@ -1,32 +1,24 @@
# Build output (root-level binaries only)
/mciassrv
/mciasctl
/mciasdb
/mciasgrpcctl
/bin/
*.exe
# Database files
*.db
*.db-wal
*.db-shm
# Test artifacts
*.out
*.test
coverage.html
coverage.txt
# Config files with secrets (keep example configs)
mcias.toml
# Editor artifacts
.DS_Store
.idea/
.vscode/
*.swp
*.swo
*~
# Go workspace files
go.work
go.work.sum
dist/mcias_*.tar.gz
man/man1/*.gz

99
Dockerfile Normal file
View File

@@ -0,0 +1,99 @@
# Dockerfile — MCIAS multi-stage container image
#
# Stage 1 (builder): Compiles all four MCIAS binaries.
# Stage 2 (runtime): Minimal Debian image containing only the binaries.
#
# The final image:
# - Runs as non-root uid 10001 (mcias)
# - Exposes port 8443 (REST/TLS) and 9443 (gRPC/TLS)
# - Declares VOLUME /data for the SQLite database
# - Does NOT contain the Go toolchain, source code, or build cache
#
# Build:
# docker build -t mcias:$(git describe --tags --always) .
#
# Run:
# docker run -d \
# --name mcias \
# -v /path/to/config:/etc/mcias:ro \
# -v mcias-data:/data \
# -e MCIAS_MASTER_PASSPHRASE=your-passphrase \
# -p 8443:8443 \
# -p 9443:9443 \
# mcias:latest
# ---------------------------------------------------------------------------
# Stage 1 — builder
# ---------------------------------------------------------------------------
FROM golang:1.26-bookworm AS builder
WORKDIR /build
# Download dependencies first for layer caching.
COPY go.mod go.sum ./
RUN go mod download
# Copy source.
COPY . .
# CGO_ENABLED=1 is required by modernc.org/sqlite (pure-Go CGo-free SQLite).
# -trimpath removes local file system paths from the binary.
# -ldflags="-s -w" strips the DWARF debug info and symbol table to reduce
# image size.
RUN CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /out/mciassrv ./cmd/mciassrv && \
CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /out/mciasctl ./cmd/mciasctl && \
CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /out/mciasdb ./cmd/mciasdb && \
CGO_ENABLED=1 go build -trimpath -ldflags="-s -w" -o /out/mciasgrpcctl ./cmd/mciasgrpcctl
# ---------------------------------------------------------------------------
# Stage 2 — runtime
# ---------------------------------------------------------------------------
FROM debian:bookworm-slim
# Install runtime dependencies.
# ca-certificates: required to validate external TLS certificates.
# libc6: required by CGo-compiled binaries (sqlite).
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
libc6 && \
rm -rf /var/lib/apt/lists/*
# Create a non-root user for the service.
# uid/gid 10001 is chosen to be well above the range typically assigned to
# system users (1999) and human users (1000+), reducing the chance of
# collision with existing uids on the host when using host networking.
RUN groupadd --gid 10001 mcias && \
useradd --uid 10001 --gid 10001 --no-create-home --shell /usr/sbin/nologin mcias
# Copy compiled binaries from the builder stage.
COPY --from=builder /out/mciassrv /usr/local/bin/mciassrv
COPY --from=builder /out/mciasctl /usr/local/bin/mciasctl
COPY --from=builder /out/mciasdb /usr/local/bin/mciasdb
COPY --from=builder /out/mciasgrpcctl /usr/local/bin/mciasgrpcctl
# Create the config and data directories.
# /etc/mcias is mounted read-only by the operator with the config file,
# TLS cert, and TLS key.
# /data is the SQLite database mount point.
RUN mkdir -p /etc/mcias /data && \
chown mcias:mcias /data && \
chmod 0750 /data
# Declare /data as a volume so the operator must explicitly mount it.
# The SQLite database must persist across container restarts.
VOLUME /data
# REST/TLS port and gRPC/TLS port. These are documentation only; the actual
# ports are set in the config file. Override by mounting a different config.
EXPOSE 8443
EXPOSE 9443
# Run as the non-root mcias user.
USER mcias
# Default entry point and config path.
# The operator mounts /etc/mcias/mcias.conf from the host or a volume.
# See dist/mcias.conf.docker.example for a suitable template.
ENTRYPOINT ["mciassrv"]
CMD ["-config", "/etc/mcias/mcias.conf"]

151
Makefile Normal file
View File

@@ -0,0 +1,151 @@
# Makefile — MCIAS build, test, lint, and release targets
#
# Usage:
# make build — compile all binaries to bin/
# make test — run tests with race detector
# make lint — run golangci-lint
# make generate — regenerate protobuf stubs (requires protoc)
# make man — build compressed man pages
# make install — run dist/install.sh (requires root)
# make clean — remove bin/ and generated artifacts
# make dist — build release tarballs for linux/amd64 and linux/arm64
# make docker — build Docker image tagged mcias:$(VERSION)
# ---------------------------------------------------------------------------
# Variables
# ---------------------------------------------------------------------------
MODULE := git.wntrmute.dev/kyle/mcias
BINARIES := mciassrv mciasctl mciasdb mciasgrpcctl
BIN_DIR := bin
MAN_DIR := man/man1
MAN_PAGES := $(MAN_DIR)/mciassrv.1 $(MAN_DIR)/mciasctl.1 \
$(MAN_DIR)/mciasdb.1 $(MAN_DIR)/mciasgrpcctl.1
# Version: prefer git describe; fall back to "dev" when git is unavailable
# or the tree has no tags.
VERSION := $(shell git describe --tags --always 2>/dev/null || echo dev)
# Build flags: trim paths from binaries and strip DWARF/symbol table.
# CGO_ENABLED=1 is required for modernc.org/sqlite.
GO := go
GOFLAGS := -trimpath
LDFLAGS := -s -w -X main.version=$(VERSION)
CGO := CGO_ENABLED=1
# Platforms for cross-compiled dist tarballs.
DIST_PLATFORMS := linux/amd64 linux/arm64
# ---------------------------------------------------------------------------
# Default target
# ---------------------------------------------------------------------------
.PHONY: all
all: build
# ---------------------------------------------------------------------------
# build — compile all binaries to bin/
# ---------------------------------------------------------------------------
.PHONY: build
build:
@mkdir -p $(BIN_DIR)
@for bin in $(BINARIES); do \
echo " GO BUILD cmd/$$bin"; \
$(CGO) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" \
-o $(BIN_DIR)/$$bin ./cmd/$$bin; \
done
# ---------------------------------------------------------------------------
# test — run all tests with race detector
# ---------------------------------------------------------------------------
.PHONY: test
test:
$(CGO) $(GO) test -race ./...
# ---------------------------------------------------------------------------
# lint — run golangci-lint
# ---------------------------------------------------------------------------
.PHONY: lint
lint:
golangci-lint run ./...
# ---------------------------------------------------------------------------
# generate — regenerate protobuf stubs from proto/ definitions
# Requires: protoc, protoc-gen-go, protoc-gen-go-grpc
# ---------------------------------------------------------------------------
.PHONY: generate
generate:
$(GO) generate ./...
# ---------------------------------------------------------------------------
# man — build compressed man pages
# ---------------------------------------------------------------------------
.PHONY: man
man: $(patsubst %.1,%.1.gz,$(MAN_PAGES))
%.1.gz: %.1
gzip -kf $<
# ---------------------------------------------------------------------------
# install — run the installation script (requires root)
# ---------------------------------------------------------------------------
.PHONY: install
install: build
sh dist/install.sh
# ---------------------------------------------------------------------------
# clean — remove build artifacts
# ---------------------------------------------------------------------------
.PHONY: clean
clean:
rm -rf $(BIN_DIR)
rm -f $(patsubst %.1,%.1.gz,$(MAN_PAGES))
# ---------------------------------------------------------------------------
# dist — cross-compiled release tarballs for linux/amd64 and linux/arm64
#
# Output files: dist/mcias_<version>_<os>_<arch>.tar.gz
# Each tarball contains: mciassrv, mciasctl, mciasdb, mciasgrpcctl,
# man pages, and dist/ files.
# ---------------------------------------------------------------------------
.PHONY: dist
dist: man
@for platform in $(DIST_PLATFORMS); do \
os=$$(echo $$platform | cut -d/ -f1); \
arch=$$(echo $$platform | cut -d/ -f2); \
outdir=dist/mcias_$$(echo $(VERSION) | tr -d 'v')_$${os}_$${arch}; \
echo " DIST $$platform -> $$outdir.tar.gz"; \
mkdir -p $$outdir/bin; \
for bin in $(BINARIES); do \
CGO_ENABLED=1 GOOS=$$os GOARCH=$$arch $(GO) build \
$(GOFLAGS) -ldflags "$(LDFLAGS)" \
-o $$outdir/bin/$$bin ./cmd/$$bin; \
done; \
cp -r man $$outdir/; \
cp dist/mcias.conf.example dist/mcias-dev.conf.example \
dist/mcias.env.example dist/mcias.service \
dist/install.sh $$outdir/; \
tar -czf $$outdir.tar.gz -C dist mcias_$$(echo $(VERSION) | tr -d 'v')_$${os}_$${arch}; \
rm -rf $$outdir; \
done
# ---------------------------------------------------------------------------
# docker — build the Docker image
# ---------------------------------------------------------------------------
.PHONY: docker
docker:
docker build -t mcias:$(VERSION) .
# ---------------------------------------------------------------------------
# Help
# ---------------------------------------------------------------------------
.PHONY: help
help:
@echo "Available targets:"
@echo " build Compile all binaries to bin/"
@echo " test Run tests with race detector"
@echo " lint Run golangci-lint"
@echo " generate Regenerate protobuf stubs"
@echo " man Build compressed man pages"
@echo " install Install to /usr/local/bin (requires root)"
@echo " clean Remove build artifacts"
@echo " dist Build release tarballs for Linux amd64/arm64"
@echo " docker Build Docker image mcias:$(VERSION)"

View File

@@ -1,16 +1,10 @@
# MCIAS Progress
Source of truth for current development state.
---
## Current Status: Phase 7 Complete — Phases 89 Planned
137 tests pass with zero race conditions. Phase 7 (gRPC dual-stack) is
complete. Phases 89 are designed and documented; implementation not yet started.
### Completed Phases
137 tests pass with zero race conditions. Phase 8 (operational artifacts) is
complete. Phase 9 (client libraries) is designed and documented; implementation
not yet started.
- [x] Phase 0: Repository bootstrap (go.mod, .gitignore, docs)
- [x] Phase 1: Foundational packages (model, config, crypto, db)
- [x] Phase 2: Auth core (auth, token, middleware)
@@ -19,16 +13,46 @@ complete. Phases 89 are designed and documented; implementation not yet start
- [x] Phase 5: E2E tests, security hardening, commit
- [x] Phase 6: mciasdb — direct SQLite maintenance tool
- [x] Phase 7: gRPC interface (alternate transport; dual-stack with REST)
### Planned Phases
- [ ] Phase 8: Operational artifacts (systemd unit, man pages, Makefile, install script)
- [x] Phase 8: Operational artifacts (Makefile, Dockerfile, systemd, man pages, install script)
- [ ] Phase 9: Client libraries (Go, Rust, Common Lisp, Python)
---
## Implementation Log
**Makefile**
- Targets: build, test, lint, generate, man, install, clean, dist, docker
- build: compiles all four binaries to bin/ with CGO_ENABLED=1 and
-trimpath -ldflags="-s -w"
- dist: cross-compiled tarballs for linux/amd64 and linux/arm64
- docker: builds image tagged mcias:$(git describe --tags --always)
- VERSION derived from git describe --tags --always
**Dockerfile** (multi-stage)
- Build stage: golang:1.26-bookworm with CGO_ENABLED=1
- Runtime stage: debian:bookworm-slim with only ca-certificates and libc6;
no Go toolchain, no source, no build cache in final image
- Non-root user mcias (uid/gid 10001)
- EXPOSE 8443 (REST/TLS) and EXPOSE 9443 (gRPC/TLS)
- VOLUME /data for the SQLite database mount point
- ENTRYPOINT ["mciassrv"] CMD ["-config", "/etc/mcias/mcias.conf"]
**dist/ artifacts**
- dist/mcias.service: hardened systemd unit with ProtectSystem=strict,
ProtectHome=true, PrivateTmp=true, NoNewPrivileges=true,
CapabilityBoundingSet= (no capabilities), ReadWritePaths=/var/lib/mcias,
EnvironmentFile=/etc/mcias/env, Restart=on-failure, LimitNOFILE=65536
- dist/mcias.env.example: passphrase env file template
- dist/mcias.conf.example: fully-commented production TOML config reference
- dist/mcias-dev.conf.example: local dev config (127.0.0.1, short expiry)
- dist/mcias.conf.docker.example: container config template
- dist/install.sh: idempotent POSIX sh installer; creates user/group,
installs binaries, creates /etc/mcias and /var/lib/mcias, installs
systemd unit and man pages; existing configs not overwritten (placed .new)
**man/ pages** (mdoc format)
- man/man1/mciassrv.1: synopsis, options, config, REST API, signals, files
- man/man1/mciasctl.1: all subcommands, env vars, examples
- man/man1/mciasdb.1: trust model warnings, all subcommands, examples
- man/man1/mciasgrpcctl.1: gRPC subcommands, grpcurl examples
**Documentation**
- README.md: replaced dev-workflow notes with user-facing docs; quick-start,
first-run setup, build instructions, CLI references, Docker deployment,
man page index, security notes
- .gitignore: added /bin/, dist/mcias_*.tar.gz, man/man1/*.gz
### 2026-03-11 — Phase 7: gRPC dual-stack
**proto/mcias/v1/**

277
README.md
View File

@@ -1,32 +1,257 @@
The project notes are in PROJECT.md.
# MCIAS — Metacircular Identity and Access System
Before starting the project, read the project description and develop a
system architecture document (ARCHITECTURE.md). It should describe the
technical design of the system. Then, PROJECT_PLAN.md should be written
that describes how to build this system in discrete steps.
MCIAS is a self-hosted SSO and IAM service for personal projects.
It provides authentication (JWT/Ed25519), account management, TOTP, and
Postgres credential storage over a REST API (HTTPS) and a gRPC API (TLS).
Commit these files to Git after human review. For this project, all git
commits should have a single first line, no more than 55 characters,
that succinctly describes an overview of the changes. As necessary, the
body of the commit message should contain bullets describing the work
that was done.
See [ARCHITECTURE.md](ARCHITECTURE.md) for the technical design and
[PROJECT_PLAN.md](PROJECT_PLAN.md) for the implementation roadmap.
Then, create PROGRESS.md. This should be initialized with the first
steps for the project.
---
The development process for this should generally be:
## Quick Start
1. Determine the current state of the project. Track the progress in
PROGRESS.md, explicitly stating what was done and next steps.
2. Develop reasonable, discrete next steps to move forward with
actionable acceptance criteria.
3. Complete the next steps.
+ You should adversarially check whether any outputs are correct.
+ All code units should be thoroughly unit tested.
+ All subsystems should be thoroughly integration tested.
+ Where appropriate, end-to-end tests to validate the system should
be developed.
+ All code changes must pass golangci-lint checks.
4. After each phase, checkpoint your work, committing it to git.
### Option A: Install from source
Repeat this cycle until the system is in the desired end state.
**Prerequisites:** Go 1.26+, a C compiler (required by modernc.org/sqlite).
```sh
git clone https://git.wntrmute.dev/kyle/mcias
cd mcias
make build # produces bin/mciassrv, other binaries
sudo make install
```
### Option B: Docker
```sh
docker build -t mcias:latest .
# or
make docker # tags mcias:<git-describe>
```
See [Deploying with Docker](#deploying-with-docker) below.
---
## First-Run Setup
### 1. Generate a TLS certificate
```sh
openssl req -x509 -newkey ed25519 -days 3650 \
-keyout /etc/mcias/server.key \
-out /etc/mcias/server.crt \
-subj "/CN=auth.example.com" \
-nodes
chmod 0640 /etc/mcias/server.key
chown root:mcias /etc/mcias/server.key
```
### 2. Configure the server
```sh
cp dist/mcias.conf.example /etc/mcias/mcias.conf
$EDITOR /etc/mcias/mcias.conf
chmod 0640 /etc/mcias/mcias.conf
chown root:mcias /etc/mcias/mcias.conf
```
Minimum required fields:
```toml
[server]
listen_addr = "0.0.0.0:8443"
tls_cert = "/etc/mcias/server.crt"
tls_key = "/etc/mcias/server.key"
[database]
path = "/var/lib/mcias/mcias.db"
[tokens]
issuer = "https://auth.example.com"
[master_key]
passphrase_env = "MCIAS_MASTER_PASSPHRASE"
```
See `dist/mcias.conf.example` for the fully-commented reference configuration.
For local development, use `dist/mcias-dev.conf.example`.
### 3. Set the master key passphrase
```sh
cp dist/mcias.env.example /etc/mcias/env
$EDITOR /etc/mcias/env # replace the placeholder passphrase
chmod 0640 /etc/mcias/env
chown root:mcias /etc/mcias/env
```
> **Important:** Back up the passphrase to a secure offline location.
> Losing it means losing access to all encrypted data in the database.
### 4. Create the first admin account
```sh
export MCIAS_MASTER_PASSPHRASE=your-passphrase
mciasdb --config /etc/mcias/mcias.conf account create \
--username admin --type human
mciasdb --config /etc/mcias/mcias.conf account set-password --id <UUID>
mciasdb --config /etc/mcias/mcias.conf role grant --id <UUID> --role admin
```
### 5. Start the server
```sh
# systemd
systemctl enable --now mcias
# manual
MCIAS_MASTER_PASSPHRASE=your-passphrase mciassrv -config /etc/mcias/mcias.conf
```
### 6. Verify
```sh
curl -k https://localhost:8443/v1/health
# {"status":"ok"}
```
---
## Building
```sh
make build # compile all binaries to bin/
make test # go test -race ./...
make lint # golangci-lint run ./...
make generate # regenerate protobuf stubs (requires protoc)
make man # build compressed man pages
make clean # remove bin/ and generated artifacts
make dist # cross-compiled tarballs for linux/amd64 and linux/arm64
make docker # build Docker image mcias:<version>
```
---
## Admin CLI (mciasctl)
```sh
TOKEN=$(curl -sk https://localhost:8443/v1/auth/login \
-d '{"username":"admin","password":"..."}' | jq -r .token)
export MCIAS_TOKEN=$token
mciasctl -server https://localhost:8443 account list
mciasctl account create -username alice -password s3cr3t
mciasctl role set -id $UUID -roles admin
mciasctl token issue -id $SYSTEM_UUID
mciasctl pgcreds set -id $UUID -host db.example.com -port 5432 \
-db mydb -user myuser -password mypassword
```
See `man mciasctl` for the full reference.
---
## Database Maintenance Tool (mciasdb)
```sh
export MCIAS_MASTER_PASSPHRASE=your-passphrase
CONF<<3C>--config /etc/mcias/mcias.conf
mciasdb $CONF schema verify
mciasdb $CONF account list
mciasdb $CONF account set-password --id $UUID
mciasdb $CONF role grant --id $UUID --role admin
mciasdb $CONF prune tokens
mciasdb $CONF audit tail --n 50
mciasdb $CONF audit query --account $UUID --json
```
See `man mciasdb` for the full reference, including the trust model and
safety warnings.
---
## gRPC Interface
Enable the gRPC listener in config:
```toml
[server]
listen_addr = "0.0.0.0:8443"
grpc_addr = "0.0.0.0:9443"
tls_cert = "/etc/mcias/server.crt"
tls_key = "/etc/mcias/server.key"
```
Using mciasgrpcctl:
```sh
export MCIAS_TOKEN=$ADMIN_JWT
mciasgrpcctl -server auth.example.com:9443 -cacert /etc/mcias/server.crt health
mciasgrpcctl account list
```
Using grpcurl:
```sh
grpcurl -cacert /etc/mcias/server.crt \
-H "authorization: Bearer $ADMIN_JWT" \
auth.example.com:9443 \
mcias.v1.AdminService/Health
```
See `man mciasgrpcctl` and [ARCHITECTURE.md](ARCHITECTURE.md) §17.
---
## Deploying with Docker
```sh
make docker
mkdir -p /srv/mcias/config
cp dist/mcias.conf.docker.example /srv/mcias/config/mcias.conf
$EDITOR /srv/mcias/config/mcias.conf
docker run -d \
--name mcias \
-v /srv/mcias/config:/etc/mcias:ro \
-v mcias-data:/data \
-e MCIAS_MASTER_PASSPHRASE=your-passphrase \
-p 8443:8443 \
-p 9443:9443 \
mcias:latest
curl -k https://localhost:8443/v1/health
```
The container runs as uid 10001 (mcias) with no capabilities.
TLS termination happens inside the container.
See `dist/mcias.conf.docker.example` for the config template.
---
## Man Pages
```sh
man mciassrv # server reference
man mciasctl # admin CLI (REST)
man mciasdb # database maintenance tool
man mciasgrpcctl # gRPC admin CLI
```
---
## Security Notes
- JWTs signed with Ed25519 (EdDSA); `alg` header validated before processing
to defeat algorithm confusion attacks.
- Passwords hashed with Argon2id at OWASP 2023 minimum parameters.
- All secret material encrypted at rest with AES-256-GCM using the master key.
- Credential fields never appear in any API response.
- TLS 1.2 minimum protocol version.
See [ARCHITECTURE.md](ARCHITECTURE.md) §2-³3 for the full security model.

225
dist/install.sh vendored Normal file
View File

@@ -0,0 +1,225 @@
#!/bin/sh
# install.sh — MCIAS first-time and upgrade installer
#
# Usage: sh dist/install.sh
#
# This script must be run as root. It:
# 1. Creates the mcias system user and group (idempotent).
# 2. Copies binaries to /usr/local/bin/.
# 3. Creates /etc/mcias/ and /var/lib/mcias/ with correct permissions.
# 4. Installs the systemd service unit.
# 5. Prints post-install instructions.
#
# The script does NOT start or enable the service automatically. Review the
# configuration files and set the master key passphrase before starting.
#
# Idempotent: safe to re-run after upgrades. Existing config files are not
# overwritten; new example files are placed alongside them with a .new suffix
# so you can review and merge changes.
#
# POSIX sh compatible — no bash-isms.
set -eu
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
BIN_DIR="/usr/local/bin"
CONF_DIR="/etc/mcias"
DATA_DIR="/var/lib/mcias"
MAN_DIR="/usr/share/man/man1"
SYSTEMD_DIR="/etc/systemd/system"
SERVICE_USER="mcias"
SERVICE_GROUP="mcias"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
info() {
printf '==> %s\n' "$*"
}
warn() {
printf 'WARNING: %s\n' "$*" >&2
}
die() {
printf 'ERROR: %s\n' "$*" >&2
exit 1
}
require_root() {
if [ "$(id -u)" -ne 0 ]; then
die "This script must be run as root."
fi
}
# install_file SRC DST MODE OWNER
# Installs SRC to DST. If DST already exists, installs SRC as DST.new
# so the operator can review changes.
install_file() {
src="$1"
dst="$2"
mode="$3"
owner="$4"
if [ -e "$dst" ]; then
info "Existing file found: $dst (installing as $dst.new)"
install -m "$mode" -o "$owner" "$src" "$dst.new"
else
install -m "$mode" -o "$owner" "$src" "$dst"
fi
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
require_root
# Step 1: Create system user and group.
info "Creating system user and group: $SERVICE_USER"
if getent group "$SERVICE_GROUP" > /dev/null 2>&1; then
info "Group $SERVICE_GROUP already exists — skipping."
else
groupadd --system "$SERVICE_GROUP"
fi
if getent passwd "$SERVICE_USER" > /dev/null 2>&1; then
info "User $SERVICE_USER already exists — skipping."
else
useradd \
--system \
--gid "$SERVICE_GROUP" \
--no-create-home \
--shell /usr/sbin/nologin \
--comment "MCIAS authentication server" \
"$SERVICE_USER"
fi
# Step 2: Install binaries.
info "Installing binaries to $BIN_DIR"
for bin in mciassrv mciasctl mciasdb mciasgrpcctl; do
src="$REPO_ROOT/$bin"
if [ ! -f "$src" ]; then
# Try bin/ subdirectory (Makefile build output).
src="$REPO_ROOT/bin/$bin"
fi
if [ ! -f "$src" ]; then
warn "Binary not found: $bin — skipping. Run 'make build' first."
continue
fi
info " Installing $bin"
install -m 0755 -o root -g root "$src" "$BIN_DIR/$bin"
done
# Step 3: Create configuration directory.
info "Creating $CONF_DIR"
install -d -m 0750 -o root -g "$SERVICE_GROUP" "$CONF_DIR"
# Install example config files; never overwrite existing configs.
for f in mcias.conf.example mcias.env.example; do
src="$SCRIPT_DIR/$f"
dst="$CONF_DIR/$f"
if [ -f "$src" ]; then
install -m 0640 -o root -g "$SERVICE_GROUP" "$src" "$dst" 2>/dev/null || true
fi
done
# Step 4: Create data directory.
info "Creating $DATA_DIR"
install -d -m 0750 -o "$SERVICE_USER" -g "$SERVICE_GROUP" "$DATA_DIR"
# Step 5: Install systemd service unit.
if [ -d "$SYSTEMD_DIR" ]; then
info "Installing systemd service unit to $SYSTEMD_DIR"
install -m 0644 -o root -g root "$SCRIPT_DIR/mcias.service" "$SYSTEMD_DIR/mcias.service"
info "Reloading systemd daemon"
systemctl daemon-reload 2>/dev/null || warn "systemctl not available; reload manually."
else
warn "systemd not found at $SYSTEMD_DIR; skipping service unit installation."
fi
# Step 6: Install man pages.
if [ -d "$REPO_ROOT/man/man1" ]; then
install -d -m 0755 -o root -g root "$MAN_DIR"
info "Installing man pages to $MAN_DIR"
for page in "$REPO_ROOT/man/man1"/*.1.gz; do
[ -f "$page" ] || continue
install -m 0644 -o root -g root "$page" "$MAN_DIR/"
done
# Also install uncompressed pages if no gz versions exist.
for page in "$REPO_ROOT/man/man1"/*.1; do
[ -f "$page" ] || continue
gzname="${MAN_DIR}/$(basename "$page").gz"
if [ ! -f "$gzname" ]; then
gzip -c "$page" > "$gzname"
chmod 0644 "$gzname"
fi
done
fi
# ---------------------------------------------------------------------------
# Post-install instructions
# ---------------------------------------------------------------------------
cat <<EOF
==========================================================================
MCIAS installed successfully.
==========================================================================
Next steps:
1. Generate a TLS certificate and key:
# Self-signed (development / personal use):
openssl req -x509 -newkey ed25519 -days 3650 \\
-keyout /etc/mcias/server.key \\
-out /etc/mcias/server.crt \\
-subj "/CN=auth.example.com" \\
-nodes
chmod 0640 /etc/mcias/server.key
chown root:mcias /etc/mcias/server.key
2. Copy and edit the configuration file:
cp /etc/mcias/mcias.conf.example /etc/mcias/mcias.conf
\$EDITOR /etc/mcias/mcias.conf
chmod 0640 /etc/mcias/mcias.conf
chown root:mcias /etc/mcias/mcias.conf
3. Set the master key passphrase:
cp /etc/mcias/mcias.env.example /etc/mcias/env
\$EDITOR /etc/mcias/env # replace the placeholder passphrase
chmod 0640 /etc/mcias/env
chown root:mcias /etc/mcias/env
IMPORTANT: Back up the passphrase to a secure offline location.
Losing it means losing access to all encrypted data in the database.
4. Enable and start the service:
systemctl enable mcias
systemctl start mcias
systemctl status mcias
5. Create the first admin account using mciasdb (while the server is
running, or before first start):
MCIAS_MASTER_PASSPHRASE=\$(grep MCIAS_MASTER_PASSPHRASE /etc/mcias/env | cut -d= -f2) \\
mciasdb --config /etc/mcias/mcias.conf account create \\
--username admin --type human
Then set a password:
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
account set-password --id <UUID>
And grant the admin role:
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
role grant --id <UUID> --role admin
For full documentation, see: man mciassrv
==========================================================================
EOF

42
dist/mcias-dev.conf.example vendored Normal file
View File

@@ -0,0 +1,42 @@
# 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.conf
[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"
[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"

48
dist/mcias.conf.docker.example vendored Normal file
View File

@@ -0,0 +1,48 @@
# mcias.conf.docker.example — Config template for container deployment
#
# Mount this file into the container at /etc/mcias/mcias.conf:
#
# docker run -d \
# --name mcias \
# -v /path/to/mcias.conf:/etc/mcias/mcias.conf:ro \
# -v /path/to/certs:/etc/mcias:ro \
# -v mcias-data:/data \
# -e MCIAS_MASTER_PASSPHRASE=your-passphrase \
# -p 8443:8443 \
# -p 9443:9443 \
# mcias:latest
#
# The container runs as uid 10001 (mcias). Ensure that:
# - /data volume 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. Mount your certificate and key under /etc/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 = "/etc/mcias/server.crt"
tls_key = "/etc/mcias/server.key"
[database]
# VOLUME /data is declared in the Dockerfile; map a named volume here.
path = "/data/mcias.db"
[tokens]
issuer = "https://auth.example.com"
default_expiry = "720h"
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"

110
dist/mcias.conf.example vendored Normal file
View File

@@ -0,0 +1,110 @@
# mcias.conf — Reference configuration for mciassrv
#
# Copy this file to /etc/mcias/mcias.conf 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 /etc/mcias/mcias.conf
# chown root:mcias /etc/mcias/mcias.conf
# ---------------------------------------------------------------------------
# [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 = "/etc/mcias/server.crt"
# REQUIRED. Path to the TLS private key (PEM format).
# Permissions: mode 0640, owner root:mcias.
tls_key = "/etc/mcias/server.key"
# ---------------------------------------------------------------------------
# [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 = "/var/lib/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: 720h (30 days). Reduce for higher-security deployments.
default_expiry = "720h"
# 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 /etc/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 /etc/mcias/master.key 32
# Permissions: mode 0640, owner root:mcias.
#
# Uncomment and comment out passphrase_env to switch modes.
# keyfile = "/etc/mcias/master.key"

17
dist/mcias.env.example vendored Normal file
View File

@@ -0,0 +1,17 @@
# /etc/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 /etc/mcias/env
# chown root:mcias /etc/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

51
dist/mcias.service vendored Normal file
View File

@@ -0,0 +1,51 @@
[Unit]
Description=MCIAS Authentication Server
Documentation=man:mciassrv(1)
After=network.target
# Require network to be available before starting.
# Remove if you bind only to loopback.
[Service]
Type=simple
User=mcias
Group=mcias
# Configuration and secrets.
# /etc/mcias/env must contain MCIAS_MASTER_PASSPHRASE=<passphrase>
# See dist/mcias.env.example for the template.
EnvironmentFile=/etc/mcias/env
ExecStart=/usr/local/bin/mciassrv -config /etc/mcias/mcias.conf
Restart=on-failure
RestartSec=5
# File descriptor limit. mciassrv keeps one fd per open connection plus
# the SQLite WAL files; 65536 is generous headroom for a personal server.
LimitNOFILE=65536
# Sandboxing. mcias does not need capabilities; it listens on ports > 1024.
# If you need port 443 or 8443 on a privileged port (< 1024), either:
# a) use a reverse proxy (recommended), or
# b) grant CAP_NET_BIND_SERVICE with: AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=
# Filesystem restrictions.
# mciassrv reads /etc/mcias (config, TLS cert/key) and writes /var/lib/mcias (DB).
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/mcias
# Additional hardening.
NoNewPrivileges=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target

124
man/man1/mciasctl.1 Normal file
View File

@@ -0,0 +1,124 @@
.Dd March 11, 2026
.Dt MCIASCTL 1
.Os
.Sh NAME
.Nm mciasctl
.Nd MCIAS admin CLI (REST)
.Sh SYNOPSIS
.Nm
.Op Fl server Ar url
.Op Fl token Ar jwt
.Op Fl cacert Ar path
.Ar command
.Op Ar subcommand
.Op Ar flags
.Sh DESCRIPTION
.Nm
is the administrator command-line interface for MCIAS.
It connects to a running
.Xr mciassrv 1
instance via the REST API over HTTPS and provides subcommands for managing
accounts, roles, tokens, and Postgres credentials.
.Pp
Authentication is performed using a bearer JWT.
The token must have the
.Qq admin
role for most operations.
Pass the token with the
.Fl token
flag or by setting the
.Ev MCIAS_TOKEN
environment variable.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl server Ar url
Base URL of the mciassrv instance.
Default:
.Qq https://localhost:8443 .
Can also be set with the
.Ev MCIAS_SERVER
environment variable.
.It Fl token Ar jwt
Bearer token for authentication.
Can also be set with the
.Ev MCIAS_TOKEN
environment variable.
.It Fl cacert Ar path
Path to a CA certificate in PEM format for TLS verification.
Useful when mciassrv uses a self-signed certificate.
If omitted, the system certificate pool is used.
.El
.Sh COMMANDS
.Ss account
.Bl -tag -width Ds
.It Nm Ic account Ic list
Lists all accounts.
Credential fields are never included in the output.
.It Nm Ic account Ic create Fl username Ar name Fl password Ar pass Op Fl type Ar human|system
Creates a new account.
.Fl type
defaults to
.Qq human .
.It Nm Ic account Ic get Fl id Ar uuid
Returns the account with the given UUID.
.It Nm Ic account Ic update Fl id Ar uuid Op Fl status Ar active|inactive
Updates account fields.
Currently only status can be updated.
.It Nm Ic account Ic delete Fl id Ar uuid
Soft-deletes the account and revokes all its tokens.
.El
.Ss role
.Bl -tag -width Ds
.It Nm Ic role Ic list Fl id Ar uuid
Lists the roles assigned to the account.
.It Nm Ic role Ic set Fl id Ar uuid Fl roles Ar role1,role2,...
Replaces the role set for the account with the comma-separated list.
.El
.Ss token
.Bl -tag -width Ds
.It Nm Ic token Ic issue Fl id Ar uuid
Issues a new service token for a system account.
.It Nm Ic token Ic revoke Fl jti Ar jti
Revokes the token with the given JTI.
.El
.Ss pgcreds
.Bl -tag -width Ds
.It Nm Ic pgcreds Ic set Fl id Ar uuid Fl host Ar host Fl port Ar port Fl db Ar db Fl user Ar user Fl password Ar pass
Sets Postgres credentials for the account.
The credentials are encrypted with AES-256-GCM using the server master key.
.It Nm Ic pgcreds Ic get Fl id Ar uuid
Retrieves and prints the Postgres credentials.
The password is included in plaintext; treat the output as sensitive.
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev MCIAS_TOKEN
Bearer token used for authentication when
.Fl token
is not specified.
.It Ev MCIAS_SERVER
Base URL of the mciassrv instance when
.Fl server
is not specified.
.El
.Sh EXAMPLES
List all accounts:
.Bd -literal -offset indent
mciasctl -server https://auth.example.com -token $ADMIN_TOKEN account list
.Ed
.Pp
Create a human account:
.Bd -literal -offset indent
mciasctl account create -username alice -password s3cr3t
.Ed
.Pp
Grant the admin role:
.Bd -literal -offset indent
mciasctl role set -id $UUID -roles admin
.Ed
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr mciassrv 1 ,
.Xr mciasdb 1 ,
.Xr mciasgrpcctl 1

203
man/man1/mciasdb.1 Normal file
View File

@@ -0,0 +1,203 @@
.Dd March 11, 2026
.Dt MCIASDB 1
.Os
.Sh NAME
.Nm mciasdb
.Nd MCIAS database maintenance tool
.Sh SYNOPSIS
.Nm
.Op Fl config Ar path
.Ar command
.Op Ar subcommand
.Op Ar flags
.Sh DESCRIPTION
.Nm
is the MCIAS database maintenance tool.
It operates
.Em directly
on the SQLite database file, bypassing the mciassrv REST API.
.Pp
Use
.Nm
for:
.Bl -bullet -compact
.It
Break-glass recovery when the server is unavailable.
.It
Offline account and role management before first server start.
.It
Schema verification and migration.
.It
Token pruning and audit log inspection.
.El
.Pp
.Nm
requires the same master key configuration as
.Xr mciassrv 1
to decrypt secrets at rest.
.Sh TRUST MODEL AND SAFETY WARNINGS
.Nm
has
.Em direct write access
to the database and can modify any record without going through the server's
validation logic.
Use it only when necessary.
.Bl -bullet
.It
Run
.Nm
on the same host as the database file.
Never copy the database to an untrusted host.
.It
The master key passphrase (or keyfile) must be available.
Set the environment variable named by
.Sy passphrase_env
in the config, or ensure the keyfile is accessible.
.It
All write operations append an audit log entry tagged
.Qq actor:mciasdb .
.It
Avoid running
.Nm
while mciassrv is under heavy write load; SQLite WAL mode provides some
isolation, but schema migrations acquire an exclusive lock.
.El
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl config Ar path
Path to the TOML configuration file.
Defaults to
.Pa mcias.toml
in the current directory.
.El
.Sh COMMANDS
.Ss schema
.Bl -tag -width Ds
.It Nm Ic schema Ic verify
Reports the current schema version.
Exits 0 if the schema is up to date; exits 1 if migrations are pending.
.It Nm Ic schema Ic migrate
Applies any pending migrations and reports each one applied.
Exits 0 on success.
.El
.Ss account
.Bl -tag -width Ds
.It Nm Ic account Ic list
Prints UUID, username, type, and status for all accounts.
.It Nm Ic account Ic get Fl -id Ar uuid
Prints the account record for the given UUID.
.It Nm Ic account Ic create Fl -username Ar name Fl -type Ar human|system
Inserts a new account row and prints the new UUID.
.It Nm Ic account Ic set-password Fl -id Ar uuid
Prompts for a new password twice (confirmation).
Re-hashes with Argon2id and updates the row.
No
.Fl -password
flag is provided; the password is always read interactively.
.It Nm Ic account Ic set-status Fl -id Ar uuid Fl -status Ar STATUS
Updates the account status.
Valid statuses:
.Qq active ,
.Qq inactive ,
.Qq deleted .
.It Nm Ic account Ic reset-totp Fl -id Ar uuid
Clears the TOTP secret and disables TOTP for the account.
The user will be able to log in with password only until they re-enrol.
.El
.Ss role
.Bl -tag -width Ds
.It Nm Ic role Ic list Fl -id Ar uuid
Prints the roles assigned to the account.
.It Nm Ic role Ic grant Fl -id Ar uuid Fl -role Ar role
Adds a role to the account.
.It Nm Ic role Ic revoke Fl -id Ar uuid Fl -role Ar role
Removes a role from the account.
.El
.Ss token
.Bl -tag -width Ds
.It Nm Ic token Ic list Fl -id Ar uuid
Prints JTI, issued_at, expires_at, and revoked_at for all tokens associated
with the account.
.It Nm Ic token Ic revoke Fl -jti Ar jti
Sets revoked_at to the current time on the specified token row.
.It Nm Ic token Ic revoke-all Fl -id Ar uuid
Revokes all non-revoked tokens for the account.
A no-op (exits 0) if no active tokens exist.
.El
.Ss prune
.Bl -tag -width Ds
.It Nm Ic prune Ic tokens
Deletes rows from the token_revocation table where expires_at is in the past.
Prints the number of rows removed.
.El
.Ss audit
.Bl -tag -width Ds
.It Nm Ic audit Ic tail Op Fl -n Ar N
Prints the last N audit events, newest last.
Default: 50.
.It Nm Ic audit Ic query Op Fl -account Ar uuid Op Fl -type Ar event_type Op Fl -since Ar timestamp Op Fl -json
Queries the audit log.
Filters are combinable (AND semantics).
.Fl -since
accepts an RFC 3339 timestamp.
.Fl -json
emits newline-delimited JSON instead of tabular output.
.El
.Ss pgcreds
.Bl -tag -width Ds
.It Nm Ic pgcreds Ic get Fl -id Ar uuid
Decrypts and prints the Postgres credentials for the account.
A warning header is printed before the output because the password is
displayed in plaintext.
.It Nm Ic pgcreds Ic set Fl -id Ar uuid Fl -host Ar host Fl -port Ar port Fl -db Ar db Fl -user Ar user
Prompts for the Postgres password interactively (no
.Fl -password
flag).
Encrypts with AES-256-GCM using the master key and stores the record.
.El
.Sh EXAMPLES
Verify schema after upgrade:
.Bd -literal -offset indent
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf schema verify
.Ed
.Pp
Create an admin account before first server start:
.Bd -literal -offset indent
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
account create --username admin --type human
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
account set-password --id <UUID>
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
role grant --id <UUID> --role admin
.Ed
.Pp
Tail the last 20 audit events:
.Bd -literal -offset indent
MCIAS_MASTER_PASSPHRASE=... mciasdb --config /etc/mcias/mcias.conf \\
audit tail --n 20
.Ed
.Sh ENVIRONMENT
The environment variable named by
.Sy passphrase_env
in the configuration file must be set to the master key passphrase, unless
.Sy keyfile
is used instead.
.Pp
Example (with default config):
.Bd -literal -offset indent
export MCIAS_MASTER_PASSPHRASE=your-passphrase
mciasdb --config /etc/mcias/mcias.conf account list
.Ed
.Sh EXIT STATUS
.Ex -std
.Sh FILES
.Bl -tag -width Ds
.It Pa /etc/mcias/mcias.conf
Default configuration file location.
.It Pa /var/lib/mcias/mcias.db
SQLite database.
.El
.Sh SEE ALSO
.Xr mciassrv 1 ,
.Xr mciasctl 1 ,
.Xr mciasgrpcctl 1

122
man/man1/mciasgrpcctl.1 Normal file
View File

@@ -0,0 +1,122 @@
.Dd March 11, 2026
.Dt MCIASGRPCCTL 1
.Os
.Sh NAME
.Nm mciasgrpcctl
.Nd MCIAS gRPC admin CLI
.Sh SYNOPSIS
.Nm
.Op Fl server Ar addr
.Op Fl token Ar jwt
.Op Fl cacert Ar path
.Ar command
.Op Ar subcommand
.Op Ar flags
.Sh DESCRIPTION
.Nm
is the gRPC companion to
.Xr mciasctl 1 .
It connects to the gRPC/TLS listener of a running
.Xr mciassrv 1
instance and provides subcommands mirroring the REST admin CLI.
.Pp
The gRPC listener must be enabled in the mciassrv configuration
.Pq Sy grpc_addr
for
.Nm
to connect.
.Pp
Authentication is performed using a bearer JWT passed as gRPC metadata.
The token must have the
.Qq admin
role for most operations.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl server Ar addr
gRPC server address in
.Ar host:port
format.
Default:
.Qq localhost:9443 .
.It Fl token Ar jwt
Bearer token for authentication.
Can also be set with the
.Ev MCIAS_TOKEN
environment variable.
.It Fl cacert Ar path
Path to a CA certificate in PEM format for TLS verification.
Useful when mciassrv uses a self-signed certificate.
.El
.Sh COMMANDS
.Ss Informational (no authentication required)
.Bl -tag -width Ds
.It Nm Ic health
Calls the Health RPC.
Prints
.Qq ok
and exits 0 if the server is healthy.
.It Nm Ic pubkey
Returns the server's Ed25519 public key as a JWK.
.El
.Ss account
.Bl -tag -width Ds
.It Nm Ic account Ic list
Lists all accounts.
.It Nm Ic account Ic create Fl username Ar name Fl password Ar pass Op Fl type Ar human|system
Creates a new account.
.It Nm Ic account Ic get Fl id Ar uuid
Returns the account with the given UUID.
.It Nm Ic account Ic update Fl id Ar uuid Fl status Ar active|inactive
Updates account status.
.It Nm Ic account Ic delete Fl id Ar uuid
Soft-deletes the account and revokes all its tokens.
.El
.Ss role
.Bl -tag -width Ds
.It Nm Ic role Ic list Fl id Ar uuid
Lists roles for the account.
.It Nm Ic role Ic set Fl id Ar uuid Fl roles Ar role1,role2,...
Replaces the role set for the account.
.El
.Ss token
.Bl -tag -width Ds
.It Nm Ic token Ic validate Fl token Ar jwt
Validates the given token and prints its claims.
.It Nm Ic token Ic issue Fl id Ar uuid
Issues a new service token for a system account.
.It Nm Ic token Ic revoke Fl jti Ar jti
Revokes the token with the given JTI.
.El
.Ss pgcreds
.Bl -tag -width Ds
.It Nm Ic pgcreds Ic get Fl id Ar uuid
Returns the Postgres credentials for the account.
.It Nm Ic pgcreds Ic set Fl id Ar uuid Fl host Ar host Op Fl port Ar port Fl db Ar db Fl user Ar user Fl password Ar pass
Sets Postgres credentials for the account.
.El
.Sh ENVIRONMENT
.Bl -tag -width Ds
.It Ev MCIAS_TOKEN
Bearer token used for authentication when
.Fl token
is not specified.
.El
.Sh EXAMPLES
Check server health over gRPC:
.Bd -literal -offset indent
mciasgrpcctl -server auth.example.com:9443 -cacert /etc/mcias/ca.crt health
.Ed
.Pp
Using grpcurl as an alternative client:
.Bd -literal -offset indent
grpcurl -cacert /etc/mcias/ca.crt \\
-H "authorization: Bearer $TOKEN" \\
auth.example.com:9443 \\
mcias.v1.AdminService/Health
.Ed
.Sh EXIT STATUS
.Ex -std
.Sh SEE ALSO
.Xr mciassrv 1 ,
.Xr mciasctl 1 ,
.Xr mciasdb 1

228
man/man1/mciassrv.1 Normal file
View File

@@ -0,0 +1,228 @@
.Dd March 11, 2026
.Dt MCIASSRV 1
.Os
.Sh NAME
.Nm mciassrv
.Nd MCIAS authentication server
.Sh SYNOPSIS
.Nm
.Fl config
.Ar path
.Sh DESCRIPTION
.Nm
is the MCIAS (Metacircular Identity and Access System) authentication server.
It provides a single-sign-on (SSO) and identity and access management (IAM)
service for personal applications.
.Pp
.Nm
reads a TOML configuration file, derives the AES-256 master encryption key,
loads or generates an Ed25519 signing key, opens the SQLite database, applies
any pending schema migrations, and starts an HTTPS listener.
.Pp
If the
.Sy grpc_addr
field is set in the configuration file, a gRPC/TLS listener is also started
on the specified address.
Both listeners share the same signing key, database connection, and
configuration.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl config Ar path
Path to the TOML configuration file.
Defaults to
.Pa mcias.toml
in the current directory.
.El
.Sh CONFIGURATION
The configuration file is in TOML format.
See
.Pa /etc/mcias/mcias.conf
(or
.Pa dist/mcias.conf.example
in the source tree) for a fully-commented reference configuration.
.Pp
The following sections are recognised:
.Bl -tag -width Ds
.It Sy [server]
.Bl -tag -width Ds
.It Sy listen_addr
.Pq required
Host and port for the HTTPS REST listener.
Example:
.Qq 0.0.0.0:8443 .
.It Sy grpc_addr
.Pq optional
Host and port for the gRPC/TLS listener.
If omitted, gRPC is disabled.
Example:
.Qq 0.0.0.0:9443 .
.It Sy tls_cert
.Pq required
Path to the TLS certificate file in PEM format.
.It Sy tls_key
.Pq required
Path to the TLS private key file in PEM format.
.El
.It Sy [database]
.Bl -tag -width Ds
.It Sy path
.Pq required
Path to the SQLite database file.
WAL mode and foreign key enforcement are enabled automatically.
.El
.It Sy [tokens]
.Bl -tag -width Ds
.It Sy issuer
.Pq required
Issuer claim embedded in every JWT.
Use the base URL of your MCIAS server.
.It Sy default_expiry
.Pq optional, default 720h
Token expiry for interactive logins.
Go duration string.
.It Sy admin_expiry
.Pq optional, default 8h
Token expiry for tokens with the
.Qq admin
role.
.It Sy service_expiry
.Pq optional, default 8760h
Token expiry for system account tokens.
.El
.It Sy [argon2]
.Bl -tag -width Ds
.It Sy time
.Pq optional, default 3
Argon2id time cost.
Must be >= 2 (OWASP 2023 minimum).
.It Sy memory
.Pq optional, default 65536
Argon2id memory cost in KiB.
Must be >= 65536 (64 MB, OWASP 2023 minimum).
.It Sy threads
.Pq optional, default 4
Argon2id parallelism.
.El
.It Sy [master_key]
Exactly one of the following must be set:
.Bl -tag -width Ds
.It Sy passphrase_env
Name of the environment variable containing the master key passphrase.
The passphrase is read at startup and the environment variable is immediately
cleared.
.It Sy keyfile
Path to a file containing exactly 32 bytes of raw AES-256 key material.
.El
.El
.Sh REST API
.Nm
exposes the following REST endpoints over HTTPS:
.Bl -tag -width Ds
.It GET /v1/health
Returns
.Qq {"status":"ok"}
with HTTP 200.
No authentication required.
.It GET /v1/keys/public
Returns the Ed25519 public key as a JWK.
No authentication required.
.It POST /v1/auth/login
Authenticates a user and issues a JWT.
Body: JSON with
.Sy username ,
.Sy password ,
and optionally
.Sy totp_code .
Returns
.Sy token
and
.Sy expires_at .
.It POST /v1/auth/logout
Revokes the current JWT.
Requires a valid Bearer token.
Returns HTTP 204.
.It POST /v1/auth/renew
Renews the current JWT, revoking the old one.
Requires a valid Bearer token.
Returns
.Sy token
and
.Sy expires_at .
.It POST /v1/token/validate
Validates a submitted token and returns its claims.
.It DELETE /v1/token/{jti}
Revokes a token by JTI.
Requires admin role.
.It POST /v1/accounts
Creates a new account.
Requires admin role.
.It GET /v1/accounts
Lists all accounts (no credential fields in response).
Requires admin role.
.It GET /v1/accounts/{id}
Returns a single account.
Requires admin role.
.It PATCH /v1/accounts/{id}
Updates an account.
Requires admin role.
.It DELETE /v1/accounts/{id}
Soft-deletes an account and revokes all its tokens.
Requires admin role.
.It GET /v1/accounts/{id}/roles
Returns the role set for an account.
Requires admin role.
.It PUT /v1/accounts/{id}/roles
Replaces the role set for an account.
Requires admin role.
.It POST /v1/auth/totp/enroll
Generates a TOTP secret and returns the otpauth URI.
Requires authentication.
.It POST /v1/auth/totp/confirm
Confirms TOTP enrollment.
Requires authentication.
.It DELETE /v1/auth/totp
Removes TOTP from an account.
Requires admin role.
.It GET /v1/accounts/{id}/pgcreds
Returns the Postgres credentials for an account (decrypted).
Requires admin role.
.It PUT /v1/accounts/{id}/pgcreds
Sets Postgres credentials for an account.
Requires admin role.
.El
.Sh SIGNALS
.Bl -tag -width Ds
.It Dv SIGINT , SIGTERM
Initiate graceful shutdown.
In-flight requests are drained for up to 15 seconds before the server exits.
.El
.Sh EXIT STATUS
.Ex -std
.Sh FILES
.Bl -tag -width Ds
.It Pa /etc/mcias/mcias.conf
Default configuration file location.
.It Pa /etc/mcias/server.crt
TLS certificate (operator-supplied).
.It Pa /etc/mcias/server.key
TLS private key (operator-supplied).
.It Pa /var/lib/mcias/mcias.db
SQLite database.
.It Pa /etc/systemd/system/mcias.service
systemd service unit.
.El
.Sh SEE ALSO
.Xr mciasctl 1 ,
.Xr mciasdb 1 ,
.Xr mciasgrpcctl 1
.Sh SECURITY
.Nm
enforces TLS 1.2 as the minimum protocol version.
All JWTs are signed with Ed25519; the
.Sy alg
header is validated before any other processing to defeat algorithm confusion
attacks.
Password hashing uses Argon2id with parameters meeting or exceeding the
OWASP 2023 recommendations.
Credential fields (password hashes, TOTP secrets, Postgres passwords) are
never included in any API response.