Wire Forgejo SMTP password from OpenBao via External Secrets
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 5s

Add forgejo-mailer ExternalSecret, inject GITEA__mailer__PASSWD from the
synced Secret, remove mailer from SOPS overlay, and add ESO bootstrap targets.
This commit is contained in:
tegwick 2026-07-07 14:30:02 +02:00
parent 24c8882b69
commit d5bb4f66f2
7 changed files with 141 additions and 35 deletions

View file

@ -48,6 +48,9 @@ FORGEJO_DB_CLUSTER ?= forgejo-db
FORGEJO_DB_NAMESPACE ?= databases FORGEJO_DB_NAMESPACE ?= databases
FORGEJO_RUNNER_MANIFEST ?= manifests/forgejo-runner.yaml FORGEJO_RUNNER_MANIFEST ?= manifests/forgejo-runner.yaml
FORGEJO_RUNNER_REGISTRATION_SECRET ?= helm/forgejo-runner-registration.sops.yaml FORGEJO_RUNNER_REGISTRATION_SECRET ?= helm/forgejo-runner-registration.sops.yaml
FORGEJO_MAILER_EXTERNAL_SECRET ?= manifests/forgejo-mailer-externalsecret.yaml
PLATFORM_REPO ?= $(HOME)/railiance-platform
ESO_CHART_VERSION ?= 0.16.1
INTER_HUB_IMAGE_REF = $(INTER_HUB_IMAGE_REPOSITORY):$(INTER_HUB_IMAGE_TAG) INTER_HUB_IMAGE_REF = $(INTER_HUB_IMAGE_REPOSITORY):$(INTER_HUB_IMAGE_TAG)
INTER_HUB_IMAGE_SET_ARG = $(if $(strip $(INTER_HUB_IMAGE_TAG)),--set image.tag=$(INTER_HUB_IMAGE_TAG),) INTER_HUB_IMAGE_SET_ARG = $(if $(strip $(INTER_HUB_IMAGE_TAG)),--set image.tag=$(INTER_HUB_IMAGE_TAG),)
@ -171,31 +174,56 @@ inter-hub-logs: check-railiance01-kubeconfig ## Tail inter-hub app logs from Rai
##@ Forgejo (forgejo.coulomb.social) ##@ Forgejo (forgejo.coulomb.social)
forgejo-dry-run: check-railiance01-kubeconfig check-sops ## helm template render for Forgejo eso-deploy: check-railiance01-kubeconfig ## Install External Secrets Operator on railiance01
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add external-secrets https://charts.external-secrets.io --force-update
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install external-secrets external-secrets/external-secrets \
--version $(ESO_CHART_VERSION) \
--namespace external-secrets --create-namespace \
--set installCRDs=true \
--set serviceAccount.create=true \
--set serviceAccount.name=external-secrets \
--wait --timeout 5m
forgejo-openbao-store-deploy: check-railiance01-kubeconfig eso-deploy ## Apply openbao-forgejo ClusterSecretStore
test -d "$(PLATFORM_REPO)/argocd/platform-addons/openbao-secretstore"
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f "$(PLATFORM_REPO)/argocd/platform-addons/openbao-secretstore/openbao-forgejo.clustersecretstore.yaml"
forgejo-mailer-es-deploy: check-railiance01-kubeconfig forgejo-openbao-store-deploy ## Apply Forgejo mailer ExternalSecret
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f $(FORGEJO_MAILER_EXTERNAL_SECRET)
forgejo-mailer-es-status: check-railiance01-kubeconfig ## Show Forgejo mailer ExternalSecret sync status
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get externalsecret,secret -n $(FORGEJO_NAMESPACE) forgejo-mailer
forgejo-dry-run: check-railiance01-kubeconfig ## helm template render for Forgejo
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm template $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm template $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \ --version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) \ --namespace $(FORGEJO_NAMESPACE) \
-f $(FORGEJO_VALUES) \ -f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS)) -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh)
forgejo-server-dry-run: check-railiance01-kubeconfig check-sops ## Helm server dry-run Forgejo upgrade forgejo-server-dry-run: check-railiance01-kubeconfig ## Helm server dry-run Forgejo upgrade
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \ --version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) --create-namespace \ --namespace $(FORGEJO_NAMESPACE) --create-namespace \
-f $(FORGEJO_VALUES) \ -f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS)) \ -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) \
--dry-run=server --timeout 5m --dry-run=server --timeout 5m
forgejo-deploy: check-railiance01-kubeconfig check-sops ## Deploy / upgrade Forgejo on railiance01 forgejo-deploy: check-railiance01-kubeconfig forgejo-mailer-es-deploy ## Deploy / upgrade Forgejo on railiance01
@test -n "$$(KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get secret forgejo-mailer -n $(FORGEJO_NAMESPACE) -o name 2>/dev/null)" || { \
echo "Missing secret forgejo-mailer in $(FORGEJO_NAMESPACE)." >&2; \
echo "Ensure ExternalSecret is Ready and OpenBao role external-secrets-forgejo is configured." >&2; \
exit 1; \
}
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add gitea-charts https://dl.gitea.com/charts/ --force-update KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add gitea-charts https://dl.gitea.com/charts/ --force-update
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \ KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \ --version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) --create-namespace \ --namespace $(FORGEJO_NAMESPACE) --create-namespace \
-f $(FORGEJO_VALUES) \ -f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \ -f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS)) \ -f <(KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-helm-secrets.sh) \
--wait --timeout 10m --wait --timeout 10m
forgejo-ingress-deploy: check-railiance01-kubeconfig ## Apply Forgejo HTTPS ingress forgejo-ingress-deploy: check-railiance01-kubeconfig ## Apply Forgejo HTTPS ingress
@ -268,4 +296,4 @@ help: ## Show this help
/^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \ /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help .PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret eso-deploy forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help

View file

@ -104,25 +104,28 @@ gitea:
USER: forgejo@coulomb.social USER: forgejo@coulomb.social
``` ```
Mailbox password goes in `helm/forgejo-secrets.sops.yaml` (encrypt from template): Mailbox password is stored in **OpenBao** and delivered by External Secrets:
```yaml | Item | Value |
gitea: | --- | --- |
config: | OpenBao path | `platform/workloads/forgejo/forgejo-mailer` |
mailer: | Field | `MAILER_PASSWD` |
PASSWD: <IONOS mailbox password> | ExternalSecret | `manifests/forgejo-mailer-externalsecret.yaml` |
``` | Helm consumption | `gitea.additionalConfigFromEnvs``GITEA__mailer__PASSWD` |
Deploy path: Bootstrap (railiance01):
```bash ```bash
cd ~/railiance-platform
KUBECONFIG=~/.kube/config-hosteurope make openbao-configure-external-secrets-forgejo
cd ~/railiance-apps cd ~/railiance-apps
make check-sops KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-deploy
KUBECONFIG=~/.kube/config-hosteurope make forgejo-mailer-es-status # expect SecretSynced
KUBECONFIG=~/.kube/config-hosteurope make forgejo-deploy KUBECONFIG=~/.kube/config-hosteurope make forgejo-deploy
# T06: Admin → user → Send reset password; confirm email delivery
``` ```
Do not enable `mailer.ENABLED` until the SOPS secret is in place. Do not put the SMTP password in `helm/forgejo-secrets.sops.yaml`.
## Coexistence with Gitea ## Coexistence with Gitea

View file

