From a291d6601199c3528959a72459f941a90c8e141a Mon Sep 17 00:00:00 2001 From: Dagan Henderson Date: Sun, 23 Jul 2023 11:31:25 -0700 Subject: [PATCH] Adds user impersonation --- .../bases/konfirm.goraft.tech_testsuites.yaml | 4 - controllers/testsuite_controller.go | 31 +++- internal/helm/types.go | 3 - internal/impersonate/client.go | 4 +- internal/setup/helm.go | 144 ++++++++++++++++++ logging/logging.go | 4 + 6 files changed, 179 insertions(+), 11 deletions(-) delete mode 100644 internal/helm/types.go create mode 100644 internal/setup/helm.go diff --git a/config/crd/bases/konfirm.goraft.tech_testsuites.yaml b/config/crd/bases/konfirm.goraft.tech_testsuites.yaml index 6838eaa..2f907ee 100644 --- a/config/crd/bases/konfirm.goraft.tech_testsuites.yaml +++ b/config/crd/bases/konfirm.goraft.tech_testsuites.yaml @@ -53,12 +53,8 @@ spec: helm: description: TestSuiteHelmSetUp describes a Secret-embedded properties: - chartKey: - type: string secret: type: string - valuesKey: - type: string type: object type: object template: diff --git a/controllers/testsuite_controller.go b/controllers/testsuite_controller.go index 2f7d579..00a9f16 100644 --- a/controllers/testsuite_controller.go +++ b/controllers/testsuite_controller.go @@ -172,6 +172,9 @@ func (r *TestSuiteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( logger.Debug("added finalizer") } + // Handle errors + + // Handle changes to scheduling if sched := testSuite.Spec.When.Schedule; sched != "" { if sched != testSuite.Annotations[TestSuiteScheduleAnnotation] { @@ -705,8 +708,32 @@ func (r *TestSuiteReconciler) isRunning(ctx context.Context, testSuite *konfirm. // doSetUp performs any required setup and returns true when all set up // is complete. An error is returned if set up cannot be performed. -func (r *TestSuiteReconciler) doSetUp(ctx context.Context, testSuite *konfirm.TestSuite) (ok bool) { - ok = true +func (r *TestSuiteReconciler) doSetUp(ctx context.Context, testSuite *konfirm.TestSuite) bool { + + // Return early if no set up + if testSuite.Spec.SetUp.Helm.SecretName == "" { + return true + } + + logger := logging.FromContextWithName(ctx, testSuiteControllerLoggerName) + var err error + + // Get impersonator + var user client.Client + logger.Trace("initiating impersonation") + if user, err = r.Impersonate(ctx, testSuite.Namespace, testSuite.Spec.RunAs); err != nil { + logger.Error(err, "error initializing impersonation") + return false + } + logger.Debug("impersonation initialized") + + // Get the Helm secret + if name := testSuite.Spec.SetUp.Helm.SecretName; name != "" { + secret := v1.Secret{} + if err := + } else { + return true + } return } diff --git a/internal/helm/types.go b/internal/helm/types.go deleted file mode 100644 index fa9556f..0000000 --- a/internal/helm/types.go +++ /dev/null @@ -1,3 +0,0 @@ -package helm - -import _ "helm.sh/helm/v3/pkg/action" diff --git a/internal/impersonate/client.go b/internal/impersonate/client.go index eeb2adb..eeadbd4 100644 --- a/internal/impersonate/client.go +++ b/internal/impersonate/client.go @@ -79,12 +79,12 @@ func (ic impersonatingClient) Impersonate(ctx context.Context, namespace string, } // Copy the rest.Config and add impersonation - config := *ic.config // Dereference original config to avoid reconfiguring other clients + config := rest.CopyConfig(ic.config) if user := userRef.Spec.UserName; user != "" { config.Impersonate.UserName = user } - c, err := client.New(&config, ic.options) + c, err := client.New(config, ic.options) if err != nil { return nil, err } diff --git a/internal/setup/helm.go b/internal/setup/helm.go new file mode 100644 index 0000000..0e83297 --- /dev/null +++ b/internal/setup/helm.go @@ -0,0 +1,144 @@ +package setup + +import ( + "context" + "fmt" + "github.com/raft-tech/konfirm/logging" + helm "helm.sh/helm/v3/pkg/action" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +type HelmInstallInput struct { + types.NamespacedName + ChartUrl string + Username string + Password string + Values map[string]interface{} +} + +type HelmRelease struct{} + +type HelmClient interface { + Install(ctx context.Context, config HelmInstallInput) (*HelmRelease, error) + Uninstall(ctx context.Context, release *HelmRelease) error +} + +func NewHelmClient(namespace string, cfg *rest.Config, mapper meta.RESTMapper, logger logging.Logger) (HelmClient, error) { + debugLogger := logger.DebugLogger().WithName("helm").WithCallDepth(1) + debugFunc := func(format string, v ...interface{}) { + if debugLogger.Enabled() { + debugLogger.Info(fmt.Sprintf(format, v...)) + } + } + config := &helm.Configuration{} + if err := config.Init(&restClientGetter{config: cfg, restMapper: mapper}, namespace, "", debugFunc); err != nil { + return nil, err + } + + return &helmClient{config: config}, nil +} + +type helmClient struct { + config *helm.Configuration +} + +func (h helmClient) Install(ctx context.Context, config HelmInstallInput) (*HelmRelease, error) { + //TODO implement me + panic("implement me") +} + +func (h helmClient) Uninstall(ctx context.Context, release *HelmRelease) error { + //TODO implement me + panic("implement me") +} + +type restClientGetter struct { + config *rest.Config + restMapper meta.RESTMapper +} + +func (r restClientGetter) ToRESTConfig() (*rest.Config, error) { + return r.config, nil +} + +func (r restClientGetter) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) { + if dc, err := discovery.NewDiscoveryClientForConfig(r.config); err == nil { + return memory.NewMemCacheClient(dc), nil + } else { + return nil, err + } +} + +func (r restClientGetter) ToRESTMapper() (meta.RESTMapper, error) { + return r.restMapper, nil +} + +func (r restClientGetter) ToRawKubeConfigLoader() clientcmd.ClientConfig { + + // This is based on the behavior of genericclioptions.ConfigFlags.ToRawKubeConfigLoader + + loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig + + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + + if v := r.config.CertFile; v != "" { + overrides.AuthInfo.ClientCertificate = v + } + + if v := r.config.KeyFile; v != "" { + overrides.AuthInfo.ClientKey = v + } + + if v := r.config.BearerTokenFile; v != "" { + overrides.AuthInfo.TokenFile = v + } else if v = r.config.BearerToken; v != "" { + overrides.AuthInfo.Token = v + } + + if v := r.config.Impersonate.UserName; v != "" { + overrides.AuthInfo.Impersonate = v + } + + if v := r.config.Impersonate.UID; v != "" { + overrides.AuthInfo.ImpersonateUID = v + } + + if v := r.config.Impersonate.Groups; v != nil { + overrides.AuthInfo.ImpersonateGroups = make([]string, len(v)) + copy(overrides.AuthInfo.ImpersonateGroups, v) + } + + if v := r.config.Username; v != "" { + overrides.AuthInfo.Username = v + } + + if v := r.config.Password; v != "" { + overrides.AuthInfo.Password = v + } + + if v := r.config.Host; v != "" { + overrides.ClusterInfo.Server = v + if v = r.config.APIPath; v != "" { + overrides.ClusterInfo.Server += v + } + } + + if v := r.config.ServerName; v != "" { + overrides.ClusterInfo.TLSServerName = v + } + + if v := r.config.CAFile; v != "" { + overrides.ClusterInfo.CertificateAuthority = v + } + + overrides.ClusterInfo.InsecureSkipTLSVerify = r.config.Insecure + overrides.Timeout = r.config.Timeout.String() + + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, overrides) +} diff --git a/logging/logging.go b/logging/logging.go index 1f8ef32..4aebee9 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -38,6 +38,10 @@ func NewLogger(from logr.Logger) *Logger { } } +func (l *Logger) DebugLogger() logr.Logger { + return l.debug +} + func (l *Logger) Debug(msg string, keysAndValues ...interface{}) { l.debug.Info(msg, keysAndValues...) }