Skip to content

feat: replace Infisical with OpenBao as OSS secrets backend #1

feat: replace Infisical with OpenBao as OSS secrets backend

feat: replace Infisical with OpenBao as OSS secrets backend #1

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
# ── Job 1: Helm lint & render ──────────────────────────────────────────────
helm-lint:
name: Helm lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: "latest"
- name: Lint openbao-init (local values)
run: helm lint apps/openbao-init/ -f apps/openbao-init/values-local.yaml
- name: Lint openbao-init (scaleway values)
run: helm lint apps/openbao-init/ -f apps/openbao-init/values-scaleway.yaml
- name: Render bootstrap chart (local)
run: helm template boot bootstrap/ --set env=local --set revision=main
- name: Render clusters/local chart
run: helm template loc clusters/local/ --set revision=main
- name: Render clusters/scaleway chart
run: helm template loc clusters/scaleway/ --set revision=main
# ── Job 2: Infisical absent ────────────────────────────────────────────────
infisical-absent:
name: Infisical absent
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Assert no Infisical references in active GitOps manifests
run: |
if git grep -ri infisical apps/ platform/ clusters/ bootstrap/ 2>/dev/null; then
echo "ERROR: Infisical references found in GitOps manifests"
exit 1
fi
echo "OK: No Infisical references found"
# ── Job 3: Secrets restore (destroy/recreate) ──────────────────────────────
secrets-restore:
name: Secrets restore
runs-on: ubuntu-latest
environment: dev
env:
ARGOCD_VERSION: "v2.13.0"
BAO_VERSION: "2.0.3"
REPO_URL: https://github.com/${{ github.repository }}
BRANCH: ${{ github.head_ref || github.ref_name }}
steps:
- uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: "latest"
- name: Install bao CLI
run: |
curl -sSfL \
"https://github.com/openbao/openbao/releases/download/v${BAO_VERSION}/bao_${BAO_VERSION}_linux_amd64.zip" \
-o bao.zip
unzip -q bao.zip bao
sudo mv bao /usr/local/bin/bao
- name: Start minikube
uses: medyagh/setup-minikube@latest
- name: Install ArgoCD
run: |
kubectl create namespace argocd
kubectl apply -n argocd \
-f "https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
- name: Expose ArgoCD + login
id: argocd-login
run: |
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
sleep 8
PASS=$(kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d)
argocd login localhost:8080 --username admin --password "${PASS}" --insecure
# Allow ArgoCD to pull from this (possibly private) repo via GITHUB_TOKEN
argocd repo add "${REPO_URL}" \
--username x-access-token \
--password "${{ secrets.GITHUB_TOKEN }}" \
--insecure-skip-server-verification || true
- name: Inject S3 credentials Secret (pre-sync)
run: |
kubectl create namespace openbao --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic scaleway-s3-credentials \
--namespace openbao \
--from-literal=access_key="${{ secrets.SCW_ACCESS_KEY }}" \
--from-literal=secret_key="${{ secrets.SCW_SECRET_KEY }}" \
--from-literal=bucket="${{ vars.SCW_S3_BUCKET }}"
- name: Apply bootstrap Application
run: |
argocd app create bootstrap \
--repo "${REPO_URL}" \
--path bootstrap \
--dest-server https://kubernetes.default.svc \
--dest-namespace argocd \
--helm-set env=local \
--helm-set "revision=${BRANCH}" \
--helm-set "repoURL=${REPO_URL}" \
--sync-policy automated \
--upsert
- name: Wait for OpenBao to unseal
id: wait-unseal
run: |
# Poll health endpoint until initialized + unsealed (HTTP 200)
BAO_ADDR="http://$(kubectl get svc openbao -n openbao \
-o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo 'pending'):8200"
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
for i in $(seq 1 72); do
# Refresh ClusterIP in case the svc wasn't ready yet
CIP=$(kubectl get svc openbao -n openbao -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)
if [ -n "${CIP}" ]; then
BAO_ADDR="http://${CIP}:8200"
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
fi
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \
"${BAO_ADDR}/v1/sys/health" 2>/dev/null || echo "000")
echo "Attempt ${i}/72 — OpenBao status: ${STATUS}"
if [ "${STATUS}" = "200" ] || [ "${STATUS}" = "429" ]; then
echo "OpenBao is unsealed."
break
fi
sleep 5
done
if [ "${STATUS}" != "200" ] && [ "${STATUS}" != "429" ]; then
echo "ERROR: OpenBao did not unseal within 6 minutes"
kubectl get events -n openbao --sort-by='.lastTimestamp' | tail -20 || true
kubectl logs -l 'app.kubernetes.io/name=openbao' -n openbao --tail=50 || true
exit 1
fi
- name: Write canary secret
run: |
CANARY_VALUE="survive-${{ github.run_id }}"
echo "CANARY_VALUE=${CANARY_VALUE}" >> "${GITHUB_ENV}"
# Authenticate via Kubernetes auth using the external-secrets SA token
SA_JWT=$(kubectl create token external-secrets -n external-secrets --duration=5m)
BAO_TOKEN=$(curl -sf "${BAO_ADDR}/v1/auth/kubernetes/login" \
-d "{\"role\":\"external-secrets\",\"jwt\":\"${SA_JWT}\"}" \
| jq -r '.auth.client_token')
curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
-H "X-Vault-Token: ${BAO_TOKEN}" \
-d "{\"data\":{\"value\":\"${CANARY_VALUE}\"}}"
echo "Canary written: ${CANARY_VALUE}"
# ── Cluster destroy & recreate ─────────────────────────────────────────
- name: Destroy minikube
run: minikube delete
- name: Recreate minikube
uses: medyagh/setup-minikube@latest
- name: Re-install ArgoCD
run: |
kubectl create namespace argocd
kubectl apply -n argocd \
-f "https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
kubectl rollout status deployment/argocd-server -n argocd --timeout=180s
- name: Re-expose ArgoCD + login
run: |
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
sleep 8
PASS=$(kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d)
argocd login localhost:8080 --username admin --password "${PASS}" --insecure
argocd repo add "${REPO_URL}" \
--username x-access-token \
--password "${{ secrets.GITHUB_TOKEN }}" \
--insecure-skip-server-verification || true
- name: Re-inject S3 credentials Secret
run: |
kubectl create namespace openbao --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic scaleway-s3-credentials \
--namespace openbao \
--from-literal=access_key="${{ secrets.SCW_ACCESS_KEY }}" \
--from-literal=secret_key="${{ secrets.SCW_SECRET_KEY }}" \
--from-literal=bucket="${{ vars.SCW_S3_BUCKET }}"
- name: Re-apply bootstrap Application
run: |
argocd app create bootstrap \
--repo "${REPO_URL}" \
--path bootstrap \
--dest-server https://kubernetes.default.svc \
--dest-namespace argocd \
--helm-set env=local \
--helm-set "revision=${BRANCH}" \
--helm-set "repoURL=${REPO_URL}" \
--sync-policy automated \
--upsert
- name: Wait for OpenBao to unseal after recreate
id: wait-unseal-2
run: |
for i in $(seq 1 72); do
CIP=$(kubectl get svc openbao -n openbao -o jsonpath='{.spec.clusterIP}' 2>/dev/null || true)
if [ -n "${CIP}" ]; then BAO_ADDR="http://${CIP}:8200"; fi
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" \
"${BAO_ADDR}/v1/sys/health" 2>/dev/null || echo "000")
echo "Attempt ${i}/72 — OpenBao status: ${STATUS}"
if [ "${STATUS}" = "200" ] || [ "${STATUS}" = "429" ]; then
echo "OpenBao is unsealed after recreate."
break
fi
sleep 5
done
if [ "${STATUS}" != "200" ] && [ "${STATUS}" != "429" ]; then
echo "ERROR: OpenBao did not unseal after cluster recreate"
kubectl get events -n openbao --sort-by='.lastTimestamp' | tail -20 || true
exit 1
fi
echo "BAO_ADDR=${BAO_ADDR}" >> "${GITHUB_ENV}"
- name: Assert canary secret restored (SC-001)
run: |
SA_JWT=$(kubectl create token external-secrets -n external-secrets --duration=5m)
BAO_TOKEN=$(curl -sf "${BAO_ADDR}/v1/auth/kubernetes/login" \
-d "{\"role\":\"external-secrets\",\"jwt\":\"${SA_JWT}\"}" \
| jq -r '.auth.client_token')
RESTORED=$(curl -sf "${BAO_ADDR}/v1/kv/data/apps/test/canary" \
-H "X-Vault-Token: ${BAO_TOKEN}" \
| jq -r '.data.data.value')
echo "Expected : ${CANARY_VALUE}"
echo "Restored : ${RESTORED}"
if [ "${RESTORED}" != "${CANARY_VALUE}" ]; then
echo "ERROR: Canary mismatch — secret not restored after destroy/recreate"
exit 1
fi
echo "OK: SC-001 verified — secrets survive cluster destroy/recreate"
- name: Assert no Infisical pods (SC-003)
run: |
if kubectl get pods -A -o name | grep -i infisical; then
echo "ERROR: Infisical pods found in cluster"
exit 1
fi
echo "OK: No Infisical pods in cluster"