From 6590db133c26f3ef55dfedcbe427b887cfac67c7 Mon Sep 17 00:00:00 2001 From: nick-gorse Date: Mon, 20 Apr 2026 01:19:54 +0100 Subject: [PATCH] Initial sealed-secrets gitops app --- .gitea/workflows/validate.yaml | 108 ++++++++++++++++++ .gitignore | 5 + README.md | 59 ++++++++++ bootstrap/applicationset.yaml | 46 ++++++++ bootstrap/config.yaml | 3 + .../production/helm-values/values.yaml | 21 ++++ 6 files changed, 242 insertions(+) create mode 100644 .gitea/workflows/validate.yaml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bootstrap/applicationset.yaml create mode 100644 bootstrap/config.yaml create mode 100644 manifest/overlays/production/helm-values/values.yaml diff --git a/.gitea/workflows/validate.yaml b/.gitea/workflows/validate.yaml new file mode 100644 index 0000000..23cfb91 --- /dev/null +++ b/.gitea/workflows/validate.yaml @@ -0,0 +1,108 @@ +name: Validate manifests + +on: + push: + pull_request: + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install tools + run: | + curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz \ + | tar xz -C /usr/local/bin + curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + curl -fsSL https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 \ + -o /usr/local/bin/yq + chmod +x /usr/local/bin/yq + + mkdir -p .ci-schemas/argoproj.io + curl -fsSL https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/argoproj.io/applicationset_v1alpha1.json \ + -o .ci-schemas/argoproj.io/applicationset_v1alpha1.json + cp .ci-schemas/argoproj.io/applicationset_v1alpha1.json .ci-schemas/argoproj.io/ApplicationSet_v1alpha1.json + cp .ci-schemas/argoproj.io/applicationset_v1alpha1.json .ci-schemas/argoproj.io/ApplicationSet.json + cp .ci-schemas/argoproj.io/applicationset_v1alpha1.json .ci-schemas/argoproj.io/applicationset.json + + - name: kubeconform - raw YAML + run: | + bootstrap_enabled=false + if [ -f bootstrap/config.yaml ] && grep -Eq '^[[:space:]]*enabled:[[:space:]]*true[[:space:]]*$' bootstrap/config.yaml; then + bootstrap_enabled=true + fi + + mapfile -t manifests < <( + find . -type f -name '*.yaml' \ + ! -path './.gitea/*' \ + ! -path './manifest/overlays/*/helm-values/*' \ + ! -path './bootstrap/config.yaml' \ + | sort + ) + + if [ "$bootstrap_enabled" != "true" ]; then + filtered=() + for manifest in "${manifests[@]}"; do + [ "$manifest" = "./bootstrap/applicationset.yaml" ] && continue + filtered+=("$manifest") + done + manifests=("${filtered[@]}") + fi + + if [ "${#manifests[@]}" -eq 0 ]; then + echo "No manifests found" + exit 0 + fi + + printf '%s\n' "${manifests[@]}" \ + | xargs kubeconform \ + -strict \ + -kubernetes-version 1.35.0 \ + -schema-location default \ + -schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}{{.KindSuffix}}.json' \ + -schema-location '.ci-schemas/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ + -summary + + - name: Helm template + run: | + chart_version=$(yq '.spec.generators[0].list.elements[0].chartVersion' bootstrap/applicationset.yaml) + helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets + helm repo update sealed-secrets + helm template sealed-secrets-controller sealed-secrets/sealed-secrets \ + --namespace kube-system \ + --version "$chart_version" \ + -f manifest/overlays/production/helm-values/values.yaml \ + >/tmp/rendered.yaml + kubeconform \ + -strict \ + -kubernetes-version 1.35.0 \ + /tmp/rendered.yaml + + - name: Apply bootstrap ApplicationSet + env: + KUBECONFIG_B64: ${{ secrets.KUBECONFIG_B64 }} + run: | + if [ "${GITHUB_EVENT_NAME:-}" != "push" ] || [ "${GITHUB_REF:-}" != "refs/heads/main" ]; then + echo "Skipping bootstrap apply: only push events on main may apply" + exit 0 + fi + + if [ ! -f bootstrap/config.yaml ] || ! grep -Eq '^[[:space:]]*enabled:[[:space:]]*true[[:space:]]*$' bootstrap/config.yaml; then + echo "Skipping bootstrap apply: bootstrap/config.yaml is not enabled" + exit 0 + fi + + if [ -z "${KUBECONFIG_B64:-}" ]; then + echo "KUBECONFIG_B64 secret is required to apply bootstrap/applicationset.yaml" + exit 1 + fi + + curl -fsSL https://dl.k8s.io/release/$(curl -fsSL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl \ + -o /usr/local/bin/kubectl + chmod +x /usr/local/bin/kubectl + + mkdir -p "${HOME}/.kube" + printf '%s' "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config" + + kubectl apply -f bootstrap/applicationset.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cbf5f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea/ +.vscode/ +*.swp +*.swo diff --git a/README.md b/README.md new file mode 100644 index 0000000..e24e683 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# sealed-secrets + +GitOps source-of-truth repository for the cluster Sealed Secrets controller. + +This repo follows the same dormant-bootstrap pattern as the other application +repositories, but the runtime payload is a pinned upstream Helm chart instead of +raw kustomize manifests. The controller is installed in `kube-system` with the +name `sealed-secrets-controller` so `kubeseal` works with its default controller +name assumptions. + +## Layout + +```text +. +├── .gitea/ +│ └── workflows/validate.yaml +├── .gitignore +├── bootstrap/ +│ ├── applicationset.yaml +│ └── config.yaml +├── manifest/ +│ └── overlays/ +│ └── production/ +│ └── helm-values/ +│ └── values.yaml +└── README.md +``` + +## Bootstrap activation model + +1. Prepare the repo on `init`. +2. Keep `bootstrap/config.yaml` set to `enabled: false` while validating. +3. Promote the repo to `main`. +4. Change `bootstrap/config.yaml` to `enabled: true` on `main`. +5. Let the bootstrap workflow apply `bootstrap/applicationset.yaml`. + +## Controller decisions + +- Namespace: `kube-system` +- Helm chart: `bitnami-labs/sealed-secrets` +- Chart version pin: `2.18.5` +- Controller name override: `sealed-secrets-controller` + +The name override matters because the chart defaults to `sealed-secrets`, while +`kubeseal` expects `sealed-secrets-controller` unless you pass explicit flags. + +## Secret migration workflow + +1. Install `kubeseal` locally. +2. Fetch the controller cert: + `kubeseal --fetch-cert --controller-namespace kube-system > sealed-secrets.pem` +3. Convert an existing Secret manifest: + `kubeseal --format yaml --cert sealed-secrets.pem < secret.yaml > sealed-secret.yaml` +4. Commit the resulting `SealedSecret` manifest in the owning app repo. +5. Remove the old SOPS-backed secret generator entry only after the app is + syncing from the new `SealedSecret` path. + +Use the default `strict` scope unless a secret must survive renames inside the +same namespace. Avoid `cluster-wide` scope unless there is a concrete need. diff --git a/bootstrap/applicationset.yaml b/bootstrap/applicationset.yaml new file mode 100644 index 0000000..99682ba --- /dev/null +++ b/bootstrap/applicationset.yaml @@ -0,0 +1,46 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: sealed-secrets + namespace: argocd +spec: + goTemplate: true + goTemplateOptions: + - missingkey=error + generators: + - list: + elements: + - environment: production + namespace: kube-system + path: manifest/overlays/production + chartVersion: 2.18.5 + template: + metadata: + name: 'sealed-secrets-{{ .environment }}' + labels: + app.kubernetes.io/managed-by: argocd + app.kubernetes.io/name: sealed-secrets + spec: + project: default + sources: + - repoURL: https://bitnami-labs.github.io/sealed-secrets + chart: sealed-secrets + targetRevision: '{{ .chartVersion }}' + helm: + releaseName: sealed-secrets-controller + valueFiles: + - $values/{{ .path }}/helm-values/values.yaml + - repoURL: http://gitea-ha-http.apps:3000/olb42/sealed-secrets.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: '{{ .namespace }}' + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=false + - ApplyOutOfSyncOnly=true + - ServerSideApply=true diff --git a/bootstrap/config.yaml b/bootstrap/config.yaml new file mode 100644 index 0000000..5e77a0b --- /dev/null +++ b/bootstrap/config.yaml @@ -0,0 +1,3 @@ +enabled: false +target_namespace: argocd +apply_from_branch: main diff --git a/manifest/overlays/production/helm-values/values.yaml b/manifest/overlays/production/helm-values/values.yaml new file mode 100644 index 0000000..60a8d8d --- /dev/null +++ b/manifest/overlays/production/helm-values/values.yaml @@ -0,0 +1,21 @@ +# Production values for the official sealed-secrets Helm chart. +# Chart version is pinned in bootstrap/applicationset.yaml. + +fullnameOverride: sealed-secrets-controller +namespace: kube-system + +resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 256Mi + +podLabels: + app.kubernetes.io/part-of: sealed-secrets + app.kubernetes.io/environment: production + +metrics: + serviceMonitor: + enabled: false