[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
opts := []auth.Option{
auth.WithClient(d.client),
}
if d.kustomization.Spec.Decryption.ServiceAccountName != "" {
serviceAccount := types.NamespacedName{
Name: d.kustomization.Spec.Decryption.ServiceAccountName,
Namespace: d.kustomization.GetNamespace(),
}
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{

57
main.go
View File

@ -80,27 +80,29 @@ func main() {
)
var (
metricsAddr string
eventsAddr string
healthAddr string
concurrent int
concurrentSSA int
requeueDependency time.Duration
clientOptions runtimeClient.Options
kubeConfigOpts runtimeClient.KubeConfigOptions
logOptions logger.Options
leaderElectionOptions leaderelection.Options
rateLimiterOptions runtimeCtrl.RateLimiterOptions
watchOptions runtimeCtrl.WatchOptions
intervalJitterOptions jitter.IntervalOptions
aclOptions acl.Options
noRemoteBases bool
httpRetry int
defaultServiceAccount string
sopsAgeSecret string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
tokenCacheOptions pkgcache.TokenFlags
metricsAddr string
eventsAddr string
healthAddr string
concurrent int
concurrentSSA int
requeueDependency time.Duration
clientOptions runtimeClient.Options
kubeConfigOpts runtimeClient.KubeConfigOptions
logOptions logger.Options
leaderElectionOptions leaderelection.Options
rateLimiterOptions runtimeCtrl.RateLimiterOptions
watchOptions runtimeCtrl.WatchOptions
intervalJitterOptions jitter.IntervalOptions
aclOptions acl.Options
noRemoteBases bool
httpRetry int
defaultServiceAccount string
defaultDecryptionServiceAccount string
defaultKubeConfigServiceAccount string
sopsAgeSecret string
featureGates feathelper.FeatureGates
disallowedFieldManagers []string
tokenCacheOptions pkgcache.TokenFlags
)
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
@ -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)