{"id":13521804,"url":"https://github.com/fluxcd/flux2-multi-tenancy","last_synced_at":"2025-05-15T05:03:30.089Z","repository":{"id":37029868,"uuid":"316442097","full_name":"fluxcd/flux2-multi-tenancy","owner":"fluxcd","description":"Manage multi-tenant clusters with Flux","archived":false,"fork":false,"pushed_at":"2025-04-03T00:06:29.000Z","size":196,"stargazers_count":521,"open_issues_count":7,"forks_count":266,"subscribers_count":21,"default_branch":"main","last_synced_at":"2025-04-14T22:14:52.444Z","etag":null,"topics":["example","flux2","fluxcd","gitops","kubernetes","multitenancy"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fluxcd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-27T08:24:15.000Z","updated_at":"2025-04-13T19:35:59.000Z","dependencies_parsed_at":"2024-01-06T09:26:09.472Z","dependency_job_id":"85b2c95d-d137-4793-8f35-763cbdc16c7c","html_url":"https://github.com/fluxcd/flux2-multi-tenancy","commit_stats":{"total_commits":124,"total_committers":22,"mean_commits":5.636363636363637,"dds":0.6048387096774194,"last_synced_commit":"24a3735b1da45bedeb8668b94a251dd679a261b1"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluxcd%2Fflux2-multi-tenancy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluxcd%2Fflux2-multi-tenancy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluxcd%2Fflux2-multi-tenancy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fluxcd%2Fflux2-multi-tenancy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fluxcd","download_url":"https://codeload.github.com/fluxcd/flux2-multi-tenancy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248968917,"owners_count":21191162,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["example","flux2","fluxcd","gitops","kubernetes","multitenancy"],"created_at":"2024-08-01T06:00:38.165Z","updated_at":"2025-04-14T22:14:58.828Z","avatar_url":"https://github.com/fluxcd.png","language":"Shell","funding_links":[],"categories":["kubernetes","Shell","Tutorials"],"sub_categories":["Secrets"],"readme":"# flux2-multi-tenancy\n\n[![test](https://github.com/fluxcd/flux2-multi-tenancy/workflows/test/badge.svg)](https://github.com/fluxcd/flux2-multi-tenancy/actions)\n[![e2e](https://github.com/fluxcd/flux2-multi-tenancy/workflows/e2e/badge.svg)](https://github.com/fluxcd/flux2-multi-tenancy/actions)\n[![license](https://img.shields.io/github/license/fluxcd/flux2-multi-tenancy.svg)](https://github.com/fluxcd/flux2-multi-tenancy/blob/main/LICENSE)\n\nThis repository serves as a starting point for managing multi-tenant clusters with Git and Flux v2.\n\n![](docs/img/flux2-multi-tenancy.png)\n\n## Roles\n\n**Platform Admin**\n\n- Has cluster admin access to the fleet of clusters\n- Has maintainer access to the fleet Git repository\n- Manages cluster wide resources (CRDs, controllers, cluster roles, etc)\n- Onboards the tenant’s main `GitRepository` and `Kustomization` \n- Manages tenants by assigning namespaces, service accounts and role binding to the tenant's apps\n\n**Tenant** \n\n- Has admin access to the namespaces assigned to them by the platform admin\n- Has maintainer access to the tenant Git repository and apps repositories \n- Manages app deployments with `GitRepositories` and `Kustomizations`\n- Manages app releases with `HelmRepositories` and `HelmReleases`\n\n## Repository structure\n\nThe [platform admin repository](https://github.com/fluxcd/flux2-multi-tenancy/tree/main) contains the following top directories:\n\n- **clusters** dir contains the Flux configuration per cluster\n- **infrastructure** dir contains common infra tools such as admission controllers, CRDs and cluster-wide polices\n- **tenants** dir contains namespaces, service accounts, role bindings and Flux custom resources for registering tenant repositories\n\n```\n├── clusters\n│   ├── production\n│   └── staging\n├── infrastructure\n│   ├── kyverno\n│   └── kyverno-policies\n└── tenants\n    ├── base\n    ├── production\n    └── staging\n```\n\nA [tenant repository](https://github.com/fluxcd/flux2-multi-tenancy/tree/dev-team) contains the following top directories:\n\n- **base** dir contains `HelmRepository` and `HelmRelease` manifests\n- **staging** dir contains `HelmRelease` Kustomize patches for deploying pre-releases on the staging cluster\n- **production** dir contains `HelmRelease` Kustomize patches for deploying stable releases on the production cluster\n\n```\n├── base\n│   ├── kustomization.yaml\n│   ├── podinfo-release.yaml\n│   └── podinfo-repository.yaml\n├── production\n│   ├── kustomization.yaml\n│   └── podinfo-values.yaml\n└── staging\n    ├── kustomization.yaml\n    └── podinfo-values.yaml\n```\n\n## Bootstrap the staging cluster\n\nInstall the Flux CLI and fork this repository on your personal GitHub account\nand export your GitHub username and repo name:\n\n```sh\nexport GITHUB_USER=\u003cyour-username\u003e\nexport GITHUB_REPO=\u003crepository-name\u003e\n```\n\nVerify that your staging cluster satisfies the prerequisites with:\n\n```sh\nflux check --pre\n```\n\nSet the `--context` argument to the kubectl context to your staging cluster and bootstrap Flux:\n\n```sh\nflux bootstrap github \\\n    --context=your-staging-context \\\n    --owner=${GITHUB_USER} \\\n    --repository=${GITHUB_REPO} \\\n    --branch=main \\\n    --personal \\\n    --path=clusters/staging\n```\n\nAt this point flux cli will ask you for your `GITHUB_TOKEN` (a.k.a [Personal Access Token]).\n\n\u003e **NOTE:** The `GITHUB_TOKEN` is used exclusively by the flux CLI during the bootstrapping process,\n\u003e and does not leave your machine. The credential is used for\n\u003e configuring the GitHub repository and registering the deploy key.\n\nThe bootstrap command commits the manifests for the Flux components in `clusters/staging/flux-system` dir\nand creates a deploy key with read-only access on GitHub, so it can pull changes inside the cluster.\n\nWait for the staging cluster reconciliation to finish:\n\n```console\n$ flux get kustomizations --watch\nNAME            \tREADY  \tMESSAGE                                                        \t\nflux-system     \tTrue   \tApplied revision: main/616001c38e7bc81b00ef2c65ac8cfd58140155b8\t\nkyverno         \tUnknown\tReconciliation in progress\nkyverno-policies\tFalse  \tDependency 'flux-system/kyverno' is not ready\ntenants         \tFalse  \tDependency 'flux-system/kyverno-policies' is not ready\n```\n\nVerify that the tenant Git repository has been cloned:\n\n```console\n$ flux -n apps get sources git\nNAME    \tREADY\tMESSAGE \ndev-team\tTrue \tFetched revision: dev-team/ca8ec25405cc03f2f374d2f35f9299d84ced01e4\n```\n\nVerify that the tenant Helm repository index has been downloaded:\n\n```console\n$ flux -n apps get sources helm\nNAME   \tREADY\tMESSAGE\npodinfo\tTrue \tFetched revision: 2022-05-23T10:09:58.648748663Z\n```\n\nWait for the demo app to be installed:\n\n```console\n$ watch flux -n apps get helmreleases\nNAME   \tREADY\tMESSAGE                         \tREVISION\tSUSPENDED \npodinfo\tTrue \tRelease reconciliation succeeded\t5.0.3   \tFalse \n```\n\nTo expand on this example, check the [enforce tenant isolation](#enforce-tenant-isolation) for security related considerations. \n\n[Personal Access Token]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token\n\n### Onboard new tenants\n\nThe Flux CLI offers commands to generate the Kubernetes manifests needed to define tenants.\n\nAssuming a platform admin wants to create a tenant named `dev-team` with access to the `apps` namespace.\n\nCreate the tenant base directory:\n\n```sh\nmkdir -p ./tenants/base/dev-team\n```\n\nGenerate the namespace, service account and role binding for the dev-team:\n\n```sh\nflux create tenant dev-team --with-namespace=apps \\\n    --export \u003e ./tenants/base/dev-team/rbac.yaml\n```\n\nCreate the sync manifests for the tenant Git repository:\n\n```sh\nflux create source git dev-team \\\n    --namespace=apps \\\n    --url=https://github.com/\u003corg\u003e/\u003cdev-team\u003e \\\n    --branch=main \\\n    --export \u003e ./tenants/base/dev-team/sync.yaml\n\nflux create kustomization dev-team \\\n    --namespace=apps \\\n    --service-account=dev-team \\\n    --source=GitRepository/dev-team \\\n    --path=\"./\" \\\n    --export \u003e\u003e ./tenants/base/dev-team/sync.yaml\n```\n\nCreate the base `kustomization.yaml` file:\n\n```sh\ncd ./tenants/base/dev-team/ \u0026\u0026 kustomize create --autodetect --namespace apps \n```\n\nCreate the staging overlay and set the path to the staging dir inside the tenant repository:\n\n```sh\ncat \u003c\u003c EOF | tee ./tenants/staging/dev-team-patch.yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: dev-team\n  namespace: apps\nspec:\n  path: ./staging\nEOF\n\ncat \u003c\u003c EOF | tee ./tenants/staging/kustomization.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nnamespace: apps\nresources:\n  - ../base/dev-team\npatches:\n  - path: dev-team-patch.yaml\nEOF\n```\n\nWith the above configuration, the Flux instance running on the staging cluster will clone the\ndev-team's repository, and it will reconcile the `./staging` directory from the tenant's repo\nusing the `dev-team` service account. Since that service account is restricted to the `apps` namespace,\nthe dev-team repository must contain Kubernetes objects scoped to the `apps` namespace only.\n\n#### Tenant onboarding via Kyverno\n\nAlternatively to the `flux create tenant` approach, Kyverno's [resource generation] feature can\nbe leveraged to the same effect.\n\n[resource generation]: https://kyverno.io/docs/writing-policies/generate/\n\n## Enforce tenant isolation\n\nTo enforce tenant isolation, cluster admins must configure Flux to reconcile \nthe `Kustomization` and `HelmRelease` kinds by impersonating a service account\nfrom the namespace where these objects are created. \n\nFlux has built-in [multi-tenancy lockdown] features which enables tenant isolation \nat Control Plane level without the need of external admission controllers (e.g. Kyverno). The\nrecommended patch:\n\n- Enforce controllers to block cross namespace references.\n  Meaning that a tenant can’t use another tenant’s sources or subscribe to their events.\n- Deny accesses to Kustomize remote bases, thus ensuring all resources refer to local files. \n  Meaning that only approved Flux Sources can affect the cluster-state.\n- Sets a default service account via `--default-service-account` to `kustomize-controller` and `helm-controller`.\n  Meaning that, if a tenant does not specify a service account in a Flux `Kustomization` or \n  `HelmRelease`, it would automatically default to said account. \n\n\u003e **NOTE:** It is recommended that the default service account has no privileges.\n\u003e And each named service account used observes the least privilege model.\n\nThis repository applies this patch automatically via\n[kustomization.yaml](clusters/production/flux-system/kustomization.yaml) in both clusters.\n\n```yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n  - gotk-components.yaml\n  - gotk-sync.yaml\npatches:\n  - patch: |\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --no-cross-namespace-refs=true\n    target:\n      kind: Deployment\n      name: \"(kustomize-controller|helm-controller|notification-controller|image-reflector-controller|image-automation-controller)\"\n  - patch: |\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --no-remote-bases=true\n    target:\n      kind: Deployment\n      name: \"kustomize-controller\"\n  - patch: |\n      - op: add\n        path: /spec/template/spec/containers/0/args/-\n        value: --default-service-account=default\n    target:\n      kind: Deployment\n      name: \"(kustomize-controller|helm-controller)\"\n  - patch: |\n      - op: add\n        path: /spec/serviceAccountName\n        value: kustomize-controller\n    target:\n      kind: Kustomization\n      name: \"flux-system\"\n```\n\n### Side Effects\n\nWhen Flux is bootstrapped with the patch both `kustomize-controller` and `helm-controller` will impersonate the `default`\nservice account in the tenant namespace when applying changes to the cluster. The `default` service account \nexist in all namespaces and should always be kept without any privileges.\n\nTo enable a tenant to operate, a service account must be created with the required permissions and its name set \nto the `spec.serviceAccountName` of all `Kustomization` and `HelmRelease` resources the tenant has.\n\n### Tenancy policies\n\nDepending on the aimed security posture, the Platform Admin may impose additional policies to enforce specific \nbehaviours. Below are a few consideration points, some of which are already implemented in this repository.\n\n#### Image provenance\n\nAssuring the provenance of container images across a cluster can be achieved on several ways.\n\nThe [verify-flux-images policy](infrastructure/kyverno-policies/verify-flux-images.yaml)\nensures that all Flux images used are the ones built and signed by the Flux team:\n\n```yaml\napiVersion: kyverno.io/v1\nkind: ClusterPolicy\nmetadata:\n  name: verify-flux-images\nspec:\n  validationFailureAction: enforce\n  background: false\n  webhookTimeoutSeconds: 30\n  failurePolicy: Fail\n  rules:\n    - name: verify-cosign-signature\n      match:\n        resources:\n          kinds:\n            - Pod\n      verifyImages:\n        - imageReferences:\n            - \"ghcr.io/fluxcd/source-controller:*\"\n            - \"ghcr.io/fluxcd/kustomize-controller:*\"\n            - \"ghcr.io/fluxcd/helm-controller:*\"\n            - \"ghcr.io/fluxcd/notification-controller:*\"\n          attestors:\n            - entries:\n                - keyless:\n                    subject: \"https://github.com/fluxcd/*\"\n                    issuer: \"https://token.actions.githubusercontent.com\"\n                    rekor:\n                      url: https://rekor.sigstore.dev\n```\n\nOther policies to explore:\n- Restrict what repositories can be accessed in each cluster. Some deployments may need this to be environment-specific.\n- Align image policies with pods that require `securityContext` that are highly privileged.\n\n#### Flux Sources\n\nFlux uses sources to define the origin of flux manifests. Some deployments may require that \nall of them come from a specific GitHub Organisation, as the\n[verify-git-repositories policy](infrastructure/kyverno-policies/verify-git-repositories.yaml) shows:\n\n```yaml\napiVersion: kyverno.io/v1\nkind: ClusterPolicy\nmetadata:\n  name: verify-git-repositories\nspec:\n  validationFailureAction: audit # Change to 'enforce' once the specific org url is set.\n  rules:\n    - name: github-repositories-only\n      exclude:\n        resources:\n          namespaces:\n            - flux-system\n      match:\n        resources:\n          kinds:\n            - GitRepository\n      validate:\n        message: \".spec.url must be from a repository within the organisation X\"\n        anyPattern:\n        - spec:\n            url: \"https://github.com/fluxcd/?*\" # repositories in fluxcd via https\n        - spec:\n            url: \"ssh://git@github.com:fluxcd/?*\" # repositories in fluxcd via ssh\n```\n\nOther policies to explore:\n- Expand the policies to `HelmRepository` and `Bucket`.\n- For `HelmRepository` and `GitRepository` consider which protocols should be allowed.\n- For `Bucket`, consider restrictions on providers and regions.\n\n#### Make serviceAccountName mandatory\n\nThe lockdown patch sets a default service account that is applied to any `Kustomization` and `HelmRelease` \ninstances that have no `spec.ServiceAccountName` set.\n\nIf the recommended best practices above are followed, such instances won't be able to apply changes to\na cluster as the default service account has no permissions to do so. \n\nAn additional extra could be taken to make the `spec.ServiceAccountName` field  mandatory via a validation \nwebhook, for example [Kyverno](https://github.com/kyverno/kyverno) or\n[OPA Gatekeeper](https://github.com/open-policy-agent/gatekeeper).\nResulting on `Kustomization` and `HelmRelease` instances not being admitted when `spec.ServiceAccountName` is not set.\n\n#### Reconciliation hierarchy\n\nOn cluster bootstrap, you need to configure Flux to deploy the validation webhook and its policies before \nreconciling the tenants repositories.\n\nInside the `clusters` dir we define in which order the infrastructure items,\nand the tenant workloads are going to be reconciled on the staging and production clusters:\n\n```\n./clusters/\n├── production\n│   ├── infrastructure.yaml\n│   └── tenants.yaml\n└── staging\n    ├── infrastructure.yaml\n    └── tenants.yaml\n```\n\nFirst we setup the reconciliation of custom resource definitions and their controllers. For this \nexample we'll use [Kyverno](https://github.com/kyverno/kyverno):\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: kyverno\n  namespace: flux-system\nspec:\n  interval: 10m\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  path: ./infrastructure/kyverno\n  prune: true\n  wait: true\n  timeout: 5m\n```\n\nThen we setup [cluster policies](./infrastructure/kyverno-policies/verify-git-repositories.yaml) \n(Kyverno custom resources) to enforce a specific GitHub Organisation:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: kyverno-policies\n  namespace: flux-system\nspec:\n  dependsOn:\n    - name: kyverno\n  interval: 5m\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  path: ./infrastructure/kyverno-policies\n  prune: true\n```\n\nWith `dependsOn` we tell Flux to install Kyverno before deploying the cluster policies.\n\nAnd finally we setup the reconciliation for the tenants workloads with:\n\n```yaml\napiVersion: kustomize.toolkit.fluxcd.io/v1\nkind: Kustomization\nmetadata:\n  name: tenants\n  namespace: flux-system\nspec:\n  dependsOn:\n    - name: kyverno-policies\n  interval: 5m\n  sourceRef:\n    kind: GitRepository\n    name: flux-system\n  path: ./tenants/staging\n  prune: true\n```\n\nWith the above configuration, we ensure that the Kyverno validation webhook will reject `GitRepository`\nthat don't originate from a specific GitHub Organisation, in our case `fluxcd`.\n\n## Onboard tenants with private repositories\n\nYou can configure Flux to connect to a tenant repository\nusing SSH or token-based authentication. The tenant credentials will be stored \nin the platform admin repository as a Kubernetes secret. \n\n### Encrypt Kubernetes secrets in Git\n\nIn order to store credentials safely in a Git repository, you can use Mozilla's\nSOPS CLI to encrypt Kubernetes secrets with OpenPGP, Age or KMS.\n\nInstall [gnupg](https://www.gnupg.org/) and [sops](https://github.com/mozilla/sops):\n\n```sh\nbrew install gnupg sops\n```\n\nGenerate a GPG key for Flux without specifying a passphrase and retrieve the GPG key ID:\n\n```console\n$ gpg --full-generate-key\nEmail address: fluxcdbot@users.noreply.github.com\n\n$ gpg --list-secret-keys fluxcdbot@users.noreply.github.com\nsec   rsa3072 2020-09-06 [SC]\n      1F3D1CED2F865F5E59CA564553241F147E7C5FA4\n```\n\nCreate a Kubernetes secret in the `flux-system` namespace with the GPG private key:\n\n```sh\ngpg --export-secret-keys \\\n--armor 1F3D1CED2F865F5E59CA564553241F147E7C5FA4 |\nkubectl create secret generic sops-gpg \\\n--namespace=flux-system \\\n--from-file=sops.asc=/dev/stdin\n```\n\nYou should store the GPG private key in a safe place for disaster recovery,\nin case you need to rebuild the cluster from scratch.\nThe GPG public key can be shared with the platform team, so anyone with \nwrite access to the platform repository can encrypt secrets.\n\n### Git over SSH\n\nGenerate a Kubernetes secret with the SSH and known host keys:\n\n```sh\nflux -n apps create secret git dev-team-auth \\\n    --url=ssh://git@github.com/\u003corg\u003e/\u003cdev-team\u003e \\\n    --export \u003e ./tenants/base/dev-team/auth.yaml\n```\n\nPrint the SSH public key and add it as a read-only deploy key to the dev-team repository:\n\n```sh\nyq eval '.stringData.\"identity.pub\"' ./tenants/base/dev-team/auth.yaml\n```\n\n### Git over HTTP/S\n\nGenerate a Kubernetes secret with basic auth credentials:\n\n```sh\nflux -n apps create secret git dev-team-auth \\\n    --url=https://github.com/\u003corg\u003e/\u003cdev-team\u003e \\\n    --username=$GITHUB_USERNAME \\\n    --password=$GITHUB_TOKEN \\\n    --export \u003e ./tenants/base/dev-team/auth.yaml\n```\n\nThe GitHub token must have read-only access to the dev-team repository.\n\n### Configure Git authentication\n\nEncrypt the `dev-team-auth` secret's data field with sops:\n\n```sh\nsops --encrypt \\\n    --pgp=1F3D1CED2F865F5E59CA564553241F147E7C5FA4 \\\n    --encrypted-regex '^(data|stringData)$' \\\n    --in-place ./tenants/base/dev-team/auth.yaml\n```\n\nCreate the sync manifests for the tenant Git repository referencing the `git-auth` secret:\n\n```sh\nflux create source git dev-team \\\n    --namespace=apps \\\n    --url=https://github.com/\u003corg\u003e/\u003cdev-team\u003e \\\n    --branch=main \\\n    --secret-ref=dev-team-auth \\\n    --export \u003e ./tenants/base/dev-team/sync.yaml\n\nflux create kustomization dev-team \\\n    --namespace=apps \\\n    --service-account=dev-team \\\n    --source=GitRepository/dev-team \\\n    --path=\"./\" \\\n    --export \u003e\u003e ./tenants/base/dev-team/sync.yaml\n```\n\nCreate the base kustomization.yaml file:\n\n```sh\ncd ./tenants/base/dev-team/ \u0026\u0026 kustomize create --autodetect\n```\n\nConfigure Flux to decrypt secrets using the `sops-gpg` key:\n\n```yaml\nflux create kustomization tenants \\\n  --depends-on=kyverno-policies \\\n  --source=flux-system \\\n  --path=\"./tenants/staging\" \\\n  --prune=true \\\n  --interval=5m \\\n  --validation=client \\\n  --decryption-provider=sops \\\n  --decryption-secret=sops-gpg \\\n  --export \u003e ./clusters/staging/tenants.yaml\n```\n\nWith the above configuration, the Flux instance running on the staging cluster will:\n\n* create the tenant namespace, service account and role binding\n* decrypt the tenant Git credentials using the GPG private key\n* create the tenant Git credentials Kubernetes secret in the tenant namespace\n* clone the tenant repository using the supplied credentials\n* apply the `./staging` directory from the tenant's repo using the tenant's service account\n\n## Testing\n\nAny change to the Kubernetes manifests or to the repository structure should be validated in CI before\na pull request is merged into the main branch and synced on the cluster.\n\nThis repository contains the following GitHub CI workflows:\n\n* the [test](./.github/workflows/test.yaml) workflow validates the Kubernetes manifests\n  and Kustomize overlays with [kubeconform](https://github.com/yannh/kubeconform)\n* the [e2e](./.github/workflows/e2e.yaml) workflow starts a Kubernetes cluster in CI\n  and tests the staging setup by running Flux in Kubernetes Kind\n\n\n[multi-tenancy lockdown]: https://fluxcd.io/flux/installation/configuration/multitenancy/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluxcd%2Fflux2-multi-tenancy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffluxcd%2Fflux2-multi-tenancy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffluxcd%2Fflux2-multi-tenancy/lists"}