Merge pull request 'Migrate remaining secrets to ExternalSecrets' (#67) from feature/migrate-remaining-secrets into main

This commit is contained in:
Erich Blume 2026-01-28 20:41:45 -08:00
commit a93f2a77e1
30 changed files with 328 additions and 200 deletions

View file

@ -0,0 +1,39 @@
# ExternalSecret for ArgoCD Forge SSH credentials
#
# Replaces the manual op inject workflow from repo-forge-secret.yaml.tpl
#
# 1Password item: "argocd-forge-ssh-key" in blumeops vault (Secure Note)
# Field: "private-key-openssh"
#
# Note: Uses a separate Secure Note item because 1Password Connect doesn't
# support the ?ssh-format=openssh query parameter that the CLI uses.
#
# This is a repo-creds (credential template) that matches ALL repos under eblume/
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: repo-creds-forge
namespace: argocd
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: repo-creds-forge
creationPolicy: Owner
template:
metadata:
labels:
argocd.argoproj.io/secret-type: repo-creds
data:
type: git
url: "ssh://forgejo@forge.ops.eblu.me:2222/eblume/"
insecure: "true"
sshPrivateKey: "{{ .privateKey }}"
data:
- secretKey: privateKey
remoteRef:
key: argocd-forge-ssh-key
property: private-key-openssh

View file

@ -7,6 +7,7 @@ resources:
# Pin to specific version for intentional upgrades
- https://raw.githubusercontent.com/argoproj/argo-cd/v3.2.6/manifests/install.yaml
- service-tailscale.yaml
- external-secret-repo-forge.yaml
patches:
- path: argocd-cmd-params-cm.yaml

View file

@ -1,31 +0,0 @@
# ArgoCD credential template for forge SSH access
# This is a repo-creds (credential template) that matches ALL repos under eblume/
#
# IMPORTANT: Use ?ssh-format=openssh to get OpenSSH format (required by ArgoCD)
#
# The SSH key must be added to the Forgejo user's SSH keys (not as a deploy key)
# so it has access to all repos owned by that user.
#
# Create the secret with:
#
# PRIV_KEY=$(op read "op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key?ssh-format=openssh")$'\n' && \
# kubectl create secret generic repo-creds-forge -n argocd \
# --from-literal=type=git \
# --from-literal=url='ssh://forgejo@forge.ops.eblu.me:2222/eblume/' \
# --from-literal=insecure=true \
# --from-literal=sshPrivateKey="$PRIV_KEY" && \
# kubectl label secret repo-creds-forge -n argocd argocd.argoproj.io/secret-type=repo-creds
#
apiVersion: v1
kind: Secret
metadata:
name: repo-creds-forge
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repo-creds
stringData:
type: git
url: ssh://forgejo@forge.ops.eblu.me:2222/eblume/
insecure: "true"
sshPrivateKey: |
# Key from 1Password: op://vg6xf6vvfmoh5hqjjhlhbeoaie/csjncynh6htjvnh2l2da65y32q/private key

View file

@ -0,0 +1,30 @@
# ExternalSecret for borgmatic backup user password
#
# Replaces the manual op inject workflow from secret-borgmatic.yaml.tpl
#
# 1Password item: "borgmatic" in blumeops vault
# Field: "db-password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: blumeops-pg-borgmatic
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: blumeops-pg-borgmatic
creationPolicy: Owner
template:
type: kubernetes.io/basic-auth
data:
username: borgmatic
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: borgmatic
property: db-password

View file

@ -0,0 +1,30 @@
# ExternalSecret for eblume superuser password
#
# Replaces the manual op inject workflow from secret-eblume.yaml.tpl
#
# 1Password item: "postgres" in blumeops vault
# Field: "password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: blumeops-pg-eblume
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: blumeops-pg-eblume
creationPolicy: Owner
template:
type: kubernetes.io/basic-auth
data:
username: eblume
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: postgres
property: password

View file

@ -0,0 +1,30 @@
# ExternalSecret for TeslaMate database user password
#
# Replaces the manual op inject workflow from secret-teslamate.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "db_password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: blumeops-pg-teslamate
namespace: databases
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: blumeops-pg-teslamate
creationPolicy: Owner
template:
type: kubernetes.io/basic-auth
data:
username: teslamate
password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: TeslaMate
property: db_password

View file

@ -8,3 +8,6 @@ resources:
- immich-pg.yaml
- service-tailscale.yaml
- service-metrics-tailscale.yaml
- external-secret-eblume.yaml
- external-secret-borgmatic.yaml
- external-secret-teslamate.yaml

View file

@ -1,13 +0,0 @@
# Template for borgmatic backup user password
# Apply with: op inject -i secret-borgmatic.yaml.tpl | kubectl apply -f -
#
# Uses the same borgmatic password from 1Password as the brew PostgreSQL setup
apiVersion: v1
kind: Secret
metadata:
name: blumeops-pg-borgmatic
namespace: databases
type: kubernetes.io/basic-auth
stringData:
username: borgmatic
password: {{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/mw2bv5we7woicjza7hc6s44yvy/db-password }}

View file

@ -1,13 +0,0 @@
# Template for eblume superuser password
# Apply with: op inject -i secret-eblume.yaml.tpl | kubectl apply -f -
#
# Uses the same 1Password item as the brew PostgreSQL setup on indri
apiVersion: v1
kind: Secret
metadata:
name: blumeops-pg-eblume
namespace: databases
type: kubernetes.io/basic-auth
stringData:
username: eblume
password: {{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/guxu3j7ajhjyey6xxl2ovsl2ui/password }}

View file

@ -1,11 +0,0 @@
# Template for TeslaMate database user password
# Apply with: op inject -i argocd/manifests/databases/secret-teslamate.yaml.tpl | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: blumeops-pg-teslamate
namespace: databases
type: kubernetes.io/basic-auth
stringData:
username: teslamate
password: {{ op://blumeops/TeslaMate/db_password }}

View file

@ -1,12 +0,0 @@
# Template for devpi root password secret
# Create the secret before deploying:
# kubectl create namespace devpi
# op inject -i argocd/manifests/devpi/secret-root.yaml.tpl | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: devpi-root
namespace: devpi
type: Opaque
stringData:
password: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/kyhzfifryqnuk7jeyibmmjvxxm/root password }}"

View file

@ -0,0 +1,34 @@
# ExternalSecret for Forgejo Runner environment
#
# Replaces the manual op inject workflow from secret.yaml.tpl
#
# 1Password item: "Forgejo Secrets" in blumeops vault
# Field: "runner_reg"
#
# Note: Static values (FORGEJO_URL, RUNNER_NAME, RUNNER_LABELS) are included
# via template since they don't need to be in 1Password.
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: forgejo-runner-env
namespace: forgejo-runner
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: forgejo-runner-env
creationPolicy: Owner
template:
data:
FORGEJO_URL: "https://forge.ops.eblu.me"
RUNNER_NAME: "k8s-runner"
RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.7"
RUNNER_TOKEN: "{{ .runner_token }}"
data:
- secretKey: runner_token
remoteRef:
key: Forgejo Secrets
property: runner_reg

View file

@ -1,17 +0,0 @@
# Forgejo Runner Environment Secret
# This template is processed by `op inject` to resolve 1Password references.
#
# Usage:
# op inject -i secret.yaml.tpl | kubectl --context=minikube-indri apply -f -
#
apiVersion: v1
kind: Secret
metadata:
name: forgejo-runner-env
namespace: forgejo-runner
type: Opaque
stringData:
FORGEJO_URL: "https://forge.ops.eblu.me"
RUNNER_NAME: "k8s-runner"
RUNNER_LABELS: "k8s:docker://registry.ops.eblu.me/blumeops/forgejo-runner:v2.1.7"
RUNNER_TOKEN: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/w3663ffnvkewbftncqxtcpeavy/runner_reg }}"

View file

@ -0,0 +1,29 @@
# ExternalSecret for Grafana admin password
#
# Replaces the manual op inject workflow from secret-admin.yaml.tpl
#
# 1Password item: "Grafana (blumeops)" in blumeops vault
# Field: "password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: grafana-admin
namespace: monitoring
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: grafana-admin
creationPolicy: Owner
template:
data:
admin-user: admin
admin-password: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: Grafana (blumeops)
property: password

View file

@ -0,0 +1,31 @@
# ExternalSecret for TeslaMate PostgreSQL datasource password
#
# Replaces the manual op inject workflow from secret-teslamate-datasource.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "db_password"
#
# This secret is mounted as environment variables in Grafana.
# The password is referenced in values.yaml datasource config as $TESLAMATE_DB_PASSWORD
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: grafana-teslamate-datasource
namespace: monitoring
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: grafana-teslamate-datasource
creationPolicy: Owner
template:
data:
TESLAMATE_DB_PASSWORD: "{{ .password }}"
data:
- secretKey: password
remoteRef:
key: TeslaMate
property: db_password

View file

@ -5,6 +5,8 @@ namespace: monitoring
resources:
- ingress-tailscale.yaml
- external-secret-admin.yaml
- external-secret-teslamate-datasource.yaml
# Dashboard ConfigMaps - discovered by Grafana sidecar via label grafana_dashboard=1
- dashboards/configmap-borgmatic.yaml
- dashboards/configmap-devpi.yaml

