AlmaForge Hardening Guide
Hardening is a set of choices an SRE makes before a cluster goes live and reviews after production changes. The defaults are safe for evaluation, not for production. This page walks through the choices that matter once the cluster holds real credentials, real audit data, and real production access.
Read the High Availability Guide first if you need a refresher on the cluster moving parts (Auth, Proxy, agents, state backend, cluster CA, fleet, mTLS).
Out of scope here:
- Role design and access policy - see RBAC.
- CA rotation procedure - see CA Rotation.
- On-the-wire TLS, listeners, and PROXY protocol - see Networking Guide.
- Per-backend storage hardening (encryption, IAM, network policy) - see Storage Guide.
Pre-go-live checklist
Work the list top to bottom before flipping production traffic at the cluster. Each item links to its section below.
- State, audit, and recording backends are encrypted at rest (Encryption at rest).
- Internal mTLS works end to end and
ALMA_CA_PINis captured (mTLS inside the cluster). - Bootstrap join tokens are rotated or deleted after first agent join (Secrets and tokens).
- NTP or equivalent clock sync is configured on every Auth, Proxy, and agent host. Drift is monitored (Clock sync).
- Auth and Proxy data directories are
chmod 0700and owned byroot(Filesystem hardening). - Role
maxSessionTTL,disconnectExpiredCert, andmaxConnectionsare set on production roles (Session and connection limits). - MFA is enforced at your IdP. AlmaForge inherits the IdP's MFA decision and does not enforce it independently (Identity provider).
- Daemon outbound traffic is restricted to the state, audit, recording, OIDC, ACME, and package-mirror endpoints (Outbound network).
- L4 load balancer is the only source the daemon trusts for PROXY protocol headers (Web UI hardening).
- Web UI CSP, CORS, and HSTS behavior is verified on your hostname (Web UI hardening).
Encryption at rest
AlmaForge stores cluster state, audit events, and session recordings in three separate places. Each one carries different sensitivity and each backend has its own at-rest encryption story. Pick the option that matches your compliance posture before any production data lands. See the per-backend pages under Storage Guide for setup detail.
Disk and OS layer
Start below the application. For self-hosted etcd, PostgreSQL, and Auth hosts, encrypt the host disks with LUKS, dm-crypt, or the equivalent volume encryption in your platform. Managed databases and cloud buckets still need their own at-rest settings, but disk encryption closes the lowest layer first.
Verification is platform-specific. Capture one command per storage layer in your runbook, for example:
findmnt -no SOURCE,FSTYPE,OPTIONS /var/lib/almaforgecryptsetup status <volume-name>aws dynamodb describe-table --table-name cluster-audit --query 'Table.SSEDescription.Status'gcloud storage buckets describe gs://almaforge-recordings --format='value(encryption.defaultKmsKeyName)'az storage account show --name almaforgerecordings --query encryption.keySourceCluster state backend holds roles, connectors, tokens, target manifests, and the cluster CA. Loss of the at-rest data means an operator-level breach. Options:
- etcd: encrypt the underlying disks or volumes, including snapshots. etcd does not encrypt its stored key/value data itself. See etcd's storage encryption guidance.
- PostgreSQL: use encrypted disks or your managed service's
storage encryption. Cover data files, WAL, and backups.
pgcryptois not a transparent encryption setting for the AlmaForge state backend. See PostgreSQL encryption options. - DynamoDB: server-side encryption with an AWS-owned, AWS-managed, or customer-managed key. The default is AWS-owned. See DynamoDB encryption.
- Firestore: encryption at rest with Google-managed or customer-managed (CMEK) keys.
Audit events store the security event stream. Anyone who reads the audit store sees who-did-what timestamps. Options match the audit-capable drivers:
- PostgreSQL: same options as the state backend above.
- DynamoDB: server-side encryption with an AWS-managed or customer-managed KMS key.
- Firestore: encryption at rest with Google-managed or customer-managed (CMEK) keys.
- Athena: queries S3-backed Parquet files. Encrypt the underlying S3 bucket with SSE-S3 or SSE-KMS.
Session recordings contain terminal output and timing from privileged sessions. Protect them with encryption and restricted access.
- S3 / GCS / Azure Blob: enable bucket-level SSE with a customer-managed KMS key. On GCS, use CMEK. On Azure Blob, use a customer-managed key on the storage account. Lifecycle rules move old recordings to cold storage on the same key.
If you operate a single KMS, the simplest production shape is one customer-managed key per store, with key rotation on the cadence your compliance program requires. Record the rotation blast radius in the runbook: some managed KMS rotations are transparent, while others require explicit object or table re-encryption.
mTLS inside the cluster
The cluster CA signs every internal certificate. Every connection between Auth, Proxy, and agents is mutually authenticated against that CA. Internal TLS is on by default. The work here is to make sure no piece of the chain falls back to weaker or unauthenticated paths.
The cluster CA is created when the first Auth Service replica starts. Its keys are stored in the state backend. Any user signed in on a workstation can print the trusted key pins through the Proxy:
alma auth pinsha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefThe command prints one sha256: pin for each trusted key. CA rotation
can produce multiple pins while the cluster trusts both keys. Include
every printed pin. A direct join requires a matching pin for every CA
certificate returned by the Auth Service. A partial set fails the join.
Distribute the pins to your configuration management system. Set
ALMA_CA_PIN to the absolute path of a file containing one pin per
line. The pins are checked only when ALMA_AUTH_SERVER selects a direct
Auth Service join. Without a pin, that path trusts the certificate
presented on its first connection, which opens a window for on-path
attacks during bootstrap.
A join through ALMA_PROXY validates the Proxy's TLS certificate
against the host's system trust store. Use a publicly trusted Proxy
certificate or install its signing CA in the host's trust store. This
path ignores ALMA_CA_PIN.
Auth and Proxy mTLS: both ends present a certificate and both
verify it against the cluster CA. The Proxy presents the host
certificate issued to it when it joined, stored under /var/lib/almaforge
and rotated without operator action. Auth presents a host certificate
from the same CA, so a Proxy that reaches the wrong server fails the
handshake instead of sending it a request.
A Proxy in the same process as Auth reaches it without using the network. A Proxy on another host reaches it over the multiplex listener on TCP/443. Confirm that the listener is present:
ss -tlnp | grep almadThen confirm the auth route on it answers with a certificate from the cluster CA. Name only that route, so a host not serving it closes the connection instead of answering with the web certificate:
openssl s_client -connect <auth-host>:443 -alpn alma-auth </dev/null 2>&1 \ | grep -E 'subject=|issuer='The issuer is your cluster's host CA. Anything else on that route, or a connection that closes without a certificate, means the listener is not serving Auth.
That check proves the route is served by Auth under the cluster CA rather than by plaintext or the web certificate. It does not test client authentication: Auth accepts the handshake and rejects the request afterward, so a certless client gets a TLS session and no access. Only an end-to-end Proxy or agent join tests that path.
Agent join credentials: agents authenticate with credentials
scoped to a Token resource. The static join method uses a
client_id and client_secret. Cloud and CI/CD join methods use a
verified platform identity instead of a secret. After a successful
join, the cluster issues the agent a host certificate. The agent
rotates that certificate without operator action.
Before an agent joins through ALMA_AUTH_SERVER, retrieve the CA pins
with:
alma auth pinSet ALMA_CA_PIN in the agent's env file, including for cloud-identity
joins. Agents that join through ALMA_PROXY rely on the Proxy
certificate instead, as described above.
Secrets and tokens
Scope and expire static join secrets, and rotate other credentials according to their provider's lifecycle. Treat filesystem access to the Auth host as equivalent to admin access.
Join token rotation. A static Token carries a client_id and
client_secret. Anyone with both can join an agent of the granted
service role until the token expires. Defaults:
- Issue tokens with a short
--expireswindow when bootstrapping agents (minutes to hours, not days). - Scope tokens with
--services=<list>to the smallest role set the agent needs. - Rotate the proxy-fleet token after the initial Proxy fleet has joined and stored its long-lived identity locally.
After the fleet has joined, delete the bootstrap token and verify it is gone:
alma delete token/proxy-fleetalma get token/proxy-fleetThe second command should fail with a not-found error. When a new Proxy or agent needs to join later, issue a fresh time-boxed token for that change window and delete it after the host stores its identity.
Bootstrap connector drop-in. The first administrator signs in
through a drop-in OIDCConnector (see
Linux deployment guide).
Keep the connector file-owned in almad.d, manage it through your
configuration pipeline, and deploy mapping changes to the watched directory
on each Auth host. Connector files reload without a restart. Changes to
secrets supplied through the process environment require a restart.
Cloud and CI/CD join methods (aws, ec2, azure, gcp,
github, gitlab, circleci, spacelift, kubernetes) avoid a
shared secret entirely. Use them where the platform offers a
verifiable workload identity. The static join method is the
fallback for environments without one.
Secrets in resource manifests. Never commit OAuth client
secrets, database passwords, or storage credentials to the manifest
files in /etc/almaforge/almad.d. Reference them with ${env:NAME} or
${file:/path} and resolve from the daemon environment or a mounted
file. See
Resource value expansion.
Identity provider
AlmaForge delegates authentication entirely to your IdP. Every control that lives at the IdP layer (MFA, conditional access, device posture, geofencing, account lockout) is the IdP's job, not the cluster's. Take that seriously: a cluster with no MFA is a cluster whose IdP has no MFA.
- Require MFA at the IdP for every account that can sign in to AlmaForge. AlmaForge accepts whatever assertion the IdP produces and has no independent MFA challenge.
- Audit IdP-side changes. Adding a group, changing claim mapping, or rotating an OIDC client secret at the IdP changes who can reach the cluster. Wire IdP admin events into the same SIEM that watches AlmaForge audit events.
- Rotate the OIDC client secret on the IdP and update the cluster env in the same change window.
- Keep OIDC endpoints reachable from every Auth host, including discovery, signing keys, and the token endpoint. Browser access to the IdP alone is not enough. See egress requirements.
Clock sync
Short-lived certificates depend on time. Clock drift can make valid certificates look expired, distort audit timelines, and break SSO or MFA challenges.
Use NTP, chrony, systemd-timesyncd, or your platform's equivalent on every Auth, Proxy, and agent host. Verify on systemd hosts with:
timedatectl show -p NTPSynchronized --valueyesFilesystem hardening
Daemon user. The shipped systemd unit runs almad as
root. The daemon binds privileged ports, manages on-disk identity
material, and reads root-owned config and TLS files, so it stays
under the root account rather than a dedicated service user.
The Auth data directory holds the cluster CA bootstrap state, the
local admin identity used by --auth-server direct access, and the
working files for each running role. Lock the filesystem down.
/var/lib/almaforge access as cluster-admin equivalentRead access to /var/lib/almaforge is sufficient to take over the
cluster through the --auth-server direct-access path. Audit sudo,
filesystem mounts, and host authentication events that touch this
directory. Require multi-person approval for routine access. A
break-glass procedure that any sysadmin can execute silently is not a
break-glass procedure.
Required production paths and modes:
/var/lib/almaforge(/var/lib/almaforge): ownerroot:root, mode0700./etc/almaforge: ownerroot:root, mode0700. The config root, holding the env file and any TLS material./etc/almaforge/almad.d(/etc/almaforge/almad.d/): ownerroot:root, mode0700. Drop-in manifests are read by the root daemon and must not be world- or group-readable./etc/almaforge/almad.env(/etc/almaforge/almad.env): ownerroot:root, mode0600. Holds secrets interpolated into drop-ins.- TLS key (
/etc/almaforge/tls.key): ownerroot:root, mode0600. Same rule for any etcd client key, database password file, or per-Proxy private key.
After install, confirm. On Linux with GNU stat:
stat -c '%a %U:%G %n' /var/lib/almaforge /etc/almaforge/almad.envOn macOS or BSD systems inspecting a mounted image or copied tree, use:
stat -f '%Mp%Lp %Su:%Sg %N' /var/lib/almaforge /etc/almaforge/almad.envSELinux and AppArmor. AlmaForge does not ship a custom SELinux policy or AppArmor profile in this release. Run with your distribution's default profile and confine the daemon at the filesystem level via the modes above.
Outbound network
almad initiates outbound connections to a small, known
set of endpoints. A compromised daemon with unrestricted egress is
much harder to contain than one with a firewall-enforced
allowlist. Permit outbound only to:
- The state backend endpoints (etcd, PostgreSQL, DynamoDB, Firestore).
- The audit and recording backend endpoints.
- The OIDC provider's discovery, JWKS, and token endpoints (typically
https://<issuer>/.well-known/openid-configurationand thejwks_uriandtoken_endpointit advertises). - Resource endpoints reached by database, application, and Kubernetes agents, and SSH endpoints reached by the Proxy for agentless hosts.
- APIs used by optional integrations and platform join methods you enable. See egress requirements.
- Internal NTP and DNS resolvers.
- The internal package mirror and container registry during install and upgrade windows.
- The ACME directory when
ALMA_ACME_ENABLEDis set. For Let's Encrypt, allow TCP 443 toacme-v02.api.letsencrypt.org. Keep it allowed for automatic renewal, which begins roughly two months after issuance. A cluster that usesALMA_TLS_CERTandALMA_TLS_KEYneeds no ACME egress.
Deny everything else at the host firewall or VPC egress layer.
Cloud-managed firewalls, Kubernetes NetworkPolicy, or a per-host
iptables/nftables ruleset all work. See the
Networking Guide for the inbound port matrix.
Session and connection limits
Limits are applied by role options, not by daemon configuration. Setting them on production roles bounds the blast radius of a compromised credential.
maxSessionTTL caps the lifetime of credentials issued for the
role. An unspecified role maximum defaults to 12 hours, and built-in roles can impose shorter
limits. Issued credentials cannot exceed the 30-hour hard cap. Set a maximum that matches the role's operational needs.
Use disconnectExpiredCert when active sessions must also end at
certificate expiry. See role options.
When disconnectExpiredCert is enabled, a shorter certificate
lifetime also shortens active sessions. Test long-running maintenance
workflows before tightening production roles.
maxConnections caps the number of concurrent connections a
single identity may hold. The default is unlimited. Setting it to a
small finite number (1 to 5 for human users, higher for bot
identities) prevents a stolen certificate from holding open a fleet
of sessions.
# Production role with explicit session and connection limits.
# Use this shape for human operator roles. Keep a separate break-glass or
# maintenance role with a longer TTL if your change windows require it.
apiVersion: almaforge.com/v1
kind: Role
metadata:
name: editor
spec:
options:
maxSessionTTL: 4h # Upper bound on a single interactive session.
maxConnections: 5 # Maximum concurrent connections for one identity.
allow:
serverLabels:
env: prod # Limit this role to production-labeled servers.
serverLogins:
- ubuntu # Allow only the OS login names operators need.
Verify the limits after applying:
alma get role/editor -o yaml | grep -E 'maxSessionTTL|maxConnections'Runtime revocation
Locks block a user, role, login, server, device, or access request cluster-wide at the next decision point, independent of certificate expiry:
alma create lock --target-user=alice --message="incident-2026-1234"This is the runtime complement to the role limits above. See Revoking access for the full lock runbook, including how locks propagate during a partition.
Web UI hardening
The Proxy serves the AlmaForge Web UI on the public hostname. Content-Security-Policy, secure cookies, and HSTS ship with the Web UI. Verify the headers on the public endpoint before going live, including any changes introduced by infrastructure in front of the Proxy.
Content-Security-Policy. The Proxy emits a CSP that allows
inline styles required by the bundled UI and blocks third-party
script sources. Its frame-ancestors 'none' directive prevents a
parent portal from embedding the Web UI. Open the Web UI directly
at the cluster hostname.
CSP can lock out operator workflows. frame-ancestors 'none' blocks
every parent portal from framing the Web UI. Preserve this protection
when configuring infrastructure in front of the Proxy.
CORS. Access the Web UI and its JSON API through the same cluster origin. Do not add permissive cross-origin headers at an intermediary. Application-access CORS settings are separate from Web UI policy.
HSTS. The Proxy emits a Strict-Transport-Security header with
max-age=31536000; includeSubDomains when serving on the public
hostname. Confirm the LB does not strip the header:
curl -sI https://almaforge.example.com | grep -i strictstrict-transport-security: max-age=31536000; includeSubDomainsHSTS is cached by browsers for the max-age window. With
includeSubDomains, every subdomain under the public hostname must
keep working over HTTPS. Server-side rollback cannot clear the cache
already stored by clients.
For maximum hardening, submit the public hostname to the
HSTS preload list only after the header
has been live for at least the max-age window. Preload is harder
to undo than a normal HSTS header.
Verify the Web UI headers before go-live:
curl -sI https://almaforge.example.com \ | grep -Ei 'content-security-policy|strict-transport-security|x-frame-options'curl -sI -H 'Origin: https://dashboard.example.com' https://almaforge.example.com \ | grep -i access-control-allow-originCookies. Session cookies are flagged HttpOnly, Secure, and
SameSite=Lax. None of this is configurable. Operators who put a
caching CDN in front of the Proxy must exclude Set-Cookie and any
auth-bearing path from the cache.
Public exposure. The Proxy is intentionally fronted by a single
hostname (ALMA_ADVERTISE). Do not stand up alternate hostnames
through reverse proxies that bypass ALMA_ADVERTISE. The cluster
issues short-lived certs and validates audience claims against that
hostname. Alternate hostnames break SSO redirects and break audit
attribution.
PROXY protocol trust. When an L4 load balancer fronts the
Proxy, the LB injects the real client IP via PROXY protocol so
audit events attribute connections to the originator
rather than the LB. Set ALMA_REAL_IP_FROM to the trusted LB addresses
and restrict the listener's network exposure to those sources.
ALMA_PROXY_PROTOCOL controls header presence, not which sender's
address is trusted. Incorrect trust settings can make source-IP
attribution spoofable. See the
Networking Guide for PROXY protocol setup.
Network segmentation is a hardening choice handled at the infrastructure layer. See Networking Guide for VLAN, security group, and Kubernetes NetworkPolicy patterns.
After hardening
- Run through Break-glass once on a staging cluster. A break-glass path you have not exercised is not a path.
- Schedule a tabletop break-glass exercise within 30 days of go-live.
- Add the cluster CA fingerprint and break-glass runbook URL to your incident response wiki.
- Schedule the first CA rotation drill - see CA Rotation.
- Wire
/readyzinto your LB health check and Prometheus/metricsinto your scrape config. See Cluster on Linux and Monitoring. - Alert on high-value audit events such as
LockCreate,SessionReject, and repeated login failures. See Monitoring and the Audit Reference. - Review this guide after every major upgrade, cluster migration, audit finding, new production role, and scheduled CA rotation. A PCI shop should do that at least quarterly. A SOC2 shop should include it in the annual control review.
References
- Storage Guide - per-backend at-rest encryption.
- Networking Guide - LB, PROXY protocol, listen vs advertise.
- Break-glass - emergency recovery.
- CA Rotation - scheduled cluster CA replacement.
- Revoking access - locks and runtime revocation.
- RBAC - role design.
- Audit Reference - audit event catalog.