feat: onboard zep + LinkedIn secrets to canonical backup layer #75

Merged
forgejo_admin merged 1 commit from 74-feat-onboard-zep-linkedin-secrets-to-can into main 2026-03-15 02:18:50 +00:00

Summary

  • Add 6 GPG-encrypted secrets to Salt pillar (zep_api_key + 5 LinkedIn OAuth credentials)
  • Add registry metadata for all 6 in secrets_registry.sls
  • No Terraform changes — app secrets (SOPS path), not platform secrets

Changes

  • salt/pillar/secrets/platform.sls — 6 new GPG-encrypted secret blocks (zep_api_key, linkedin_access_token, linkedin_refresh_token, linkedin_client_id, linkedin_client_secret, linkedin_person_id)
  • salt/pillar/secrets_registry.sls — 6 new metadata entries under platform: section with origin, provider, rotation_days, and backup location

Test Plan

  • CI tofu plan shows zero diff (no Terraform variables reference these secrets)
  • No changes to Makefile, variables.tf, or main.tf
  • GPG blocks are well-formed (BEGIN/END markers, valid base64)
  • Registry entries have all required fields (origin, description, created, rotation_days, notes)

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed (values are GPG-encrypted, not plaintext)
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #74
  • plan-pal-e-platform — Platform Hardening (secrets hygiene)
  • sop-secrets-management — decision gate updated this session to prevent platform/app path confusion
## Summary - Add 6 GPG-encrypted secrets to Salt pillar (zep_api_key + 5 LinkedIn OAuth credentials) - Add registry metadata for all 6 in `secrets_registry.sls` - No Terraform changes — app secrets (SOPS path), not platform secrets ## Changes - `salt/pillar/secrets/platform.sls` — 6 new GPG-encrypted secret blocks (zep_api_key, linkedin_access_token, linkedin_refresh_token, linkedin_client_id, linkedin_client_secret, linkedin_person_id) - `salt/pillar/secrets_registry.sls` — 6 new metadata entries under `platform:` section with origin, provider, rotation_days, and backup location ## Test Plan - [ ] CI `tofu plan` shows zero diff (no Terraform variables reference these secrets) - [ ] No changes to Makefile, variables.tf, or main.tf - [ ] GPG blocks are well-formed (BEGIN/END markers, valid base64) - [ ] Registry entries have all required fields (origin, description, created, rotation_days, notes) ## Review Checklist - [ ] Passed automated review-fix loop - [ ] No secrets committed (values are GPG-encrypted, not plaintext) - [ ] No unnecessary file changes - [ ] Commit messages are descriptive ## Related - Closes #74 - `plan-pal-e-platform` — Platform Hardening (secrets hygiene) - `sop-secrets-management` — decision gate updated this session to prevent platform/app path confusion
feat: onboard zep + LinkedIn secrets to canonical backup layer
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline was successful
ci/woodpecker/pull_request_closed/woodpecker Pipeline was successful
72f9bb23cd
Add 6 GPG-encrypted secrets (zep_api_key + 5 LinkedIn OAuth) to Salt
pillar and registry metadata. These are app secrets following the SOPS
path — canonical backup layer only, no Terraform variables.

Closes #74

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Tofu Plan Output