@ -1,11 +1,9 @@
gitea: gitea:
admin: admin:
password: ENC[AES256_GCM,data:D4vjZzWdKb8FgCXcRp+Ndjya1yFqB995,iv:WzOzUMTu4TBcpEgYFkz186CnmSpmmQ+Jqnz8I4ZpVVw=,tag:hHbXEhBGfNhr63mNF/HTBA==,type:str] password: ENC[AES256_GCM,data:/0h7UHVFAKQbOczZs880ZhroIMYeuaWM,iv:cJu65mLICZHlog65qL/7OWZbMAa+2v+zXt1e8i/veqc=,tag:FJecXS8ImotmwO/avJYQ4A==,type:str]
config: config:
database: database:
PASSWD: ENC[AES256_GCM,data:oHp70axstRWh4YPYeKNpt8a8eejgFjyw6azdhZG1WqA=,iv:B9acXzEmJ2tGzkIkG2X/RsrdSGwzV/dgOI3Hn8/J9is=,tag:WHs/yEIZ7upffCXaOUfPuQ==,type:str] PASSWD: ENC[AES256_GCM,data:80VKXVe/XGiO2ROOjyweHVveYpnn/KnVfnF93IB1QY0=,iv:wAaNx2B1F3yvNliB2ntecPIPLh+lTwWTsLyM70YktCs=,tag:0hzr9dwlz8AyN5uv3AbylA==,type:str]
mailer:
PASSWD: ENC[AES256_GCM,data:W3+t8Hgn4/yPAvVWYa/Gkn4=,iv:NrL0TD3lTQWfVIzxbjJi9FrZrMmI6s4Bpo8zvMKVHLI=,tag:pBvK4wrhOOWUDI2wtg7MIQ==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
@ -15,14 +13,14 @@ sops:
- recipient: age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4 - recipient: age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4
enc: | enc: |
-----BEGIN AGE ENCRYPTED FILE----- -----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsbjJqRG9uOUZiU0RjSnVm YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwaWZ0cldQNDQyNUxJeUdX
aE9ZaitSdjdjb0RNU2pzNGlsTHV5K2I1MGlBCms2RFRPelA1ODBoVTZidFZnOC9u ZG1jbS9HVUlzUFNuM3pMdzlLS1BHSGZmb21rCmpGRG9wOVV4bGl3VmtGRk14a1o5
ZllVazZDRmZjQzg4L0E3QXpJWDFIUGcKLS0tIFViM0czZDR6dUFTRmZXRUQ0RW9w RzhDUWpPaGozZjh5YWsxdXI4NmdoRzAKLS0tIE5qTG53b2pMRGhlZzNUa3l0ejBy
aEF0RDMzb0o1YzduZHIxUGNBdVRnOGMKjDu/B39gqkT2JJ/7NGbFqmyUPtGAf3ST eE5nRDQ4WWswdmtvNmx1OSsxZVl0eG8K0Njs3CVPwUKVM4rnriJq7EXIUg0HbT5F
sUNhOgGXQRs6576QSiUEzO8Prio/lnFPM96boMgXv4+Gt2dcPmXyoA== qRiSwY8fmFa2nW38mbII1Bna6w6ahGMNdIeGL/u4tlt/Bt+iIAYItw==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2026-07-07T00:41:12Z" lastmodified: "2026-07-07T12:27:30Z"
mac: ENC[AES256_GCM,data:8TM3EOv3yhYfh6QTTcD2AJK1y77JJKygwoJUMULN3q3A+b/RefpXs6+h66wn13P9EvQu9FTZg0kwijLUOCktuBPfaAiz00+BK9PFtt5UsDA9g/qUVA5EUxpmq0vRzDv1njhomwmI/f9pMbKiXRvXpkXxhpMeRx6XN0D4itCEMvI=,iv:RlicLXVU7kQgfc6bM2wAvyAB8zAMX3G8ZPeToJ73g14=,tag:Det/rkDaw8LqJNqZlkGKkw==,type:str] mac: ENC[AES256_GCM,data:tGhGH9PjoTCSJlIi6Rh/n3jiUN/Heu7L3yvLWP1cKOZMg2N1G+B/mL0trAJbyzljbi1M/LgKEf7eUwLM1gAciLQG8pRBZqb3L+IwErf7/+ul1mhyZSwwL5DuzP+hxnwRh06TLBxbV1jYSdY5QmL8QmeNxmvgVA+4AC6CIlX7IzY=,iv:OVN6s2uobQ4jup2hoAB1+EIXe+yz49J1hJZRjTCbezU=,tag:Y9xKxrhq2DkHt2EgPq8ZJQ==,type:str]
pgp: [] pgp: []
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.9.4 version: 3.9.4

View file

@ -1,5 +1,7 @@
# Encrypt as helm/forgejo-secrets.sops.yaml before production deploy. # Encrypt as helm/forgejo-secrets.sops.yaml before production deploy.
# Contains database PASSWD, admin password, and IONOS SMTP mailbox password. # Contains database PASSWD and admin password consumed by the gitea chart.
# IONOS SMTP password lives in OpenBao platform/workloads/forgejo/forgejo-mailer
# and is synced by manifests/forgejo-mailer-externalsecret.yaml.
# #
# gitea: # gitea:
# admin: # admin:
@ -7,13 +9,9 @@
# config: # config:
# database: # database:
# PASSWD: <same as forgejo-db-credentials> # PASSWD: <same as forgejo-db-credentials>
# mailer:
# PASSWD: <IONOS mailbox password for forgejo@coulomb.social>
gitea: gitea:
admin: admin:
password: REPLACE_WITH_ADMIN_PASSWORD password: REPLACE_WITH_ADMIN_PASSWORD
config: config:
database: database:
PASSWD: REPLACE_WITH_DB_PASSWORD PASSWD: REPLACE_WITH_DB_PASSWORD
mailer:
PASSWD: REPLACE_WITH_IONOS_MAILBOX_PASSWORD

View file

@ -35,6 +35,13 @@ gitea:
username: forgejo_admin username: forgejo_admin
email: admin@coulomb.social email: admin@coulomb.social
passwordMode: initialOnlyRequireReset passwordMode: initialOnlyRequireReset
# Mailer password from OpenBao via ExternalSecret forgejo-mailer (not Helm/SOPS).
additionalConfigFromEnvs:
- name: GITEA__mailer__PASSWD
valueFrom:
secretKeyRef:
name: forgejo-mailer
key: MAILER_PASSWD
config: config:
database: database:
DB_TYPE: postgres DB_TYPE: postgres

View file

@ -0,0 +1,30 @@
# Forgejo IONOS SMTP password from OpenBao (RAIL-HO-WP-0005-T06).
#
# OpenBao path (platform KV v2): workloads/forgejo/forgejo-mailer
# Field: MAILER_PASSWD (optional SMTP_USER for non-Helm consumers)
#
# Prereqs on railiance01:
# - External Secrets Operator (make eso-deploy)
# - ClusterSecretStore openbao-forgejo (railiance-platform openbao-secretstore)
# - OpenBao role external-secrets-forgejo (make openbao-configure-external-secrets-forgejo)
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: forgejo-mailer
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo
app.kubernetes.io/part-of: railiance-apps
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao-forgejo
target:
name: forgejo-mailer
creationPolicy: Owner
data:
- secretKey: MAILER_PASSWD
remoteRef:
key: workloads/forgejo/forgejo-mailer
property: MAILER_PASSWD

42
tools/forgejo-helm-secrets.sh Executable file
View file

@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Emit Helm values for Forgejo DB + admin secrets (no mailer — OpenBao/ESO owns SMTP).
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SECRETS_FILE="${FORGEJO_SECRETS:-$ROOT_DIR/helm/forgejo-secrets.sops.yaml}"
KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config-hosteurope}"
emit_from_sops() {
sops -d "$SECRETS_FILE" | python3 -c "
import sys, yaml
data = yaml.safe_load(sys.stdin) or {}
mailer = data.get('gitea', {}).get('config', {}).get('mailer')
if mailer:
data['gitea']['config'].pop('mailer', None)
yaml.dump(data, sys.stdout, default_flow_style=False)
"
}
emit_from_cluster() {
python3 -c "
import json, os, subprocess, sys, yaml
os.environ['KUBECONFIG'] = '${KUBECONFIG}'
vals = json.loads(subprocess.check_output(
['helm', 'get', 'values', 'forgejo', '-n', 'forgejo', '-o', 'json'], text=True))
g = vals.get('gitea', {})
yaml.dump({
'gitea': {
'admin': {'password': g.get('admin', {}).get('password', '')},
'config': {
'database': {'PASSWD': g.get('config', {}).get('database', {}).get('PASSWD', '')},
},
}
}, sys.stdout, default_flow_style=False)
"
}
if command -v sops >/dev/null 2>&1 && sops -d "$SECRETS_FILE" >/dev/null 2>&1; then
emit_from_sops
else
emit_from_cluster
fi