Skip to content

Ledger public and private authentication

Public URL: https://idc.brownrook.com/ledger

Corporate-LAN URL: https://ledger.brownrook.net/ledger

The existing BrownRook NGINX proxy terminates TLS for idc.brownrook.com and forwards requests to the K3s ingress endpoint. The Kubernetes HTTP ingress routes only /ledger to ledger-oauth2-proxy, which authenticates the request before proxying it to ledger-web.

Entra ID application

Create a single-tenant Microsoft Entra app registration for Ledger.

Configure the Web redirect URI:

https://idc.brownrook.com/ledger/oauth2/callback

Recommended access control:

  • Keep the app single-tenant.
  • In the Enterprise Application, require assignment and assign only approved Ledger users/groups.
  • Require MFA/Conditional Access where licensed and appropriate.

Create the Kubernetes OAuth secret from oauth2-proxy-secret.example.yaml. Never commit the populated secret.

Generate the cookie secret with:

openssl rand -base64 32 | tr -- '+/' '-_'

PostgreSQL

Specify the deployment password in the Git-ignored .env.k3s:

POSTGRES_DB=home_budget
POSTGRES_USER=home_budget
POSTGRES_PASSWORD='your-chosen-password'
POSTGRES_HOST=postgres.brownrook.net
POSTGRES_PORT=5432
DATABASE_URL='postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}'
HOME_BUDGET_PG_DSN='${DATABASE_URL}'

Deploy the existing Argo CD application with:

make deploy-k3s

This validates .env.k3s, checks Argo CD access, synchronizes postgres-secret and the database password, applies the OCR cache setting to receipt-runtime-config, then starts the Argo CD deployment. Set HOME_BUDGET_OCR_CACHE in .env.k3s to a directory below /data; see the cache configuration guide. If the password already matches, it is reused without password changes or client restarts. When you specify a different password, the command updates the existing database and its clients before deploying. The job-suspension requirements below apply only when changing the password. Other existing Secrets, including the PV's SMB credentials, remain in Kubernetes. The Argo application and its namespace must already exist; use the initial provisioning commands below for a new installation.

The command uses the existing Argo CD login described in the operations runbook. Argo CD still deploys its configured Git revision; it does not deploy uncommitted local files. For automated GitOps deployments, the provisioned Secret supplies the password; Argo CD does not read your Mac's .env.k3s.

Set POSTGRES_DB, POSTGRES_USER, and your chosen POSTGRES_PASSWORD in .env.k3s. Quote values containing spaces or #. The deployment reader expands ${POSTGRES_*} references in the two URL entries, URL-encoding username, password, and database name. HOME_BUDGET_PG_DSN can reference ${DATABASE_URL}. Other settings, including passwords, are literal; shell commands are never executed. Keep the URL templates single-quoted as shown and use the deployment command to resolve them, rather than sourcing them in a shell. The StatefulSet uses the K3s local-path storage class and requests 10 GiB.

For a new deployment, create the namespace and provision its Secret before applying the application manifests:

make postgres-config-check
kubectl --context brownrook-k3s1 apply -f k8s/namespace.yaml
make postgres-config-apply
make k3s-config-apply

The command reads KUBE_CONTEXT, KUBE_NAMESPACE, DATABASE_URL, and HOME_BUDGET_PG_DSN from .env.k3s. URLs may be templates as above or complete PostgreSQL URLs with URL-encoded credentials. Their credentials and database must match the POSTGRES_* values; no hostname is substituted by the script. It sends only PostgreSQL settings to postgres-secret, without displaying them or writing a populated manifest to disk. k8s/postgres-secret.example.yaml remains available as a manual template. Changing only a connection URL updates the Secret and restarts its Deployment clients without changing the database password.

For the existing database, edit POSTGRES_PASSWORD in .env.k3s, suspend any database-consuming CronJobs, let their active jobs finish, then run:

make postgres-config-check
make postgres-password-rotate

Rotation changes the database role password, updates the Secret, verifies a new authenticated connection, and asks Argo CD to restart Deployments that consume postgres-secret. The helpers verify that Argo CD manages the affected Deployments before changing configuration. ARGO_APP and ARGO_SERVER select the same application for deployment sync and restart actions. It uses a temporary loopback port-forward and requires the project's database dependencies (.[db]). It refuses to rename the database/user or run while database CronJobs are unsuspended or Jobs are unfinished. The existing database role must be able to read its password verifier from pg_authid, as the current bootstrap role can. If a rejected Secret update is confirmed by reading it back, the command restores the old database password. A connectivity loss during rotation may require checking database/Secret consistency before retrying.

Check client rollout status before restoring the intended CronJob schedules. Existing database connections may briefly fail while clients reload credentials. PostgreSQL itself is not restarted. A Secret-only update cannot change the password in an initialized database; postgres-config-apply refuses a password change and directs you to the rotation command instead. See PostgreSQL's ALTER ROLE documentation and Kubernetes' Secret environment variable behavior.

The in-cluster database host is:

postgres.home-budget.svc.cluster.local:5432

The PostgreSQL Service is ClusterIP. postgres-ingressroutetcp.yaml also binds it to Traefik's dedicated postgres TCP entry point for private administrative access; that entry point must not be published to the public internet.

Apply

Keep deployment settings in the Git-ignored .env.k3s, using .env.k3s.example as the checklist. .env.dev is only for the local Mac. The Entra tenant and app credentials can be shared: map local ENTRA_CLIENT_ID and ENTRA_CLIENT_SECRET to OAUTH2_PROXY_CLIENT_ID and OAUTH2_PROXY_CLIENT_SECRET. Use the existing cluster cookie secret rather than the Mac's OAUTH2_COOKIE_SECRET.

The deployment also needs PostgreSQL credentials, SMB credentials, and GHCR pull token. Product enrichment uses BRAVE_SEARCH_API_KEY in brave-search-api and OPENAI_API_KEY in openai-api. Use the in-cluster PostgreSQL hostname above when constructing database URLs; Mac receipt paths, test broker URLs, and LEDGER_PROXY_SECRET are local-only settings for the current manifests. TLS secrets remain managed separately.

Kubernetes does not load .env.k3s automatically. Transfer the values into the corresponding Secrets before deployment. For RabbitMQ, the receipt processing runbook includes a command that creates its Secret from .env.k3s.

Create the populated postgres-secret, ledger-oauth2-proxy-secret, pve-smb-credentials, and ghcr-secret resources outside Git. Configure the Argo CD application's source path for the desired overlay, then deploy through Argo CD:

make deploy-k3s

ledger-web serves the receipt-first application on port 8080. OAuth2 Proxy passes the authenticated Entra identity headers to Ledger. Kubernetes calls /ledger/health and /ledger/ready directly for probes. Public /ledger requests still pass through OAuth2 Proxy.

Private corporate route

The optional private overlays add https://ledger.brownrook.net/ledger on the existing Traefik websecure entry point at 192.168.2.240. They run a separate OAuth2 Proxy so the private hostname has its own callback URL and secure cookie while retaining the same Entra tenant and access policy.

Add this Web redirect URI to the Entra application:

https://ledger.brownrook.net/ledger/oauth2/callback

The private hostname must resolve only through corporate DNS. The public reverse proxy must forward only explicitly configured public hosts and must not forward the private .net hostname. Create the k3ingress-tls secret outside Git before using deploy/private-lan or deploy/rabbitmq-private. See the network access guide for the required DNS, TLS, and firewall boundaries.