tailscale_acl.this: Refreshing state... [id=acl]
helm_release.nvidia_device_plugin: Refreshing state... [id=nvidia-device-plugin]
data.kubernetes_namespace_v1.tofu_state: Reading...
kubernetes_namespace_v1.minio: Refreshing state... [id=minio]
kubernetes_namespace_v1.cnpg_system: Refreshing state... [id=cnpg-system]
kubernetes_namespace_v1.tailscale: Refreshing state... [id=tailscale]
data.kubernetes_namespace_v1.pal_e_docs: Reading...
kubernetes_namespace_v1.woodpecker: Refreshing state... [id=woodpecker]
kubernetes_namespace_v1.forgejo: Refreshing state... [id=forgejo]
kubernetes_namespace_v1.ollama: Refreshing state... [id=ollama]
data.kubernetes_namespace_v1.tofu_state: Read complete after 0s [id=tofu-state]
kubernetes_namespace_v1.postgres: Refreshing state... [id=postgres]
data.kubernetes_namespace_v1.pal_e_docs: Read complete after 0s [id=pal-e-docs]
kubernetes_namespace_v1.harbor: Refreshing state... [id=harbor]
kubernetes_namespace_v1.keycloak: Refreshing state... [id=keycloak]
kubernetes_secret_v1.paledocs_db_url: Refreshing state... [id=pal-e-docs/paledocs-db-url]
kubernetes_secret_v1.woodpecker_db_credentials: Refreshing state... [id=woodpecker/woodpecker-db-credentials]
kubernetes_role_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup]
kubernetes_service_account_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup]
kubernetes_namespace_v1.monitoring: Refreshing state... [id=monitoring]
helm_release.tailscale_operator: Refreshing state... [id=tailscale-operator]
helm_release.cnpg: Refreshing state... [id=cnpg]
helm_release.forgejo: Refreshing state... [id=forgejo]
kubernetes_secret_v1.keycloak_admin: Refreshing state... [id=keycloak/keycloak-admin]
kubernetes_service_v1.keycloak: Refreshing state... [id=keycloak/keycloak]
kubernetes_persistent_volume_claim_v1.keycloak_data: Refreshing state... [id=keycloak/keycloak-data]
kubernetes_secret_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter]
helm_release.kube_prometheus_stack: Refreshing state... [id=kube-prometheus-stack]
kubernetes_service_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter]
helm_release.loki_stack: Refreshing state... [id=loki-stack]
kubernetes_config_map_v1.uptime_dashboard: Refreshing state... [id=monitoring/uptime-dashboard]
kubernetes_role_binding_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup]
kubernetes_deployment_v1.keycloak: Refreshing state... [id=keycloak/keycloak]
helm_release.ollama: Refreshing state... [id=ollama]
kubernetes_ingress_v1.keycloak_funnel: Refreshing state... [id=keycloak/keycloak-funnel]
kubernetes_ingress_v1.forgejo_funnel: Refreshing state... [id=forgejo/forgejo-funnel]
kubernetes_ingress_v1.grafana_funnel: Refreshing state... [id=monitoring/grafana-funnel]
helm_release.harbor: Refreshing state... [id=harbor]
kubernetes_config_map_v1.pal_e_docs_dashboard: Refreshing state... [id=monitoring/pal-e-docs-dashboard]
kubernetes_config_map_v1.dora_dashboard: Refreshing state... [id=monitoring/dora-dashboard]
helm_release.blackbox_exporter: Refreshing state... [id=blackbox-exporter]
kubernetes_deployment_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter]
kubernetes_ingress_v1.alertmanager_funnel: Refreshing state... [id=monitoring/alertmanager-funnel]
helm_release.minio: Refreshing state... [id=minio]
kubernetes_manifest.blackbox_alerts: Refreshing state...
kubernetes_manifest.dora_exporter_service_monitor: Refreshing state...
kubernetes_config_map_v1.grafana_loki_datasource: Refreshing state... [id=monitoring/grafana-loki-datasource]
kubernetes_ingress_v1.harbor_funnel: Refreshing state... [id=harbor/harbor-funnel]
minio_iam_policy.tf_backup: Refreshing state... [id=tf-backup]
minio_iam_user.cnpg: Refreshing state... [id=cnpg]
minio_iam_user.tf_backup: Refreshing state... [id=tf-backup]
minio_s3_bucket.postgres_wal: Refreshing state... [id=postgres-wal]
minio_s3_bucket.assets: Refreshing state... [id=assets]
kubernetes_ingress_v1.minio_api_funnel: Refreshing state... [id=minio/minio-api-funnel]
minio_iam_policy.cnpg_wal: Refreshing state... [id=cnpg-wal]
minio_s3_bucket.tf_state_backups: Refreshing state... [id=tf-state-backups]
kubernetes_ingress_v1.minio_funnel: Refreshing state... [id=minio/minio-funnel]
minio_iam_user_policy_attachment.cnpg: Refreshing state... [id=cnpg-20260302210642491000000001]
minio_iam_user_policy_attachment.tf_backup: Refreshing state... [id=tf-backup-20260314163610110100000001]
kubernetes_secret_v1.woodpecker_cnpg_s3_creds: Refreshing state... [id=woodpecker/cnpg-s3-creds]
kubernetes_secret_v1.tf_backup_s3_creds: Refreshing state... [id=tofu-state/tf-backup-s3-creds]
kubernetes_secret_v1.cnpg_s3_creds: Refreshing state... [id=postgres/cnpg-s3-creds]
kubernetes_cron_job_v1.tf_state_backup: Refreshing state... [id=tofu-state/tf-state-backup]
kubernetes_cron_job_v1.cnpg_backup_verify: Refreshing state... [id=postgres/cnpg-backup-verify]
kubernetes_manifest.woodpecker_postgres: Refreshing state...
helm_release.woodpecker: Refreshing state... [id=woodpecker]
kubernetes_ingress_v1.woodpecker_funnel: Refreshing state... [id=woodpecker/woodpecker-funnel]

No changes. Your infrastructure matches the configuration.

OpenTofu has compared your real infrastructure against your configuration and
found no differences, so no changes are needed.
## Tofu Plan Output ``` tailscale_acl.this: Refreshing state... [id=acl] helm_release.nvidia_device_plugin: Refreshing state... [id=nvidia-device-plugin] data.kubernetes_namespace_v1.tofu_state: Reading... kubernetes_namespace_v1.minio: Refreshing state... [id=minio] kubernetes_namespace_v1.cnpg_system: Refreshing state... [id=cnpg-system] kubernetes_namespace_v1.tailscale: Refreshing state... [id=tailscale] data.kubernetes_namespace_v1.pal_e_docs: Reading... kubernetes_namespace_v1.woodpecker: Refreshing state... [id=woodpecker] kubernetes_namespace_v1.forgejo: Refreshing state... [id=forgejo] kubernetes_namespace_v1.ollama: Refreshing state... [id=ollama] data.kubernetes_namespace_v1.tofu_state: Read complete after 0s [id=tofu-state] kubernetes_namespace_v1.postgres: Refreshing state... [id=postgres] data.kubernetes_namespace_v1.pal_e_docs: Read complete after 0s [id=pal-e-docs] kubernetes_namespace_v1.harbor: Refreshing state... [id=harbor] kubernetes_namespace_v1.keycloak: Refreshing state... [id=keycloak] kubernetes_secret_v1.paledocs_db_url: Refreshing state... [id=pal-e-docs/paledocs-db-url] kubernetes_secret_v1.woodpecker_db_credentials: Refreshing state... [id=woodpecker/woodpecker-db-credentials] kubernetes_role_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup] kubernetes_service_account_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup] kubernetes_namespace_v1.monitoring: Refreshing state... [id=monitoring] helm_release.tailscale_operator: Refreshing state... [id=tailscale-operator] helm_release.cnpg: Refreshing state... [id=cnpg] helm_release.forgejo: Refreshing state... [id=forgejo] kubernetes_secret_v1.keycloak_admin: Refreshing state... [id=keycloak/keycloak-admin] kubernetes_service_v1.keycloak: Refreshing state... [id=keycloak/keycloak] kubernetes_persistent_volume_claim_v1.keycloak_data: Refreshing state... [id=keycloak/keycloak-data] kubernetes_secret_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter] helm_release.kube_prometheus_stack: Refreshing state... [id=kube-prometheus-stack] kubernetes_service_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter] helm_release.loki_stack: Refreshing state... [id=loki-stack] kubernetes_config_map_v1.uptime_dashboard: Refreshing state... [id=monitoring/uptime-dashboard] kubernetes_role_binding_v1.tf_backup: Refreshing state... [id=tofu-state/tf-state-backup] kubernetes_deployment_v1.keycloak: Refreshing state... [id=keycloak/keycloak] helm_release.ollama: Refreshing state... [id=ollama] kubernetes_ingress_v1.keycloak_funnel: Refreshing state... [id=keycloak/keycloak-funnel] kubernetes_ingress_v1.forgejo_funnel: Refreshing state... [id=forgejo/forgejo-funnel] kubernetes_ingress_v1.grafana_funnel: Refreshing state... [id=monitoring/grafana-funnel] helm_release.harbor: Refreshing state... [id=harbor] kubernetes_config_map_v1.pal_e_docs_dashboard: Refreshing state... [id=monitoring/pal-e-docs-dashboard] kubernetes_config_map_v1.dora_dashboard: Refreshing state... [id=monitoring/dora-dashboard] helm_release.blackbox_exporter: Refreshing state... [id=blackbox-exporter] kubernetes_deployment_v1.dora_exporter: Refreshing state... [id=monitoring/dora-exporter] kubernetes_ingress_v1.alertmanager_funnel: Refreshing state... [id=monitoring/alertmanager-funnel] helm_release.minio: Refreshing state... [id=minio] kubernetes_manifest.blackbox_alerts: Refreshing state... kubernetes_manifest.dora_exporter_service_monitor: Refreshing state... kubernetes_config_map_v1.grafana_loki_datasource: Refreshing state... [id=monitoring/grafana-loki-datasource] kubernetes_ingress_v1.harbor_funnel: Refreshing state... [id=harbor/harbor-funnel] minio_iam_policy.tf_backup: Refreshing state... [id=tf-backup] minio_iam_user.cnpg: Refreshing state... [id=cnpg] minio_iam_user.tf_backup: Refreshing state... [id=tf-backup] minio_s3_bucket.postgres_wal: Refreshing state... [id=postgres-wal] minio_s3_bucket.assets: Refreshing state... [id=assets] kubernetes_ingress_v1.minio_api_funnel: Refreshing state... [id=minio/minio-api-funnel] minio_iam_policy.cnpg_wal: Refreshing state... [id=cnpg-wal] minio_s3_bucket.tf_state_backups: Refreshing state... [id=tf-state-backups] kubernetes_ingress_v1.minio_funnel: Refreshing state... [id=minio/minio-funnel] minio_iam_user_policy_attachment.cnpg: Refreshing state... [id=cnpg-20260302210642491000000001] minio_iam_user_policy_attachment.tf_backup: Refreshing state... [id=tf-backup-20260314163610110100000001] kubernetes_secret_v1.woodpecker_cnpg_s3_creds: Refreshing state... [id=woodpecker/cnpg-s3-creds] kubernetes_secret_v1.tf_backup_s3_creds: Refreshing state... [id=tofu-state/tf-backup-s3-creds] kubernetes_secret_v1.cnpg_s3_creds: Refreshing state... [id=postgres/cnpg-s3-creds] kubernetes_cron_job_v1.tf_state_backup: Refreshing state... [id=tofu-state/tf-state-backup] kubernetes_cron_job_v1.cnpg_backup_verify: Refreshing state... [id=postgres/cnpg-backup-verify] kubernetes_manifest.woodpecker_postgres: Refreshing state... helm_release.woodpecker: Refreshing state... [id=woodpecker] kubernetes_ingress_v1.woodpecker_funnel: Refreshing state... [id=woodpecker/woodpecker-funnel] No changes. Your infrastructure matches the configuration. OpenTofu has compared your real infrastructure against your configuration and found no differences, so no changes are needed. ```
Author
Owner

