[RFC-0010] Add multi-tenancy lockdown for decryption and kubeconfig

Adds two new controller flags to enforce ServiceAccount usage in
multi-tenant clusters where administrators need to lock down workload
identity access:

- --default-decryption-service-account
- --default-kubeconfig-service-account

These flags complement the existing --default-service-account flag to
provide complete multi-tenancy lockdown coverage for all three classes
of ServiceAccount fields in the Kustomization API.

Signed-off-by: cappyzawa <cappyzawa@gmail.com>
This commit is contained in:
cappyzawa 2025-08-17 07:58:18 +09:00
parent aaad1e033e
commit c5f0efdced
No known key found for this signature in database
5 changed files with 53 additions and 36 deletions

4
go.mod
View File

@ -24,11 +24,11 @@ require (
github.com/fluxcd/pkg/apis/event v0.18.0
github.com/fluxcd/pkg/apis/kustomize v1.11.0
github.com/fluxcd/pkg/apis/meta v1.18.0
github.com/fluxcd/pkg/auth v0.23.0
github.com/fluxcd/pkg/auth v0.26.0
github.com/fluxcd/pkg/cache v0.10.0
github.com/fluxcd/pkg/http/fetch v0.17.0
github.com/fluxcd/pkg/kustomize v1.19.0
github.com/fluxcd/pkg/runtime v0.72.0
github.com/fluxcd/pkg/runtime v0.80.0
github.com/fluxcd/pkg/ssa v0.51.0
github.com/fluxcd/pkg/tar v0.13.0
github.com/fluxcd/pkg/testserver v0.11.0

8
go.sum
View File

@ -199,8 +199,8 @@ github.com/fluxcd/pkg/apis/kustomize v1.11.0 h1:0IzDgxZkc4v+5SDNCvgZhfwfkdkQLPXC
github.com/fluxcd/pkg/apis/kustomize v1.11.0/go.mod h1:j302mJGDww8cn9qvMsRQ0LJ1HPAPs/IlX7CSsoJV7BI=
github.com/fluxcd/pkg/apis/meta v1.18.0 h1:ACHrMIjlcioE9GKS7NGk62KX4NshqNewr8sBwMcXABs=
github.com/fluxcd/pkg/apis/meta v1.18.0/go.mod h1:97l3hTwBpJbXBY+wetNbqrUsvES8B1jGioKcBUxmqd8=
github.com/fluxcd/pkg/auth v0.23.0 h1:Xt89QO1Hzh7X0JFwCeONyxMlgOX/zOPx0eyIyFoKyF0=
github.com/fluxcd/pkg/auth v0.23.0/go.mod h1:YEAHpBFuW5oLlH9ekuJaQdnJ2Q3A7Ny8kha3WY7QMnY=
github.com/fluxcd/pkg/auth v0.26.0 h1:jw128zPI4aRSvkGbFfAQcFNF3oK58P4rDdKIpj2/7yM=
github.com/fluxcd/pkg/auth v0.26.0/go.mod h1:YEAHpBFuW5oLlH9ekuJaQdnJ2Q3A7Ny8kha3WY7QMnY=
github.com/fluxcd/pkg/cache v0.10.0 h1:M+OGDM4da1cnz7q+sZSBtkBJHpiJsLnKVmR9OdMWxEY=
github.com/fluxcd/pkg/cache v0.10.0/go.mod h1:pPXRzQUDQagsCniuOolqVhnAkbNgYOg8d2cTliPs7ME=
github.com/fluxcd/pkg/envsubst v1.4.0 h1:pYsb6wrmXOSfHXuXQHaaBBMt3LumhgCb8SMdBNAwV/U=
@ -209,8 +209,8 @@ github.com/fluxcd/pkg/http/fetch v0.17.0 h1:U/Fuh+H1cRL2d/EOfdsjJPaPDPtL3pFanPSE
github.com/fluxcd/pkg/http/fetch v0.17.0/go.mod h1:nMozZtiSKtPGwMrR5wGjIJoQmhvFqZ5P4UsM/Lqza2I=
github.com/fluxcd/pkg/kustomize v1.19.0 h1:2eO8lMx0/H/Yyq35LMTAMhxEElOzMW0Yi9zUNZoimlU=
github.com/fluxcd/pkg/kustomize v1.19.0/go.mod h1:OCCW9vU3lStDh3jyg9MM/a29MSdNAVk2wjl0lDos5Fs=
github.com/fluxcd/pkg/runtime v0.72.0 h1:9JCto84iL2FziuTuuvDwvS+cfIzGhHOk25y8ulXpNOs=
github.com/fluxcd/pkg/runtime v0.72.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw=
github.com/fluxcd/pkg/runtime v0.80.0 h1:vknT2vdQSGTFnAhz4xGk2ZXUWCrXh3whsISStgA57Go=
github.com/fluxcd/pkg/runtime v0.80.0/go.mod h1:iGhdaEq+lMJQTJNAFEPOU4gUJ7kt3yeDcJPZy7O9IUw=
github.com/fluxcd/pkg/sourceignore v0.13.0 h1:ZvkzX2WsmyZK9cjlqOFFW1onHVzhPZIqDbCh96rPqbU=
github.com/fluxcd/pkg/sourceignore v0.13.0/go.mod h1:Z9H1GoBx0ljOhptnzoV0PL6Nd/UzwKcSphP27lqb4xI=
github.com/fluxcd/pkg/ssa v0.51.0 h1:sFarxKZcS0J8sjq9qvs/r+1XiJqNgRodEiPjV75F8R4=

View File

@ -178,7 +178,8 @@ data: {}
t.Run("object level workload identity feature gate enabled", func(t *testing.T) {
g := NewWithT(t)
t.Setenv(auth.EnvVarEnableObjectLevelWorkloadIdentity, "true")
auth.EnableObjectLevelWorkloadIdentity()
t.Cleanup(auth.DisableObjectLevelWorkloadIdentity)
kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("invalid-config-%s", randStringRunes(5)),

View File

@ -316,14 +316,17 @@ func (d *Decryptor) SetAuthOptions(ctx context.Context) {
switch d.kustomization.Spec.Decryption.Provider {
case DecryptionProviderSOPS:
var opts []auth.Option
if d.kustomization.Spec.Decryption.ServiceAccountName != "" {
serviceAccount := types.NamespacedName{
Name: d.kustomization.Spec.Decryption.ServiceAccountName,
Namespace: d.kustomization.GetNamespace(),
opts := []auth.Option{
auth.WithClient(d.client),
}
opts = append(opts, auth.WithServiceAccount(serviceAccount, d.client))
saName := d.kustomization.Spec.Decryption.ServiceAccountName
if saName == "" {
saName = auth.GetDefaultDecryptionServiceAccount()
}
if saName != "" {
opts = append(opts, auth.WithServiceAccountName(saName))
opts = append(opts, auth.WithServiceAccountNamespace(d.kustomization.GetNamespace()))
}
involvedObject := cache.InvolvedObject{

15
main.go
View File

@ -97,6 +97,8 @@ func main() {
noRemoteBases bool
httpRetry int
defaultServiceAccount string
defaultDecryptionServiceAccount string
defaultKubeConfigServiceAccount string
sopsAgeSecret string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
@ -112,7 +114,9 @@ func main() {
flag.BoolVar(&noRemoteBases, "no-remote-bases", false,
"Disallow remote bases usage in Kustomize overlays. When this flag is enabled, all resources must refer to local files included in the source artifact.")
flag.IntVar(&httpRetry, "http-retry", 9, "The maximum number of retries when failing to fetch artifacts over HTTP.")
flag.StringVar(&defaultServiceAccount, "default-service-account", "", "Default service account used for impersonation.")
flag.StringVar(&defaultServiceAccount, auth.ControllerFlagDefaultServiceAccount, "", "Default service account used for impersonation.")
flag.StringVar(&defaultDecryptionServiceAccount, auth.ControllerFlagDefaultDecryptionServiceAccount, "", "Default service account used for decryption.")
flag.StringVar(&defaultKubeConfigServiceAccount, auth.ControllerFlagDefaultKubeConfigServiceAccount, "", "Default service account used for kubeconfig.")
flag.StringVar(&sopsAgeSecret, "sops-age-secret", "", "The name of a Kubernetes secret in the RUNTIME_NAMESPACE containing a SOPS age decryption key for fallback usage.")
flag.StringArrayVar(&disallowedFieldManagers, "override-manager", []string{}, "Field manager disallowed to perform changes on managed resources.")
@ -146,6 +150,15 @@ func main() {
auth.EnableObjectLevelWorkloadIdentity()
}
// NOTE: defaultServiceAccount is used for regular impersonation, not workload identity lockdown
if defaultDecryptionServiceAccount != "" {
auth.SetDefaultDecryptionServiceAccount(defaultDecryptionServiceAccount)
}
if defaultKubeConfigServiceAccount != "" {
auth.SetDefaultKubeConfigServiceAccount(defaultKubeConfigServiceAccount)
}
if err := intervalJitterOptions.SetGlobalJitter(nil); err != nil {
setupLog.Error(err, "unable to set global jitter")
os.Exit(1)