kube: configure proper account impersonation NS
Fixing a regression introduced in #480 which would always pick the namespace of the release. In addition, historically seen the configuration of the impersonation username while making use of a KubeConfig has never worked correctly, this has been adressed as well. Signed-off-by: Hidde Beydals <hello@hidde.co>
This commit is contained in:
		
							parent
							
								
									bf7406b2de
								
							
						
					
					
						commit
						d19b470412
					
				| 
						 | 
				
			
			@ -474,10 +474,13 @@ func (r *HelmReleaseReconciler) checkDependencies(hr v2.HelmRelease) error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (r *HelmReleaseReconciler) buildRESTClientGetter(ctx context.Context, hr v2.HelmRelease) (genericclioptions.RESTClientGetter, error) {
 | 
			
		||||
	opts := []kube.ClientGetterOption{kube.WithClientOptions(r.ClientOpts)}
 | 
			
		||||
	if hr.Spec.ServiceAccountName != "" {
 | 
			
		||||
		opts = append(opts, kube.WithImpersonate(hr.Spec.ServiceAccountName))
 | 
			
		||||
	opts := []kube.ClientGetterOption{
 | 
			
		||||
		kube.WithClientOptions(r.ClientOpts),
 | 
			
		||||
		// When ServiceAccountName is empty, it will fall back to the configured default.
 | 
			
		||||
		// If this is not configured either, this option will result in a no-op.
 | 
			
		||||
		kube.WithImpersonate(hr.Spec.ServiceAccountName, hr.GetNamespace()),
 | 
			
		||||
	}
 | 
			
		||||
	opts = append(opts)
 | 
			
		||||
	if hr.Spec.KubeConfig != nil {
 | 
			
		||||
		secretName := types.NamespacedName{
 | 
			
		||||
			Namespace: hr.GetNamespace(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,11 +33,12 @@ const (
 | 
			
		|||
 | 
			
		||||
// clientGetterOptions used to BuildClientGetter.
 | 
			
		||||
type clientGetterOptions struct {
 | 
			
		||||
	namespace          string
 | 
			
		||||
	kubeConfig         []byte
 | 
			
		||||
	impersonateAccount string
 | 
			
		||||
	clientOptions      client.Options
 | 
			
		||||
	kubeConfigOptions  client.KubeConfigOptions
 | 
			
		||||
	namespace            string
 | 
			
		||||
	kubeConfig           []byte
 | 
			
		||||
	impersonateAccount   string
 | 
			
		||||
	impersonateNamespace string
 | 
			
		||||
	clientOptions        client.Options
 | 
			
		||||
	kubeConfigOptions    client.KubeConfigOptions
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientGetterOption configures a genericclioptions.RESTClientGetter.
 | 
			
		||||
| 
						 | 
				
			
			@ -61,10 +62,12 @@ func WithClientOptions(opts client.Options) func(o *clientGetterOptions) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// WithImpersonate configures the genericclioptions.RESTClientGetter to
 | 
			
		||||
// impersonate the provided account name.
 | 
			
		||||
func WithImpersonate(accountName string) func(o *clientGetterOptions) {
 | 
			
		||||
// impersonate with the given account name in the provided namespace.
 | 
			
		||||
// If the account name is empty, DefaultServiceAccountName is assumed.
 | 
			
		||||
func WithImpersonate(accountName, namespace string) func(o *clientGetterOptions) {
 | 
			
		||||
	return func(o *clientGetterOptions) {
 | 
			
		||||
		o.impersonateAccount = accountName
 | 
			
		||||
		o.impersonateNamespace = namespace
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +83,7 @@ func BuildClientGetter(namespace string, opts ...ClientGetterOption) (genericcli
 | 
			
		|||
		opt(o)
 | 
			
		||||
	}
 | 
			
		||||
	if len(o.kubeConfig) > 0 {
 | 
			
		||||
		return NewMemoryRESTClientGetter(o.kubeConfig, namespace, o.impersonateAccount, o.clientOptions, o.kubeConfigOptions), nil
 | 
			
		||||
		return NewMemoryRESTClientGetter(o.kubeConfig, namespace, o.impersonateAccount, o.impersonateNamespace, o.clientOptions, o.kubeConfigOptions), nil
 | 
			
		||||
	}
 | 
			
		||||
	return NewInClusterRESTClientGetter(namespace, o.impersonateAccount, &o.clientOptions)
 | 
			
		||||
	return NewInClusterRESTClientGetter(namespace, o.impersonateAccount, o.impersonateNamespace, &o.clientOptions)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ users:`)
 | 
			
		|||
		cfgOpts := client.KubeConfigOptions{InsecureTLS: true}
 | 
			
		||||
		impersonate := "jane"
 | 
			
		||||
 | 
			
		||||
		getter, err := BuildClientGetter(namespace, WithClientOptions(clientOpts), WithKubeConfig(cfg, cfgOpts), WithImpersonate(impersonate))
 | 
			
		||||
		getter, err := BuildClientGetter(namespace, WithClientOptions(clientOpts), WithKubeConfig(cfg, cfgOpts), WithImpersonate(impersonate, ""))
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(getter).To(BeAssignableToTypeOf(&MemoryRESTClientGetter{}))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,8 @@ users:`)
 | 
			
		|||
 | 
			
		||||
		namespace := "a-namespace"
 | 
			
		||||
		impersonate := "frank"
 | 
			
		||||
		getter, err := BuildClientGetter(namespace, WithImpersonate(impersonate))
 | 
			
		||||
		impersonateNS := "other-namespace"
 | 
			
		||||
		getter, err := BuildClientGetter(namespace, WithImpersonate(impersonate, impersonateNS))
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,16 +94,17 @@ users:`)
 | 
			
		|||
		g.Expect(flags.Namespace).ToNot(BeNil())
 | 
			
		||||
		g.Expect(*flags.Namespace).To(Equal(namespace))
 | 
			
		||||
		g.Expect(flags.Impersonate).ToNot(BeNil())
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:a-namespace:frank"))
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:other-namespace:frank"))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("with DefaultServiceAccount", func(t *testing.T) {
 | 
			
		||||
	t.Run("with impersonate DefaultServiceAccount", func(t *testing.T) {
 | 
			
		||||
		g := NewWithT(t)
 | 
			
		||||
		ctrl.GetConfig = mockGetConfig
 | 
			
		||||
 | 
			
		||||
		namespace := "a-namespace"
 | 
			
		||||
		DefaultServiceAccountName = "frank"
 | 
			
		||||
		getter, err := BuildClientGetter(namespace)
 | 
			
		||||
		impersonateNS := "other-namespace"
 | 
			
		||||
		getter, err := BuildClientGetter(namespace, WithImpersonate("", impersonateNS))
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(getter).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,7 +112,7 @@ users:`)
 | 
			
		|||
		g.Expect(flags.Namespace).ToNot(BeNil())
 | 
			
		||||
		g.Expect(*flags.Namespace).To(Equal(namespace))
 | 
			
		||||
		g.Expect(flags.Impersonate).ToNot(BeNil())
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:a-namespace:frank"))
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal("system:serviceaccount:other-namespace:frank"))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,12 +35,12 @@ import (
 | 
			
		|||
// using genericclioptions.NewConfigFlags, and configures it with the server,
 | 
			
		||||
// authentication, impersonation, client options, and the provided namespace.
 | 
			
		||||
// It returns an error if it fails to retrieve a rest.Config.
 | 
			
		||||
func NewInClusterRESTClientGetter(namespace, impersonateAccount string, opts *client.Options) (genericclioptions.RESTClientGetter, error) {
 | 
			
		||||
func NewInClusterRESTClientGetter(namespace, impersonateAccount, impersonateNamespace string, opts *client.Options) (genericclioptions.RESTClientGetter, error) {
 | 
			
		||||
	cfg, err := controllerruntime.GetConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to get config for in-cluster REST client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	SetImpersonationConfig(cfg, namespace, impersonateAccount)
 | 
			
		||||
	SetImpersonationConfig(cfg, impersonateNamespace, impersonateAccount)
 | 
			
		||||
 | 
			
		||||
	flags := genericclioptions.NewConfigFlags(false)
 | 
			
		||||
	flags.APIServer = pointer.String(cfg.Host)
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +73,8 @@ type MemoryRESTClientGetter struct {
 | 
			
		|||
	namespace string
 | 
			
		||||
	// impersonateAccount configures the rest.ImpersonationConfig account name.
 | 
			
		||||
	impersonateAccount string
 | 
			
		||||
	// impersonateAccount configures the rest.ImpersonationConfig account namespace.
 | 
			
		||||
	impersonateNamespace string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewMemoryRESTClientGetter returns a MemoryRESTClientGetter configured with
 | 
			
		||||
| 
						 | 
				
			
			@ -81,15 +83,17 @@ type MemoryRESTClientGetter struct {
 | 
			
		|||
func NewMemoryRESTClientGetter(
 | 
			
		||||
	kubeConfig []byte,
 | 
			
		||||
	namespace string,
 | 
			
		||||
	impersonate string,
 | 
			
		||||
	impersonateAccount string,
 | 
			
		||||
	impersonateNamespace string,
 | 
			
		||||
	clientOpts client.Options,
 | 
			
		||||
	kubeConfigOpts client.KubeConfigOptions) genericclioptions.RESTClientGetter {
 | 
			
		||||
	return &MemoryRESTClientGetter{
 | 
			
		||||
		kubeConfig:         kubeConfig,
 | 
			
		||||
		namespace:          namespace,
 | 
			
		||||
		impersonateAccount: impersonate,
 | 
			
		||||
		clientOpts:         clientOpts,
 | 
			
		||||
		kubeConfigOpts:     kubeConfigOpts,
 | 
			
		||||
		kubeConfig:           kubeConfig,
 | 
			
		||||
		namespace:            namespace,
 | 
			
		||||
		impersonateAccount:   impersonateAccount,
 | 
			
		||||
		impersonateNamespace: impersonateNamespace,
 | 
			
		||||
		clientOpts:           clientOpts,
 | 
			
		||||
		kubeConfigOpts:       kubeConfigOpts,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -102,9 +106,7 @@ func (c *MemoryRESTClientGetter) ToRESTConfig() (*rest.Config, error) {
 | 
			
		|||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	cfg = client.KubeConfig(cfg, c.kubeConfigOpts)
 | 
			
		||||
	if c.impersonateAccount != "" {
 | 
			
		||||
		cfg.Impersonate = rest.ImpersonationConfig{UserName: c.impersonateAccount}
 | 
			
		||||
	}
 | 
			
		||||
	SetImpersonationConfig(cfg, c.impersonateNamespace, c.impersonateAccount)
 | 
			
		||||
	return cfg, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -60,7 +60,7 @@ func TestNewInClusterRESTClientGetter(t *testing.T) {
 | 
			
		|||
		ctrl.GetConfig = func() (*rest.Config, error) {
 | 
			
		||||
			return cfg, nil
 | 
			
		||||
		}
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter("", "", nil)
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter("", "", "", nil)
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ func TestNewInClusterRESTClientGetter(t *testing.T) {
 | 
			
		|||
		ctrl.GetConfig = func() (*rest.Config, error) {
 | 
			
		||||
			return nil, fmt.Errorf("error")
 | 
			
		||||
		}
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter("", "", nil)
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter("", "", "", nil)
 | 
			
		||||
		g.Expect(err).To(HaveOccurred())
 | 
			
		||||
		g.Expect(err.Error()).To(ContainSubstring("failed to get config for in-cluster REST client"))
 | 
			
		||||
		g.Expect(got).To(BeNil())
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +94,7 @@ func TestNewInClusterRESTClientGetter(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
		ctrl.GetConfig = mockGetConfig
 | 
			
		||||
		namespace := "a-space"
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter(namespace, "", nil)
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter(namespace, "", "", nil)
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -109,20 +109,21 @@ func TestNewInClusterRESTClientGetter(t *testing.T) {
 | 
			
		|||
		ctrl.GetConfig = mockGetConfig
 | 
			
		||||
		ns := "a-namespace"
 | 
			
		||||
		accountName := "foo"
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter(ns, accountName, nil)
 | 
			
		||||
		accountNamespace := "another-namespace"
 | 
			
		||||
		got, err := NewInClusterRESTClientGetter(ns, accountName, accountNamespace, nil)
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(got).To(BeAssignableToTypeOf(&genericclioptions.ConfigFlags{}))
 | 
			
		||||
 | 
			
		||||
		flags := got.(*genericclioptions.ConfigFlags)
 | 
			
		||||
		g.Expect(flags.Impersonate).ToNot(BeNil())
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal(fmt.Sprintf("system:serviceaccount:%s:%s", ns, accountName)))
 | 
			
		||||
		g.Expect(*flags.Impersonate).To(Equal(fmt.Sprintf("system:serviceaccount:%s:%s", accountNamespace, accountName)))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
 | 
			
		||||
	t.Run("loads REST config from KubeConfig", func(t *testing.T) {
 | 
			
		||||
		g := NewWithT(t)
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "", client.Options{}, client.KubeConfigOptions{})
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{}, client.KubeConfigOptions{})
 | 
			
		||||
		got, err := getter.ToRESTConfig()
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(got.Host).To(Equal("http://cow.org:8080"))
 | 
			
		||||
| 
						 | 
				
			
			@ -131,11 +132,11 @@ func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	t.Run("sets ImpersonationConfig", func(t *testing.T) {
 | 
			
		||||
		g := NewWithT(t)
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "someone", client.Options{}, client.KubeConfigOptions{})
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "someone", "a-namespace", client.Options{}, client.KubeConfigOptions{})
 | 
			
		||||
 | 
			
		||||
		got, err := getter.ToRESTConfig()
 | 
			
		||||
		g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
		g.Expect(got.Impersonate.UserName).To(Equal("someone"))
 | 
			
		||||
		g.Expect(got.Impersonate.UserName).To(Equal("system:serviceaccount:a-namespace:someone"))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("uses KubeConfigOptions", func(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +144,7 @@ func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
		agent := "a static string forever," +
 | 
			
		||||
			"but static strings can have dreams and hope too"
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "someone", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{
 | 
			
		||||
		getter := NewMemoryRESTClientGetter(cfg, "", "someone", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{
 | 
			
		||||
			UserAgent: agent,
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -155,7 +156,7 @@ func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
 | 
			
		|||
	t.Run("invalid config", func(t *testing.T) {
 | 
			
		||||
		g := NewWithT(t)
 | 
			
		||||
 | 
			
		||||
		getter := NewMemoryRESTClientGetter([]byte("invalid"), "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
		getter := NewMemoryRESTClientGetter([]byte("invalid"), "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
		got, err := getter.ToRESTConfig()
 | 
			
		||||
		g.Expect(err).To(HaveOccurred())
 | 
			
		||||
		g.Expect(got).To(BeNil())
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +166,7 @@ func TestMemoryRESTClientGetter_ToRESTConfig(t *testing.T) {
 | 
			
		|||
func TestMemoryRESTClientGetter_ToDiscoveryClient(t *testing.T) {
 | 
			
		||||
	g := NewWithT(t)
 | 
			
		||||
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	got, err := getter.ToDiscoveryClient()
 | 
			
		||||
	g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
	g.Expect(got).ToNot(BeNil())
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +175,7 @@ func TestMemoryRESTClientGetter_ToDiscoveryClient(t *testing.T) {
 | 
			
		|||
func TestMemoryRESTClientGetter_ToRESTMapper(t *testing.T) {
 | 
			
		||||
	g := NewWithT(t)
 | 
			
		||||
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "", "", "", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	got, err := getter.ToRESTMapper()
 | 
			
		||||
	g.Expect(err).ToNot(HaveOccurred())
 | 
			
		||||
	g.Expect(got).ToNot(BeNil())
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +184,7 @@ func TestMemoryRESTClientGetter_ToRESTMapper(t *testing.T) {
 | 
			
		|||
func TestMemoryRESTClientGetter_ToRawKubeConfigLoader(t *testing.T) {
 | 
			
		||||
	g := NewWithT(t)
 | 
			
		||||
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "a-namespace", "impersonate", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	getter := NewMemoryRESTClientGetter(cfg, "a-namespace", "impersonate", "other-namespace", client.Options{QPS: 400, Burst: 800}, client.KubeConfigOptions{})
 | 
			
		||||
	got := getter.ToRawKubeConfigLoader()
 | 
			
		||||
	g.Expect(got).ToNot(BeNil())
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue