Skip to content

NetBox DB Provisioning + App Deployment via GitOps + 1Password Operator #191

@SRF-Audio

Description

@SRF-Audio

Goals

  1. Provision NetBox database + user in the shared Postgres cluster without impacting Paperless.
  2. Keep DB admin credentials confined to the db-postgres namespace.
  3. Keep app runtime credentials confined to the app namespace (infra-netbox).
  4. Support repeatable onboarding for future apps by copying a folder and changing names.

Non-goals

  • No changes to the Bitnami Postgres Helm release.
  • No changes to Paperless role/password/database.
  • No “trust” edits to pg_hba.conf (we already recovered; now we operate normally).

Repo Layout

Create two new GitOps apps:

argocd/
  apps/
    db/
      netbox-db-provisioner.yml
    infra/
      netbox.yml

k8s/
  db-provisioning/
    netbox/
      00-onepassworditems-db-postgres.yaml
      10-netbox-db-provision-job.yaml
  infra/
    netbox/
      00-onepassworditems-infra-netbox.yaml
      10-netbox-application.yaml   (or keep Argo Application in argocd/apps/infra)

(Exact placement is flexible; the key is separation by concern: db-provisioning/* vs infra/*.)


Prereqs

  • 1Password Operator is already installed and working.
  • A 1Password vault exists for Coachlight secrets.
  • coachlight_admin exists in Postgres and has superuser (or at least createdb/createrole).

1Password Items Required (in 1Password)

Create these items in 1Password (single source of truth):

A) postgres-admin (DBA identity)

Fields:

  • username: coachlight_admin
  • password: <strong password>

B) netbox-db-credentials (app identity)

Fields:

  • username: netbox
  • password: <strong password>

C) netbox-redis-credentials (optional if Redis requires auth)

Fields:

  • password: <strong password>

D) netbox-django-secret

Fields:

  • secretKey: <strong random string>

E) netbox-superuser

Fields:

  • password: <strong password>
    (Email can stay in Helm values.)

Argo Application 1: DB Provisioning (lives with database)

File: argocd/apps/db/netbox-db-provisioner.yml

  • Target namespace: db-postgres
  • Sync wave: earlier than NetBox app (e.g. "15" if NetBox is "20")

Responsibilities

  • Create secrets in db-postgres using OnePasswordItem:

    • postgres-admin
    • netbox-db-credentials
  • Run a Job that:

    • Creates role netbox if missing
    • Creates database netbox if missing
    • Ensures ownership/grants
    • Ensures password matches 1Password value (rotation-friendly)

Manifest: k8s/db-provisioning/netbox/00-onepassworditems-db-postgres.yaml

Create Two OnePasswordItems in namespace db-postgres:

  • postgres-admin → points at 1Password item postgres-admin
  • netbox-db-credentials → points at 1Password item netbox-db-credentials

Naming contract:

  • K8s Secret name == OnePasswordItem name
  • Keys inside Secret match 1Password fields (username, password)

Manifest: k8s/db-provisioning/netbox/10-netbox-db-provision-job.yaml

Job requirements

  • Namespace: db-postgres

  • Uses a container with psql available (Bitnami postgres image is fine).

  • Reads env from Secrets:

    • POSTGRES_ADMIN_USER / POSTGRES_ADMIN_PASSWORD from postgres-admin secret keys username/password
    • NETBOX_DB_USER / NETBOX_DB_PASSWORD from netbox-db-credentials secret keys username/password
  • Connects to the Postgres service:

    • host: postgres-postgresql.db-postgres.svc.cluster.local
    • port: 5432
    • db: postgres (maintenance DB)
  • Runs idempotent SQL.

SQL contract (idempotent)

Run via psql with ON_ERROR_STOP=1.

Use one transaction-safe sequence:

  1. Create role if missing (or ensure it can login)
  2. Set role password to match NETBOX_DB_PASSWORD (rotate-friendly)
  3. Create DB if missing with owner netbox
  4. Ensure privileges

Implementation detail: use DO $$ ... $$; blocks for “IF NOT EXISTS” and always run ALTER ROLE ... PASSWORD.

Example SQL logic (Copilot should implement exactly this behavior):

  • DO create role only if not exists
  • ALTER ROLE netbox WITH LOGIN PASSWORD '<from secret>';
  • DO create database only if not exists
  • ALTER DATABASE netbox OWNER TO netbox; (safe even if already)
  • GRANT CONNECT ON DATABASE netbox TO netbox;

Argo hook behavior

Choose ONE:

Option 1 (recommended): normal Job, idempotent

  • Pros: Argo doesn’t need hook semantics; reruns only if changed
  • Cons: Job object persists unless TTL used

Option 2: Argo PreSync hook Job

  • Add annotations:

    • argocd.argoproj.io/hook: PreSync
    • argocd.argoproj.io/hook-delete-policy: HookSucceeded
  • Pros: runs automatically before sync and cleans up

  • Cons: reruns more often; still fine because SQL is idempotent

Pick Option 2 if you want strict ordering every sync; otherwise Option 1.

TTL

Set spec.ttlSecondsAfterFinished (e.g. 300–3600) to keep namespace clean.


Argo Application 2: NetBox (lives with the app)

File: argocd/apps/infra/netbox.yml

  • Destination namespace: infra-netbox

  • Sync wave: "20" (after db provisioner)

  • Contains:

    • OnePasswordItems in infra-netbox for runtime secrets
    • NetBox Helm chart Application (or Helm values in this Argo app directly)

Manifest: k8s/infra/netbox/00-onepassworditems-infra-netbox.yaml

Create OnePasswordItems (namespace infra-netbox) pointing at the same 1Password items:

  • netbox-db-credentials (same itemPath as db-postgres version)
  • netbox-redis-credentials
  • netbox-django-secret
  • netbox-superuser

This is deliberate duplication due to namespace boundaries.


NetBox Helm values wiring (in your Argo Application)

Update your existing NetBox Application values to use existingSecret* everywhere possible:

External DB (already good)

  • externalDatabase.existingSecretName: netbox-db-credentials
  • externalDatabase.existingSecretKey: password
  • externalDatabase.username: netbox (or omit if chart reads from secret; follow chart schema)

Redis

  • externalRedis.existingSecretName: netbox-redis-credentials
  • externalRedis.existingSecretKey: password

Django secret key + superuser password

  • Configure the chart to source these from Kubernetes Secrets created by OnePasswordItem
  • Use the chart’s supported existingSecret / secretKey references (Copilot must read chart values/schema and wire correctly; no guessing).

Ordering / Sync Waves

  • DB provisioner app: sync-wave "15"

  • NetBox app: sync-wave "20"

  • Within each app:

    • OnePasswordItems apply before Jobs/Helm resources (either by file naming 00- and 10-, or by Argo wave annotations if needed)

Security / Blast Radius Rules

  1. DB admin Secret (postgres-admin) exists only in db-postgres.

  2. App namespaces never receive DB admin creds.

  3. DB provision jobs use least exposure:

    • only connect to Postgres service within cluster
    • no broad RBAC beyond creating Jobs/reading Secrets in their namespace
  4. App DB user (netbox) is not superuser.


Rotation Workflow

  1. Rotate password in 1Password item netbox-db-credentials.

  2. Both namespace Secrets update automatically via 1Password operator.

  3. Re-sync Argo:

    • DB provision Job runs and executes ALTER ROLE netbox PASSWORD ...
    • NetBox deployment restarts if needed (depends on chart; ideally it watches secret or you trigger a rollout)

Do the same for postgres-admin only when needed.


Acceptance Criteria

  1. infra-netbox namespace contains K8s Secrets:

    • netbox-db-credentials
    • netbox-redis-credentials
    • netbox-django-secret
    • netbox-superuser
  2. db-postgres namespace contains:

    • postgres-admin Secret
    • netbox-db-credentials Secret
    • netbox-db-provision Job succeeded (or hook ran successfully)
  3. In Postgres:

    • database netbox exists
    • role netbox exists and can login
    • owner/grants correct
  4. Paperless still works:

    • PGPASSWORD="$(cat "$POSTGRES_PASSWORD_FILE")" psql -U paperless -d paperless -c "select 1;" returns 1
  5. NetBox pods start successfully and connect to Postgres and Redis.


Implementation Notes for Copilot

  • Do not invent NetBox chart secret field names. Read chart docs/values and wire secrets correctly.
  • Keep SQL idempotent and safe. Never drop/alter Paperless DB/role.
  • Ensure the Job fails fast with -v ON_ERROR_STOP=1.
  • Use args/command that are robust and don’t echo secrets.
  • Prefer psql variables or env injection; avoid printing passwords.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions