Add ESO OpenBao GitOps add-ons

This commit is contained in:
2026-06-25 20:08:36 +02:00
parent 0f0b14001e
commit 693dc71833
12 changed files with 353 additions and 6 deletions

View File

@@ -19,6 +19,7 @@ OPENBAO_UI_OVERLAY_K8S ?= helm/openbao-ui-overlay-k8s.yaml
OPENBAO_VERIFY_AUTH_ARGS ?=
OPENBAO_RESTORE_EVIDENCE ?= /tmp/netkingdom-openbao-restore-drill/evidence.json
OPENBAO_EMERGENCY_EVIDENCE ?= /tmp/netkingdom-openbao-emergency-drill/evidence.json
EXTERNAL_SECRETS_NAMESPACE ?= external-secrets
ARGOCD_NAMESPACE ?= argocd
ARGOCD_BOOTSTRAP_DIR ?= argocd/bootstrap
ARGOCD_REPOSITORY_SECRET ?=
@@ -157,6 +158,11 @@ openbao-verify-authenticated: ## Run authenticated non-mutating OpenBao audit/au
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) scripts/openbao-verify-authenticated.sh $(OPENBAO_VERIFY_AUTH_ARGS)
openbao-configure-external-secrets-issue-core: ## Configure OpenBao policy/role for issue-core ESO pilot
KUBECTL='$(KUBECTL)' OPENBAO_NAMESPACE=$(OPENBAO_NAMESPACE) \
OPENBAO_RELEASE=$(OPENBAO_RELEASE) ESO_NAMESPACE=$(EXTERNAL_SECRETS_NAMESPACE) \
scripts/openbao-apply-external-secrets-issue-core.sh
openbao-validate-restore-evidence: ## Validate non-secret OpenBao restore-drill evidence JSON
OPENBAO_RESTORE_EVIDENCE='$(OPENBAO_RESTORE_EVIDENCE)' \
scripts/openbao-validate-restore-evidence.sh
@@ -180,9 +186,9 @@ argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOC
argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos
$(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \
railiance-bootstrap railiance-tenants
railiance-bootstrap railiance-tenants railiance-platform-addons
$(KUBECTL) get applications.argoproj.io -n $(ARGOCD_NAMESPACE) \
railiance-apps-root
railiance-apps-root external-secrets openbao-secretstore issue-core
$(KUBECTL) get secrets -n $(ARGOCD_NAMESPACE) \
-l argocd.argoproj.io/secret-type=repository
@@ -198,4 +204,4 @@ help: ## Show this help
/^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-22s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: db-deploy db-status db-shell db-logs apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-validate-restore-evidence openbao-validate-emergency-evidence argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup help
.PHONY: db-deploy db-status db-shell db-logs apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-validate-restore-evidence openbao-validate-emergency-evidence argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup help

View File

@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: external-secrets
namespace: argocd
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
project: railiance-platform-addons
source:
repoURL: https://charts.external-secrets.io
chart: external-secrets
targetRevision: 0.16.1
helm:
releaseName: external-secrets
values: |
installCRDs: true
serviceAccount:
create: true
name: external-secrets
destination:
server: https://kubernetes.default.svc
namespace: external-secrets
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true
- ApplyOutOfSyncOnly=true
- PruneLast=true

View File

@@ -0,0 +1,27 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: openbao-secretstore
namespace: argocd
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
project: railiance-platform-addons
source:
repoURL: https://gitea.coulomb.social/coulomb/railiance-platform.git
targetRevision: main
path: argocd/platform-addons/openbao-secretstore
destination:
server: https://kubernetes.default.svc
namespace: external-secrets
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ApplyOutOfSyncOnly=true
- PruneLast=true

View File

@@ -0,0 +1,48 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: railiance-platform-addons
namespace: argocd
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: gitops
spec:
description: Platform-owned cluster add-ons required by tenant workloads.
sourceRepos:
- https://gitea.coulomb.social/coulomb/railiance-platform.git
- https://charts.external-secrets.io
destinations:
- server: https://kubernetes.default.svc
namespace: "*"
clusterResourceWhitelist:
- group: ""
kind: Namespace
- group: apiextensions.k8s.io
kind: CustomResourceDefinition
- group: admissionregistration.k8s.io
kind: MutatingWebhookConfiguration
- group: admissionregistration.k8s.io
kind: ValidatingWebhookConfiguration
- group: rbac.authorization.k8s.io
kind: ClusterRole
- group: rbac.authorization.k8s.io
kind: ClusterRoleBinding
- group: external-secrets.io
kind: ClusterSecretStore
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: apps
kind: Deployment
- group: rbac.authorization.k8s.io
kind: Role
- group: rbac.authorization.k8s.io
kind: RoleBinding
orphanedResources:
warn: true

View File

@@ -3,5 +3,6 @@ kind: Kustomization
resources:
- 00-railiance-bootstrap-project.yaml
- 01-railiance-tenants-project.yaml
- 02-railiance-platform-addons-project.yaml
- 10-railiance-apps-root.application.yaml

View File

@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- openbao.clustersecretstore.yaml

View File

@@ -0,0 +1,23 @@
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
name: openbao
labels:
app.kubernetes.io/part-of: railiance-gitops
railiance-platform/component: external-secrets
spec:
provider:
vault:
server: http://openbao.openbao.svc.cluster.local:8200
path: platform
version: v2
auth:
kubernetes:
mountPath: kubernetes
role: external-secrets-issue-core
serviceAccountRef:
name: external-secrets
namespace: external-secrets
conditions:
- namespaces:
- issue-core

View File

@@ -174,3 +174,41 @@ ClusterRoleBindings, or other cluster-admin resources.
If a tenant needs a cluster-scoped platform resource, create a new
platform-owned workplan instead of broadening the tenant project by default.
## Platform Add-ons
External Secrets Operator is a platform-owned add-on because it installs CRDs,
webhooks, and cluster RBAC. Tenant Applications must not install or upgrade it.
The GitOps contract uses:
- `railiance-platform-addons` AppProject for cluster add-ons.
- `external-secrets` ArgoCD Application for the public Helm chart.
- `openbao-secretstore` ArgoCD Application for the OpenBao
`ClusterSecretStore`.
- OpenBao Kubernetes auth role `external-secrets-issue-core` for the
issue-core pilot.
The initial `ClusterSecretStore/openbao` is intentionally limited to the
`issue-core` namespace. Broaden it only with a new platform review when another
tenant is ready to consume OpenBao through ESO.
Configure the OpenBao side without printing token values:
```bash
OPENBAO_TOKEN_FILE=~/.local/openbao/platform-admin.token \
make openbao-configure-external-secrets-issue-core
```
The helper keeps Kubernetes auth in local-reviewer mode: OpenBao rereads its
own mounted service-account token and CA file instead of storing an expiring
reviewer JWT.
Then sync ArgoCD and verify:
```bash
make argocd-bootstrap-deploy
make argocd-status
kubectl -n external-secrets get deploy,pod
kubectl get clustersecretstore.external-secrets.io openbao
```

View File

@@ -0,0 +1,13 @@
# Least-privilege policy for the External Secrets Operator issue-core pilot.
#
# The matching Kubernetes auth role binds only the ESO service account in the
# external-secrets namespace. ClusterSecretStore usage is separately limited to
# the issue-core namespace.
path "platform/data/workloads/issue-core/issue-core/*" {
capabilities = ["read"]
}
path "platform/metadata/workloads/issue-core/issue-core/*" {
capabilities = ["read", "list"]
}

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
OPENBAO_NAMESPACE="${OPENBAO_NAMESPACE:-openbao}"
OPENBAO_RELEASE="${OPENBAO_RELEASE:-openbao}"
KUBECTL="${KUBECTL:-kubectl}"
TOKEN_FILE="${OPENBAO_TOKEN_FILE:-}"
ROLE_NAME="${OPENBAO_ESO_ROLE:-external-secrets-issue-core}"
POLICY_NAME="${OPENBAO_ESO_POLICY:-external-secrets-issue-core}"
ESO_NAMESPACE="${ESO_NAMESPACE:-external-secrets}"
ESO_SERVICE_ACCOUNT="${ESO_SERVICE_ACCOUNT:-external-secrets}"
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
POLICY_FILE="${POLICY_FILE:-$REPO_DIR/openbao/policies/external-secrets-issue-core.hcl}"
DRY_RUN=0
usage() {
cat <<'USAGE'
Usage: scripts/openbao-apply-external-secrets-issue-core.sh [--dry-run]
Configures OpenBao for the issue-core External Secrets Operator pilot:
- refreshes Kubernetes auth config for in-cluster short-lived tokens
- writes the external-secrets-issue-core read policy
- writes the Kubernetes auth role bound to external-secrets/external-secrets
The script reads an OpenBao operator token from OPENBAO_TOKEN_FILE or an
interactive hidden prompt. It never prints or stores the token.
USAGE
}
while [ "$#" -gt 0 ]; do
case "$1" in
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "ERROR: unknown argument: $1" >&2
usage >&2
exit 2
;;
esac
done
pod="${OPENBAO_RELEASE}-0"
read_token() {
if [ "$DRY_RUN" -eq 1 ]; then
printf 'dry-run-token\n'
return
fi
if [ -n "$TOKEN_FILE" ]; then
if [ ! -f "$TOKEN_FILE" ]; then
echo "ERROR: OPENBAO_TOKEN_FILE does not exist: $TOKEN_FILE" >&2
exit 1
fi
head -n 1 "$TOKEN_FILE"
return
fi
local token
read -r -s -p "OpenBao token: " token
printf '\n' >&2
printf '%s\n' "$token"
}
kubectl_exec() {
# shellcheck disable=SC2086
$KUBECTL "$@"
}
remote_bao() {
local token="$1"
shift
if [ "$DRY_RUN" -eq 1 ]; then
printf 'DRY-RUN: bao %s\n' "$*"
return 0
fi
printf '%s\n' "$token" | kubectl_exec exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \
sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; exec bao "$@"' sh "$@"
}
remote_sh() {
local token="$1"
local script="$2"
if [ "$DRY_RUN" -eq 1 ]; then
printf 'DRY-RUN: remote shell: %s\n' "$script"
return 0
fi
printf '%s\n%s\n' "$token" "$script" | kubectl_exec exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \
sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; sh'
}
write_policy() {
local token="$1"
if [ ! -f "$POLICY_FILE" ]; then
echo "ERROR: missing policy file: $POLICY_FILE" >&2
exit 1
fi
if [ "$DRY_RUN" -eq 1 ]; then
printf 'DRY-RUN: bao policy write %s %s\n' "$POLICY_NAME" "$POLICY_FILE"
return 0
fi
{ printf '%s\n' "$token"; cat "$POLICY_FILE"; } | kubectl_exec exec -i -n "$OPENBAO_NAMESPACE" "$pod" -- \
sh -c 'read -r BAO_TOKEN; export BAO_TOKEN; bao policy write "$1" -' sh "$POLICY_NAME"
}
token="$(read_token)"
if [ -z "$token" ]; then
echo "ERROR: empty token" >&2
exit 1
fi
remote_bao "$token" status
remote_sh "$token" 'bao write auth/kubernetes/config \
kubernetes_host="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}" \
disable_iss_validation=true'
write_policy "$token"
remote_bao "$token" write "auth/kubernetes/role/${ROLE_NAME}" \
"bound_service_account_names=${ESO_SERVICE_ACCOUNT}" \
"bound_service_account_namespaces=${ESO_NAMESPACE}" \
"policies=${POLICY_NAME}" \
ttl=15m
remote_bao "$token" read "auth/kubernetes/role/${ROLE_NAME}"
cat <<'NEXT'
External Secrets OpenBao role configured.
Next steps:
1. Sync the external-secrets and openbao-secretstore ArgoCD Applications.
2. Provision platform/workloads/issue-core/issue-core/issue-core-runtime
with ISSUE_CORE_API_KEY and GITEA_BACKEND_TOKEN without printing values.
3. Confirm ExternalSecret/issue-core-runtime becomes Ready.
NEXT

View File

@@ -188,8 +188,7 @@ enable_optional "$token" "kubernetes/ auth method is already enabled." auth enab
remote_sh "$token" 'bao write auth/kubernetes/config \
kubernetes_host="https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}" \
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt'
disable_iss_validation=true'
write_policy "$token" platform-admin "$POLICY_DIR/platform-admin.hcl"
write_policy "$token" platform-readonly "$POLICY_DIR/platform-readonly.hcl"

View File

@@ -10,7 +10,7 @@ topic_slug: railiance
planning_priority: high
planning_order: 4
created: "2026-06-19"
updated: "2026-06-19"
updated: "2026-06-25"
state_hub_workstream_id: "e57e487b-8557-439d-8093-0457c73ede93"
---
@@ -149,6 +149,21 @@ platform/operators/argocd/repositories/<repo-name>
External Secrets Operator for values that become Kubernetes Secrets, CSI for
file-reference workloads, and no OpenBao injector in the current deployment.
## Follow-up Progress (2026-06-25)
- Added a platform-owned `railiance-platform-addons` AppProject for
cluster-scoped add-ons.
- Added the `external-secrets` ArgoCD Application for External Secrets
Operator and the `openbao-secretstore` Application for
`ClusterSecretStore/openbao`.
- Added the least-privilege OpenBao policy and Kubernetes auth role helper for
the issue-core ESO pilot. The role binds only the
`external-secrets/external-secrets` service account and reads only
`platform/workloads/issue-core/issue-core/*`.
- Limited the initial `ClusterSecretStore/openbao` to the `issue-core`
namespace; broaden only through a later platform review.
## Target State
- `argocd/bootstrap/` contains the two AppProjects and root app-of-apps