View file

@ -1,16 +0,0 @@
# Grafana admin password secret
#
# Apply with: op inject -i secret-admin.yaml.tpl | kubectl apply -f -
#
# 1Password item: blumeops vault (vg6xf6vvfmoh5hqjjhlhbeoaie)
# Item ID: oxkcr3xtxnewy7noep2izvyr6y
# Field: password
apiVersion: v1
kind: Secret
metadata:
name: grafana-admin
namespace: monitoring
type: Opaque
stringData:
admin-user: admin
admin-password: {{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/oxkcr3xtxnewy7noep2izvyr6y/password }}

View file

@ -1,13 +0,0 @@
# TeslaMate PostgreSQL datasource password for Grafana
# Apply with: op inject -i argocd/manifests/grafana-config/secret-teslamate-datasource.yaml.tpl | kubectl apply -f -
#
# This secret is mounted as environment variables in Grafana
# The password is referenced in values.yaml datasource config as $TESLAMATE_DB_PASSWORD
apiVersion: v1
kind: Secret
metadata:
name: grafana-teslamate-datasource
namespace: monitoring
type: Opaque
stringData:
TESLAMATE_DB_PASSWORD: {{ op://blumeops/TeslaMate/db_password }}

View file

@ -19,17 +19,20 @@ Self-hosted photo and video management solution with AI-powered search and face
## Secret Setup
The `immich-db` secret contains the database password, which is auto-generated by CloudNativePG
in the `immich-pg-app` secret. To create or regenerate the secret:
```bash
# Create namespace
kubectl create namespace immich
# Create namespace if needed
kubectl --context=minikube-indri create namespace immich
# Get the auto-generated immich password from CloudNativePG
kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d
# Store that password in 1Password under blumeops/immich-pg, then:
op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f -
# Copy password from CNPG secret to immich namespace
kubectl --context=minikube-indri create secret generic immich-db -n immich \
--from-literal=password="$(kubectl --context=minikube-indri -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d)"
```
Note: This secret is not managed by ExternalSecrets since the source of truth is the CNPG-generated secret.
## Access
- **URL**: https://photos.ops.eblu.me (after Caddy is updated)

View file

@ -1,12 +0,0 @@
# Immich database password secret
# Apply with: op inject -i argocd/manifests/immich/secret-db.yaml.tpl | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: immich-db
namespace: immich
type: Opaque
stringData:
# Password is auto-generated by CloudNativePG and stored in immich-pg-app secret
# Retrieve with: kubectl -n databases get secret immich-pg-app -o jsonpath='{.data.password}' | base64 -d
password: "{{ op://blumeops/immich-pg/password }}"

View file

@ -16,8 +16,11 @@ RSS/Atom feed reader deployed via ArgoCD.
kubectl create namespace miniflux
# The miniflux user password is auto-generated by CNPG in blumeops-pg-app secret
kubectl create secret generic miniflux-db -n miniflux \
--from-literal=url="$(kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
kubectl --context=minikube-indri create secret generic miniflux-db -n miniflux \
--from-literal=url="$(kubectl --context=minikube-indri -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
# Note: This secret is not managed by ExternalSecrets since the source of truth
# is the CNPG-generated secret.
```
2. Apply the ArgoCD application:

View file

@ -1,13 +0,0 @@
# Miniflux database connection secret
#
# The miniflux user password is auto-generated by CloudNativePG and stored in
# blumeops-pg-app secret in the databases namespace. To create this secret:
#
# 1. Get the URI from CNPG secret:
# kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d
#
# 2. Create the secret (one-liner):
# kubectl create secret generic miniflux-db -n miniflux \
# --from-literal=url="$(kubectl -n databases get secret blumeops-pg-app -o jsonpath='{.data.uri}' | base64 -d)"
#
# Note: Uses internal k8s DNS hostname (blumeops-pg-rw.databases) not Tailscale

View file

@ -0,0 +1,29 @@
# ExternalSecret for Tailscale Operator OAuth credentials
#
# Replaces the manual op inject workflow from secret.yaml.tpl
#
# 1Password item: "Tailscale K8s Operator OAuth" in blumeops vault
# Fields: "client-id", "client-secret"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: operator-oauth
namespace: tailscale
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: operator-oauth
creationPolicy: Owner
data:
- secretKey: client_id
remoteRef:
key: Tailscale K8s Operator OAuth
property: client-id
- secretKey: client_secret
remoteRef:
key: Tailscale K8s Operator OAuth
property: client-secret

View file

@ -8,6 +8,4 @@ resources:
- proxyclass.yaml
- dnsconfig.yaml
- egress-forge.yaml
# Note: OAuth secret (operator-oauth) is NOT included here.
# It must be manually applied before deploying - see README.md
- external-secret.yaml

View file

@ -1,14 +0,0 @@
# Tailscale Operator OAuth Secret
# This template is processed by `op inject` to resolve 1Password references.
#
# Usage:
# op inject -i secret.yaml.tpl | kubectl apply -f -
#
apiVersion: v1
kind: Secret
metadata:
name: operator-oauth
namespace: tailscale
stringData:
client_id: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/2it22lavwgbxdskoaxanej354q/client-id }}"
client_secret: "{{ op://vg6xf6vvfmoh5hqjjhlhbeoaie/2it22lavwgbxdskoaxanej354q/client-secret }}"

View file

@ -0,0 +1,25 @@
# ExternalSecret for TeslaMate database password
#
# Replaces the manual op inject workflow from secret-db.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "db_password"
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-db
namespace: teslamate
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: teslamate-db
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: TeslaMate
property: db_password

View file

@ -0,0 +1,27 @@
# ExternalSecret for TeslaMate encryption key
#
# Replaces the manual op inject workflow from secret-encryption-key.yaml.tpl
#
# 1Password item: "TeslaMate" in blumeops vault
# Field: "api_enc_key"
#
# This key encrypts Tesla API tokens at rest in the database.
#
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: teslamate-encryption
namespace: teslamate
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-blumeops
target:
name: teslamate-encryption
creationPolicy: Owner
data:
- secretKey: key
remoteRef:
key: TeslaMate
property: api_enc_key

View file

@ -7,3 +7,5 @@ resources:
- deployment.yaml
- service.yaml
- ingress-tailscale.yaml
- external-secret-db.yaml
- external-secret-encryption-key.yaml

View file

@ -1,11 +0,0 @@
# TeslaMate database password secret
#
# Apply with: op inject -i argocd/manifests/teslamate/secret-db.yaml.tpl | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: teslamate-db
namespace: teslamate
type: Opaque
stringData:
password: {{ op://blumeops/TeslaMate/db_password }}

View file

@ -1,12 +0,0 @@
# TeslaMate encryption key secret
# This key encrypts Tesla API tokens at rest in the database
#
# Apply with: op inject -i argocd/manifests/teslamate/secret-encryption-key.yaml.tpl | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: teslamate-encryption
namespace: teslamate
type: Opaque
stringData:
key: {{ op://blumeops/TeslaMate/api_enc_key }}