PR #75 Review

DOMAIN REVIEW

Secrets handling -- GOOD. All 6 new secrets are GPG-encrypted with the correct Salt Master key (81A03D1CF874DC90). Every PGP block has proper -----BEGIN PGP MESSAGE----- / -----END PGP MESSAGE----- delimiters and valid base64 content. No plaintext secret values are committed.

Salt pillar structure -- GOOD. All 6 secrets are correctly placed under secrets:platform: in platform.sls, consistent with the existing pattern. The YAML structure uses | block scalars for multi-line GPG content.

Registry metadata -- GOOD. All 6 entries in secrets_registry.sls include the required fields: origin, description, created, rotation_days, and notes. The provider field is included where applicable (external secrets). Origin types are correctly categorized:

  • zep_api_key: external (correct -- third-party API key)
  • linkedin_access_token: external (correct)
  • linkedin_refresh_token: external (correct)
  • linkedin_client_id: external (correct)
  • linkedin_client_secret: external (correct)
  • linkedin_person_id: config (correct -- static identifier, not a secret per se)

Rotation days -- GOOD. LinkedIn access token at 60 days and refresh token at 365 days match LinkedIn OAuth2 documented expiry windows. Static credentials (client_id, client_secret, person_id, zep_api_key) correctly set to 0.

