feat: replace Infisical with OpenBao as OSS secrets backend #1
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |