A demonstration of decoupled platform engineering and application development workflows using Flux CD, OCI artifacts, and Kyverno on a local kind cluster.
This repository demonstrates a separation of concerns between platform teams and application teams:
- Platform Engineering: Owns archetype Helm charts (
charts/) and cluster-wide governance policies. Charts are packaged and published to GitHub Container Registry (GHCR) with SemVer tags. - Application Development: Owns application source code and deployment parameters (
apps-source/values.yaml). Application teams deploy by publishing container images andvalues.yamlartifacts to GHCR using a mutablelatesttag without making Git commits to the cluster repository. - Cluster Infrastructure: Provisions a local kind cluster and bootstraps Flux CD and Kyverno using OpenTofu (
kind-cluster/). - Reconciliation & Composition: Flux
source-watchercomposes the platform base chart and developer values into anExternalArtifact, triggering immediate event-driven upgrades inhelm-controller(clusters/kind/).
┌────────────────────────────────────────────────────────┐
│ Platform Concern │
│ Base Chart (GHCR: oci://.../archetype-backend:0.1.1) │
└──────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────┐
│ Flux Artifact Composition (source-watcher) │
│ ArtifactGenerator ───► ExternalArtifact (Merged Chart) │
└──────────────────────────▲─────────────────────────────┘
│
┌──────────────────────────┴─────────────────────────────┐
│ Developer Concern │
│ App Values (GHCR: oci://.../values:latest) │
└────────────────────────────────────────────────────────┘
kind-cluster/: OpenTofu configuration that creates the kind cluster and installs theflux-operatorand Kyverno.charts/: Platform-owned Helm charts consumed by application teams.clusters/kind/: Flux manifests defining the continuous delivery pipeline:OCIRepository,ArtifactGenerator,HelmRelease, and governance policies.apps-source/: Simulated application repository containing the container build files and environment values (values.yaml).fixtures/spicedb/: Human-readable SpiceDB schema (schema.zed) and relationship tuples (relationships.txt).scripts/: Developer CLI utilities, includingspicedb-fixture.shfor testing permissions and updating fixtures..github/workflows/: GitHub Actions workflows for publishing charts, images, and values artifacts with build provenance attestations.
Ensure you have installed the required CLI tools managed by mise:
mise installThis installs:
kindopentofuhelmkubectlyqflux
All cluster lifecycle commands are managed by kind-cluster/cluster.sh:
cd kind-cluster
./cluster.sh up # Create the kind cluster, bootstrap Flux and Kyverno, and verify health
./cluster.sh check # Check pod readiness and print component status
./cluster.sh down # Destroy the OpenTofu stack and delete the kind clusterNote: Both
upandcheckautomatically configureKUBECONFIGfrom the OpenTofu state. You do not need to exportKUBECONFIGmanually.
The application team delivers updates independently of the platform GitOps repository.
- Publish Helm chart (
.github/workflows/publish-chart.yaml):- Packages the platform chart (
charts/archetype-backend) with a given SemVer version. - Pushes
oci://ghcr.io/magnusp/charts/archetype-backend:<version>. - Generates a GitHub build provenance attestation.
- Packages the platform chart (
- Build app image (
.github/workflows/build-app-image.yaml):- Builds the container image from
apps-source/. - Pushes
ghcr.io/magnusp/apps/archetype-backend:<commit-sha>. - Stamps the OCI config labels
org.opencontainers.image.revision,org.opencontainers.image.vendor, anddev.authz.app.deployer. - Generates a GitHub build provenance attestation.
- Builds the container image from
- Bump archetype-backend values (
.github/workflows/publish-app-values.yaml):- Updates
image.taginapps-source/values.yamlto the target commit SHA. - Pushes
apps-source/as an OCI artifact toghcr.io/magnusp/apps/archetype-backend-values:latestwith deployer provenance annotations. - Generates a GitHub build provenance attestation.
- Updates
Follow these steps to deploy an application change:
-
Merge changes to the main application branch.
-
Trigger
Build app image:- Navigate to Actions > Build app image and run the workflow on your target commit.
-
Trigger
Bump archetype-backend values:- Run the workflow with the
image_taginput set to the commit SHA built in Step 2.
- Run the workflow with the
-
Verify Deployment:
- Flux automatically detects the new values artifact digest, generates a new
ExternalArtifact, and reconciles theHelmRelease:
kubectl get helmrelease -n flux-system archetype-backend-demo kubectl get pods -n apps -l app.kubernetes.io/instance=archetype-backend-demo kubectl get deploy -n apps apps-archetype-backend-demo \ -o jsonpath='{.items[0].spec.template.spec.containers[0].image}' - Flux automatically detects the new values artifact digest, generates a new
This repository separates policy enforcement into two layers and scopes governance policies to opt-in application workspaces labeled governance.platform.io/managed: "true" (e.g. namespace/apps):
- Platform Validation Policy (
clusters/kind/clusterpolicy-disallow-manual-image-revision.yaml):- Enforces across managed workspaces that developers and incoming Helm charts cannot manually set or forge the
example.com/image-revisionannotation onDeploymenttemplates.
- Enforces across managed workspaces that developers and incoming Helm charts cannot manually set or forge the
- Archetype Mutation Policy (
charts/archetype-backend/templates/policy.yaml):- A namespaced Kyverno
Policypackaged with the archetype chart. - At admission time, it queries the OCI registry for the container image configuration, extracts
org.opencontainers.image.revision, and injects it intospec.template.metadata.annotations.
- A namespaced Kyverno
- Image Base Ancestor & Layer Policy (
clusters/kind/clusterpolicy-verify-image-nginx-ancestor.yaml):- Inspects the container image filesystem configuration at admission time using Kyverno's
imageRegistrycontext. - Iterates across root filesystem layer hashes (
imageData.configData.rootfs.diff_ids) to cryptographically assert that the container image is derived from an approvednginx:1.27base image (apps-source/Dockerfile), regardless of intermediate build steps.
- Inspects the container image filesystem configuration at admission time using Kyverno's
To verify that the verified image revision was stamped on the running workload:
kubectl get deploy -n apps apps-archetype-backend-demo \
-o jsonpath='{.spec.template.metadata.annotations}'Policy Reporter is installed as a Flux HelmRelease (kind-cluster/policy-reporter.tf) and persists policy execution history and violation reports in an embedded SQLite database backed by a persistent volume (policy-reporter-sqlite-pvc).
To access the interactive Policy Reporter web dashboard:
kubectl port-forward -n policy-reporter svc/policy-reporter-ui 8080:8080Open http://localhost:8080 in your browser to view real-time Kyverno policy reports, audit logs, and compliance metrics.
The cluster includes an ephemeral SpiceDB instance managed by the SpiceDB Operator (clusters/kind/spicedb-operator.yaml & clusters/kind/spicedb-cluster.yaml) and an admission gate policy (clusters/kind/clusterpolicy-spicedb-authz.yaml):
- OCI Deployer Metadata: When workflows build container images and package values artifacts, they embed the triggering actor (
dev.authz.app.deployer) in the OCI labels. - Admission Gate Check: When Flux reconciles a deployment, Kyverno extracts the deployer identity and queries SpiceDB's
/v1/permissions/checkAPI to verify if the actor hasdeploypermissions on the service. - Human-Readable Fixtures (
fixtures/spicedb/):fixtures/spicedb/schema.zed: Standard SpiceDB schema definition using.zedsyntax.fixtures/spicedb/relationships.txt: Line-delimited relationship tuples (resource#relation@subject).
- Experimenting & Testing Permissions:
# Port-forward SpiceDB HTTP API
kubectl port-forward -n authz svc/spicedb 8443:8443
# Check if user 'magnusp' has deploy permission
./scripts/spicedb-fixture.sh check magnusp
# Check an unauthorized user
./scripts/spicedb-fixture.sh check unauthorized-dev
# Edit fixtures/spicedb/relationships.txt or schema.zed, then apply:
./scripts/spicedb-fixture.sh applyAll published OCI artifacts (charts, images, and values) include GitHub Actions build provenance attestations.
To verify that an artifact was produced by an authentic repository workflow using the GitHub CLI:
# Verify platform Helm chart
gh attestation verify oci://ghcr.io/magnusp/charts/<chart-name>:<version> --owner magnusp
# Verify application container image
gh attestation verify oci://ghcr.io/magnusp/apps/archetype-backend:<commit-sha> --owner magnusp
# Verify application values artifact
gh attestation verify oci://ghcr.io/magnusp/apps/archetype-backend-values:latest --owner magnuspWhile this repository demonstrates GitHub Actions with GitHub OIDC, the same Kyverno and Flux architecture adapts directly to bare-metal / on-premises clusters using modern Identity Providers (Entra ID, Google Workspace, GitHub, Okta, Keycloak) without requiring cloud-hosted Kubernetes (EKS/GKE/AKS) or cloud KMS:
| Pattern | Signing & Identity Mechanism | Kyverno Verification Mechanism |
|---|---|---|
| Developer Workstation CLI | Cosign with Corporate OIDC (Microsoft Entra ID, Google Workspace, GitHub) + Public Sigstore Rekor |
verifyImages keyless rule matching corporate issuer (e.g. login.microsoftonline.com, accounts.google.com) and user email regex. |
| Self-Hosted CI Runners | Bare-metal runners (GitLab CI, Jenkins, Drone) signing via HashiCorp Vault Transit Engine or local Cosign keys |
verifyImages rule checking static public keys stored in a Kubernetes Secret or fetched from on-prem Vault. |
| ChatOps / Webhooks | Slack / Mattermost webhook Receiver carrying triggering user email |
Kyverno apiCall querying in-cluster SpiceDB to verify if the user has deploy permissions on the service. |
Direct kubectl Access |
Entra ID / Google / Keycloak OIDC Kubeconfig | Kyverno validation evaluating request.userInfo.username against SpiceDB ReBAC; blocks direct production edits in favor of GitOps. |
| Automated Dependency Bots | Renovate / Dependabot with dedicated bot keypair | Public key verification + OpenVEX / in-toto vulnerability scan conditions. |
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-corporate-oidc-attestations
spec:
validationFailureAction: Enforce
rules:
- name: verify-developer-identity
match:
any:
- resources:
kinds: [Deployment]
namespaceSelector:
matchLabels:
governance.platform.io/managed: "true"
verifyImages:
- imageReferences: ["ghcr.io/magnusp/apps/*"]
attestations:
- type: "https://slsa.dev/provenance/v1"
attestors:
- entries:
- keyless:
# Microsoft Entra ID, Google Workspace, or GitHub
issuer: "https://login.microsoftonline.com/<tenant-id>/v2.0"
subjectRegExp: ".*@company.com"
rekor:
url: "https://rekor.sigstore.dev"