Terraform isolation -- GOOD. Confirmed: no changes to terraform/variables.tf, terraform/main.tf, or Makefile. These are app-layer secrets (Zep for MiroFish, LinkedIn for Penny's scheduler) that belong in the Salt pillar backup layer but NOT in TF_SECRET_VARS. The PR correctly identifies these as "app secrets (SOPS path), not platform secrets."

Registry ordering -- GOOD. New entries are inserted before the services: section boundary, maintaining the platform: grouping.

BLOCKERS

None.

NITS

  1. LinkedIn comment placement. The inline comment block in platform.sls (# LinkedIn OAuth credentials...) appears only above linkedin_access_token. Consider whether zep_api_key would also benefit from a brief inline comment for consistency with the LinkedIn block (e.g., # Zep Cloud API key (mirofish agent memory)). Minor -- the registry has this info.

  2. Pre-existing: woodpecker_db_password and woodpecker_agent_secret missing from TF_SECRET_VARS. These variables exist in variables.tf but are absent from the Makefile's TF_SECRET_VARS list, meaning make tofu-secrets does not render them. This is NOT introduced by this PR but is worth noting as a pre-existing gap (likely these are passed via -var flags or another mechanism).

SOP COMPLIANCE

  • Branch named after issue (74-feat-onboard-zep-linkedin-secrets-to-can references issue #74)
  • PR body follows template: Summary, Changes, Test Plan, Related sections all present
  • Related section references plan-pal-e-platform and sop-secrets-management
  • No secrets committed (all values are GPG-encrypted, not plaintext)
  • No unnecessary file changes (exactly 2 files, both in scope)
  • Commit messages are descriptive (PR title is clear)
  • tofu plan output section: Present as a test plan checkbox ("CI tofu plan shows zero diff") rather than actual pasted output. Acceptable since this PR has zero Terraform changes -- there is nothing to plan.
  • tofu fmt / tofu validate: N/A -- no Terraform files changed
  • Closes #74 present in Related section

PROCESS OBSERVATIONS

  • Low change failure risk. Pure additive change to Salt pillar and metadata registry. No Terraform state impact, no k8s resource changes, no deployment pipeline effects.
  • Good decision gate discipline. The PR body notes that sop-secrets-management was consulted for the platform/app path decision, which is the correct SOP for this type of work.
  • Backup locations documented. Every new registry entry includes a notes field pointing to the local backup path (~/secrets/mirofish/..., ~/secrets/linkedin/...), consistent with the secrets management SOP.

VERDICT: APPROVED

## PR #75 Review ### DOMAIN REVIEW **Secrets handling -- GOOD.** All 6 new secrets are GPG-encrypted with the correct Salt Master key (`81A03D1CF874DC90`). Every PGP block has proper `-----BEGIN PGP MESSAGE-----` / `-----END PGP MESSAGE-----` delimiters and valid base64 content. No plaintext secret values are committed. **Salt pillar structure -- GOOD.** All 6 secrets are correctly placed under `secrets:platform:` in `platform.sls`, consistent with the existing pattern. The YAML structure uses `|` block scalars for multi-line GPG content. **Registry metadata -- GOOD.** All 6 entries in `secrets_registry.sls` include the required fields: `origin`, `description`, `created`, `rotation_days`, and `notes`. The `provider` field is included where applicable (external secrets). Origin types are correctly categorized: - `zep_api_key`: `external` (correct -- third-party API key) - `linkedin_access_token`: `external` (correct) - `linkedin_refresh_token`: `external` (correct) - `linkedin_client_id`: `external` (correct) - `linkedin_client_secret`: `external` (correct) - `linkedin_person_id`: `config` (correct -- static identifier, not a secret per se) **Rotation days -- GOOD.** LinkedIn access token at 60 days and refresh token at 365 days match LinkedIn OAuth2 documented expiry windows. Static credentials (client_id, client_secret, person_id, zep_api_key) correctly set to 0. **Terraform isolation -- GOOD.** Confirmed: no changes to `terraform/variables.tf`, `terraform/main.tf`, or `Makefile`. These are app-layer secrets (Zep for MiroFish, LinkedIn for Penny's scheduler) that belong in the Salt pillar backup layer but NOT in `TF_SECRET_VARS`. The PR correctly identifies these as "app secrets (SOPS path), not platform secrets." **Registry ordering -- GOOD.** New entries are inserted before the `services:` section boundary, maintaining the `platform:` grouping. ### BLOCKERS None. ### NITS 1. **LinkedIn comment placement.** The inline comment block in `platform.sls` (`# LinkedIn OAuth credentials...`) appears only above `linkedin_access_token`. Consider whether `zep_api_key` would also benefit from a brief inline comment for consistency with the LinkedIn block (e.g., `# Zep Cloud API key (mirofish agent memory)`). Minor -- the registry has this info. 2. **Pre-existing: `woodpecker_db_password` and `woodpecker_agent_secret` missing from `TF_SECRET_VARS`.** These variables exist in `variables.tf` but are absent from the Makefile's `TF_SECRET_VARS` list, meaning `make tofu-secrets` does not render them. This is NOT introduced by this PR but is worth noting as a pre-existing gap (likely these are passed via `-var` flags or another mechanism). ### SOP COMPLIANCE - [x] Branch named after issue (`74-feat-onboard-zep-linkedin-secrets-to-can` references issue #74) - [x] PR body follows template: Summary, Changes, Test Plan, Related sections all present - [x] Related section references `plan-pal-e-platform` and `sop-secrets-management` - [x] No secrets committed (all values are GPG-encrypted, not plaintext) - [x] No unnecessary file changes (exactly 2 files, both in scope) - [x] Commit messages are descriptive (PR title is clear) - [ ] `tofu plan` output section: Present as a test plan checkbox ("CI tofu plan shows zero diff") rather than actual pasted output. Acceptable since this PR has zero Terraform changes -- there is nothing to plan. - [x] `tofu fmt` / `tofu validate`: N/A -- no Terraform files changed - [x] `Closes #74` present in Related section ### PROCESS OBSERVATIONS - **Low change failure risk.** Pure additive change to Salt pillar and metadata registry. No Terraform state impact, no k8s resource changes, no deployment pipeline effects. - **Good decision gate discipline.** The PR body notes that `sop-secrets-management` was consulted for the platform/app path decision, which is the correct SOP for this type of work. - **Backup locations documented.** Every new registry entry includes a `notes` field pointing to the local backup path (`~/secrets/mirofish/...`, `~/secrets/linkedin/...`), consistent with the secrets management SOP. ### VERDICT: APPROVED
forgejo_admin deleted branch 74-feat-onboard-zep-linkedin-secrets-to-can 2026-03-15 02:18:50 +00:00
Sign in to join this conversation.
No description provided.