diff --git a/Taskfile.yaml b/Taskfile.yaml index 54447cd4..35c89b0e 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -153,6 +153,7 @@ tasks: - kubectl rollout restart deployment/security-operator-generator -n {{.DEPLOYMENT_NAMESPACE}} - kubectl rollout restart deployment/security-operator-initializer -n {{.DEPLOYMENT_NAMESPACE}} - kubectl rollout restart deployment/security-operator-terminator -n {{.DEPLOYMENT_NAMESPACE}} + - kubectl rollout restart deployment/security-operator-system -n {{.DEPLOYMENT_NAMESPACE}} - echo "All deployments restarted" docker:kind: desc: "Build container image, load it into kind cluster, and restart deployments" diff --git a/cmd/model_generator.go b/cmd/model_generator.go index 7dd54c38..64fa6685 100644 --- a/cmd/model_generator.go +++ b/cmd/model_generator.go @@ -70,7 +70,7 @@ var modelGeneratorCmd = &cobra.Command{ return fmt.Errorf("scheme should not be nil") } - provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSliceName, apiexport.Options{ + provider, err := apiexport.New(restCfg, generatorCfg.APIExportEndpointSlices.CorePlatformMeshIO, apiexport.Options{ Scheme: mgrOpts.Scheme, }) if err != nil { diff --git a/cmd/operator.go b/cmd/operator.go index ee1c0405..35eb4862 100644 --- a/cmd/operator.go +++ b/cmd/operator.go @@ -141,7 +141,7 @@ var operatorCmd = &cobra.Command{ return fmt.Errorf("scheme should not be nil") } - provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSliceName, apiexport.Options{ + provider, err := apiexport.New(restCfg, operatorCfg.APIExportEndpointSlices.CorePlatformMeshIO, apiexport.Options{ Scheme: mgrOpts.Scheme, }) if err != nil { @@ -161,11 +161,6 @@ var operatorCmd = &cobra.Command{ return err } - orgClient, err := logicalClusterClientFromKey(mgr.GetLocalManager().GetConfig(), log)(logicalcluster.Name("root:orgs")) - if err != nil { - log.Error().Err(err).Msg("Failed to create org client") - return err - } fga := openfgav1.NewOpenFGAServiceClient(conn) @@ -180,10 +175,6 @@ var operatorCmd = &cobra.Command{ log.Error().Err(err).Str("controller", "authorizationmodel").Msg("unable to create controller") return err } - if err = controller.NewIdentityProviderConfigurationReconciler(ctx, mgr, orgClient, &operatorCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil { - log.Error().Err(err).Str("controller", "identityprovider").Msg("unable to create controller") - return err - } if err = controller.NewInviteReconciler(ctx, mgr, &operatorCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil { log.Error().Err(err).Str("controller", "invite").Msg("unable to create controller") return err diff --git a/cmd/root.go b/cmd/root.go index ff5567ea..e7ec56bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,6 +20,7 @@ var ( terminatorCfg config.Config operatorCfg config.Config generatorCfg config.Config + systemCfg config.Config log *logger.Logger setupLog logr.Logger ) @@ -34,12 +35,14 @@ func init() { rootCmd.AddCommand(operatorCmd) rootCmd.AddCommand(modelGeneratorCmd) rootCmd.AddCommand(initContainerCmd) + rootCmd.AddCommand(systemCmd) defaultCfg = platformeshconfig.NewDefaultConfig() operatorCfg = config.NewConfig() generatorCfg = config.NewConfig() initializerCfg = config.NewConfig() terminatorCfg = config.NewConfig() + systemCfg = config.NewConfig() initContainerCfg = config.NewInitContainerConfig() defaultCfg.AddFlags(rootCmd.PersistentFlags()) @@ -47,6 +50,7 @@ func init() { generatorCfg.AddFlags(modelGeneratorCmd.Flags()) initializerCfg.AddFlags(initializerCmd.Flags()) terminatorCfg.AddFlags(terminatorCmd.Flags()) + systemCfg.AddFlags(systemCmd.Flags()) initContainerCfg.AddFlags(initContainerCmd.Flags()) cobra.OnInitialize(initLog) diff --git a/cmd/system.go b/cmd/system.go new file mode 100644 index 00000000..dae21592 --- /dev/null +++ b/cmd/system.go @@ -0,0 +1,98 @@ +package cmd + +import ( + "context" + "crypto/tls" + + platformeshcontext "github.com/platform-mesh/golang-commons/context" + "github.com/platform-mesh/security-operator/internal/controller" + "github.com/spf13/cobra" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + mcmanager "sigs.k8s.io/multicluster-runtime/pkg/manager" + + "k8s.io/client-go/rest" + + "github.com/kcp-dev/logicalcluster/v3" + "github.com/kcp-dev/multicluster-provider/apiexport" +) + +var systemCmd = &cobra.Command{ + Use: "system", + Short: "System controllers for system.platform-mesh.io apiexport resources", + RunE: func(cmd *cobra.Command, args []string) error { + ctrl.SetLogger(log.ComponentLogger("controller-runtime").Logr()) + + ctx, _, shutdown := platformeshcontext.StartContext(log, defaultCfg, defaultCfg.ShutdownTimeout) + defer shutdown() + + restCfg, err := getKubeconfigFromPath(systemCfg.KCP.Kubeconfig) + if err != nil { + log.Error().Err(err).Msg("unable to get KCP kubeconfig") + return err + } + + opts := ctrl.Options{ + Scheme: scheme, + Metrics: metricsserver.Options{ + BindAddress: defaultCfg.Metrics.BindAddress, + TLSOpts: []func(*tls.Config){ + func(c *tls.Config) { + c.NextProtos = []string{"http/1.1"} + }, + }, + }, + HealthProbeBindAddress: defaultCfg.HealthProbeBindAddress, + LeaderElection: defaultCfg.LeaderElectionEnabled, + LeaderElectionID: "security-operator-system.platform-mesh.io", + BaseContext: func() context.Context { return ctx }, + } + + if defaultCfg.LeaderElectionEnabled { + inClusterCfg, err := rest.InClusterConfig() + if err != nil { + setupLog.Error(err, "unable to get in-cluster config for leader election") + return err + } + opts.LeaderElectionConfig = inClusterCfg + } + + provider, err := apiexport.New(restCfg, systemCfg.APIExportEndpointSlices.SystemPlatformMeshIO, apiexport.Options{ + Scheme: scheme, + }) + if err != nil { + setupLog.Error(err, "unable to create apiexport provider") + return err + } + + mgr, err := mcmanager.New(restCfg, provider, opts) + if err != nil { + setupLog.Error(err, "unable to create manager") + return err + } + orgClient, err := logicalClusterClientFromKey(mgr.GetLocalManager().GetConfig(), log)(logicalcluster.Name("root:orgs")) + if err != nil { + log.Error().Err(err).Msg("Failed to create org client") + return err + } + + if err = controller.NewIdentityProviderConfigurationReconciler(ctx, mgr, orgClient, &systemCfg, log).SetupWithManager(mgr, defaultCfg, log); err != nil { + log.Error().Err(err).Str("controller", "identityprovider").Msg("unable to create controller") + return err + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + log.Error().Err(err).Msg("unable to set up health check") + return err + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + log.Error().Err(err).Msg("unable to set up ready check") + return err + } + + setupLog.Info("starting system manager") + + return mgr.Start(ctx) + }, +} diff --git a/go.mod b/go.mod index cca59597..79bcfa21 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/platform-mesh/security-operator -go 1.25.7 +go 1.26.1 require ( github.com/coreos/go-oidc v2.5.0+incompatible diff --git a/internal/config/config.go b/internal/config/config.go index 823449de..92100caa 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -56,11 +56,16 @@ type IDPConfig struct { RegistrationAllowed bool } +type APIExportEndpointSlices struct { + CorePlatformMeshIO string + SystemPlatformMeshIO string +} + // Config struct to hold the app config type Config struct { FGA FGAConfig KCP KCPConfig - APIExportEndpointSliceName string + APIExportEndpointSlices APIExportEndpointSlices CoreModulePath string BaseDomain string GroupClaim string @@ -89,13 +94,16 @@ func NewConfig() Config { KCP: KCPConfig{ Kubeconfig: "/api-kubeconfig/kubeconfig", }, - APIExportEndpointSliceName: "core.platform-mesh.io", - BaseDomain: "portal.dev.local:8443", - GroupClaim: "groups", - UserClaim: "email", - WorkspacePath: "root", - WorkspaceTypeName: "security", - HttpClientTimeoutSeconds: 30, + APIExportEndpointSlices: APIExportEndpointSlices{ + CorePlatformMeshIO: "core.platform-mesh.io", + SystemPlatformMeshIO: "system.platform-mesh.io", + }, + BaseDomain: "portal.dev.local:8443", + GroupClaim: "groups", + UserClaim: "email", + WorkspacePath: "root", + WorkspaceTypeName: "security", + HttpClientTimeoutSeconds: 30, IDP: IDPConfig{ KubectlClientRedirectURLs: []string{"http://localhost:8000", "http://localhost:18000"}, AccessTokenLifespan: 28800, @@ -123,7 +131,8 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&c.FGA.ParentRelation, "fga-parent-relation", c.FGA.ParentRelation, "Set the OpenFGA parent relation name") fs.StringVar(&c.FGA.CreatorRelation, "fga-creator-relation", c.FGA.CreatorRelation, "Set the OpenFGA creator relation name") fs.StringVar(&c.KCP.Kubeconfig, "kcp-kubeconfig", c.KCP.Kubeconfig, "Set the KCP kubeconfig path") - fs.StringVar(&c.APIExportEndpointSliceName, "api-export-endpoint-slice-name", c.APIExportEndpointSliceName, "Set the APIExportEndpointSlice name") + fs.StringVar(&c.APIExportEndpointSlices.CorePlatformMeshIO, "api-export-endpoint-slice-name", c.APIExportEndpointSlices.CorePlatformMeshIO, "Set the core.platform-mesh.io APIExportEndpointSlice name") + fs.StringVar(&c.APIExportEndpointSlices.SystemPlatformMeshIO, "system-api-export-endpoint-slice-name", c.APIExportEndpointSlices.SystemPlatformMeshIO, "Set the system.platform-mesh.io APIExportEndpointSlice name") fs.StringVar(&c.CoreModulePath, "core-module-path", c.CoreModulePath, "Set the path to the core module FGA model file") fs.StringVar(&c.BaseDomain, "base-domain", c.BaseDomain, "Set the base domain used to construct issuer URLs") fs.StringVar(&c.GroupClaim, "group-claim", c.GroupClaim, "Set the ID token group claim") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ff07b7b2..c815296d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -12,7 +12,8 @@ func TestNewConfig(t *testing.T) { assert.Equal(t, "core_platform-mesh_io_account", cfg.FGA.ObjectType) assert.Equal(t, "/api-kubeconfig/kubeconfig", cfg.KCP.Kubeconfig) - assert.Equal(t, "core.platform-mesh.io", cfg.APIExportEndpointSliceName) + assert.Equal(t, "core.platform-mesh.io", cfg.APIExportEndpointSlices.CorePlatformMeshIO) + assert.Equal(t, "system.platform-mesh.io", cfg.APIExportEndpointSlices.SystemPlatformMeshIO) assert.Equal(t, "security-operator", cfg.Keycloak.ClientID) assert.Equal(t, 9443, cfg.Webhooks.Port) assert.Equal(t, []string{"http://localhost:8000", "http://localhost:18000"}, cfg.IDP.KubectlClientRedirectURLs) diff --git a/internal/subroutine/authorization_model_generation.go b/internal/subroutine/authorization_model_generation.go index 6a66a87b..ac97e987 100644 --- a/internal/subroutine/authorization_model_generation.go +++ b/internal/subroutine/authorization_model_generation.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "slices" "strings" "text/template" @@ -210,8 +211,10 @@ func (a *AuthorizationModelGenerationSubroutine) GetName() string { func (a *AuthorizationModelGenerationSubroutine) Process(ctx context.Context, instance lifecyclecontrollerruntime.RuntimeObject) (ctrl.Result, errors.OperatorError) { binding := instance.(*kcpapisv1alpha2.APIBinding) - if binding.Spec.Reference.Export.Name == "core.platform-mesh.io" || strings.HasSuffix(binding.Spec.Reference.Export.Name, "kcp.io") { - // If the APIExport is the core.platform-mesh.io, we can skip the model generation. + internalAPIBindings := []string{"core.platform-mesh.io", "system.platform-mesh.io"} + + if slices.Contains(internalAPIBindings, binding.Spec.Reference.Export.Name) || strings.HasSuffix(binding.Spec.Reference.Export.Name, "kcp.io") { + // If the APIExport is the core.platform-mesh.io, system.platform-mesh.io we can skip the model generation. return ctrl.Result{}, nil } diff --git a/internal/subroutine/idp.go b/internal/subroutine/idp.go index 6ffae6fc..e8ce088f 100644 --- a/internal/subroutine/idp.go +++ b/internal/subroutine/idp.go @@ -127,7 +127,7 @@ func (i *IDPSubroutine) Initialize(ctx context.Context, instance runtimeobject.R } idp := &v1alpha1.IdentityProviderConfiguration{ObjectMeta: metav1.ObjectMeta{Name: workspaceName}} - _, err = controllerutil.CreateOrPatch(ctx, cl.GetClient(), idp, func() error { + _, err = controllerutil.CreateOrPatch(ctx, i.orgsClient, idp, func() error { idp.Spec.RegistrationAllowed = i.registrationAllowed for _, desired := range clients { @@ -141,7 +141,7 @@ func (i *IDPSubroutine) Initialize(ctx context.Context, instance runtimeobject.R log.Info().Str("workspace", workspaceName).Msg("idp configuration resource is created") - if err := cl.GetClient().Get(ctx, types.NamespacedName{Name: workspaceName}, idp); err != nil { + if err := i.orgsClient.Get(ctx, types.NamespacedName{Name: workspaceName}, idp); err != nil { return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get idp resource %w", err), true, true) } diff --git a/internal/subroutine/workspace_authorization.go b/internal/subroutine/workspace_authorization.go index b2dd282f..1256231b 100644 --- a/internal/subroutine/workspace_authorization.go +++ b/internal/subroutine/workspace_authorization.go @@ -7,7 +7,7 @@ import ( "github.com/platform-mesh/golang-commons/controller/lifecycle/runtimeobject" lifecyclesubroutine "github.com/platform-mesh/golang-commons/controller/lifecycle/subroutine" "github.com/platform-mesh/golang-commons/errors" - "github.com/platform-mesh/security-operator/api/v1alpha1" + accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/config" "github.com/rs/zerolog/log" ctrl "sigs.k8s.io/controller-runtime" @@ -84,26 +84,22 @@ func (r *workspaceAuthSubroutine) Initialize(ctx context.Context, instance runti return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get cluster from context %w", err), true, true) } - var idpConfig v1alpha1.IdentityProviderConfiguration - err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: workspaceName}, &idpConfig) + var accountInfo accountsv1alpha1.AccountInfo + err = cluster.GetClient().Get(ctx, types.NamespacedName{Name: "account"}, &accountInfo) if err != nil { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get IdentityProviderConfiguration: %w", err), true, true) + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("failed to get AccountInfo: %w", err), true, true) } - if len(idpConfig.Spec.Clients) == 0 || len(idpConfig.Status.ManagedClients) == 0 { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("IdentityProviderConfiguration %s has no clients in spec or status", workspaceName), true, false) + if accountInfo.Spec.OIDC == nil || len(accountInfo.Spec.OIDC.Clients) == 0 { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("AccountInfo %s has no OIDC clients", workspaceName), true, false) } - audiences := make([]string, 0, len(idpConfig.Spec.Clients)) - for _, specClient := range idpConfig.Spec.Clients { - managedClient, ok := idpConfig.Status.ManagedClients[specClient.ClientName] - if !ok { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("managed client %s not found in IdentityProviderConfiguration status", specClient.ClientName), true, false) + audiences := make([]string, 0, len(accountInfo.Spec.OIDC.Clients)) + for clientName, clientInfo := range accountInfo.Spec.OIDC.Clients { + if clientInfo.ClientID == "" { + return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("OIDC client %s has empty ClientID in AccountInfo", clientName), true, false) } - if managedClient.ClientID == "" { - return ctrl.Result{}, errors.NewOperatorError(fmt.Errorf("managed client %s has empty ClientID in IdentityProviderConfiguration status", specClient.ClientName), true, false) - } - audiences = append(audiences, managedClient.ClientID) + audiences = append(audiences, clientInfo.ClientID) } jwtAuthenticationConfiguration := kcptenancyv1alphav1.JWTAuthenticator{ diff --git a/internal/subroutine/workspace_authorization_test.go b/internal/subroutine/workspace_authorization_test.go index c8c0dfd9..22271597 100644 --- a/internal/subroutine/workspace_authorization_test.go +++ b/internal/subroutine/workspace_authorization_test.go @@ -5,7 +5,7 @@ import ( "errors" "testing" - "github.com/platform-mesh/security-operator/api/v1alpha1" + accountsv1alpha1 "github.com/platform-mesh/account-operator/api/v1alpha1" "github.com/platform-mesh/security-operator/internal/config" "github.com/platform-mesh/security-operator/internal/subroutine/mocks" "github.com/stretchr/testify/assert" @@ -43,20 +43,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -105,20 +101,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "example.com", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "existing-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "existing-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "existing-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "existing-workspace": {ClientID: "existing-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "existing-workspace": {ClientID: "existing-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -203,20 +195,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -241,20 +229,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -283,20 +267,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -319,20 +299,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "single-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "single-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "single-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "single-workspace": {ClientID: "single-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "single-workspace": {ClientID: "single-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -383,20 +359,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { DomainCALookup: true, }, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "single-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "single-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "single-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "single-workspace": {ClientID: "single-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "single-workspace": {ClientID: "single-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -451,20 +423,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -491,20 +459,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { }, cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "test-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "test-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "test-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "test-workspace": {ClientID: "test-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: "test-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -566,20 +530,16 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { DevelopmentAllowUnverifiedEmails: true, }, setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { - mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "dev-workspace"}, mock.AnythingOfType("*v1alpha1.IdentityProviderConfiguration"), mock.Anything). + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - *obj.(*v1alpha1.IdentityProviderConfiguration) = v1alpha1.IdentityProviderConfiguration{ - ObjectMeta: metav1.ObjectMeta{Name: "dev-workspace"}, - Spec: v1alpha1.IdentityProviderConfigurationSpec{ - Clients: []v1alpha1.IdentityProviderClientConfig{ - {ClientName: "dev-workspace", ClientType: v1alpha1.IdentityProviderClientTypeConfidential}, - {ClientName: "kubectl", ClientType: v1alpha1.IdentityProviderClientTypePublic}, - }, - }, - Status: v1alpha1.IdentityProviderConfigurationStatus{ - ManagedClients: map[string]v1alpha1.ManagedClient{ - "dev-workspace": {ClientID: "dev-workspace-client"}, - "kubectl": {ClientID: "kubectl-client"}, + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "dev-workspace": {ClientID: "dev-workspace-client"}, + "kubectl": {ClientID: "kubectl-client"}, + }, }, }, } @@ -622,6 +582,104 @@ func TestWorkspaceAuthSubroutine_Initialize(t *testing.T) { expectError: false, expectedResult: ctrl.Result{}, }, + { + name: "error - AccountInfo not found", + logicalCluster: &kcpcorev1alpha1.LogicalCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "kcp.io/path": "root:orgs:test-workspace", + }, + }, + }, + cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, + setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). + Return(errors.New("accountinfo not found")).Once() + }, + expectError: true, + expectedResult: ctrl.Result{}, + }, + { + name: "error - AccountInfo has no OIDC clients", + logicalCluster: &kcpcorev1alpha1.LogicalCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "kcp.io/path": "root:orgs:test-workspace", + }, + }, + }, + cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, + setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). + RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: nil, + }, + } + return nil + }).Once() + }, + expectError: true, + expectedResult: ctrl.Result{}, + }, + { + name: "error - AccountInfo has empty OIDC clients map", + logicalCluster: &kcpcorev1alpha1.LogicalCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "kcp.io/path": "root:orgs:test-workspace", + }, + }, + }, + cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, + setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). + RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{}, + }, + }, + } + return nil + }).Once() + }, + expectError: true, + expectedResult: ctrl.Result{}, + }, + { + name: "error - AccountInfo client has empty ClientID", + logicalCluster: &kcpcorev1alpha1.LogicalCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + "kcp.io/path": "root:orgs:test-workspace", + }, + }, + }, + cfg: config.Config{BaseDomain: "test.domain", GroupClaim: "groups", UserClaim: "email"}, + setupMocks: func(m *mocks.MockClient, mgrClient *mocks.MockClient) { + mgrClient.EXPECT().Get(mock.Anything, types.NamespacedName{Name: "account"}, mock.AnythingOfType("*v1alpha1.AccountInfo"), mock.Anything). + RunAndReturn(func(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + *obj.(*accountsv1alpha1.AccountInfo) = accountsv1alpha1.AccountInfo{ + ObjectMeta: metav1.ObjectMeta{Name: "account"}, + Spec: accountsv1alpha1.AccountInfoSpec{ + OIDC: &accountsv1alpha1.OIDCInfo{ + Clients: map[string]accountsv1alpha1.ClientInfo{ + "test-workspace": {ClientID: ""}, + }, + }, + }, + } + return nil + }).Once() + }, + expectError: true, + expectedResult: ctrl.Result{}, + }, } for _, tt := range tests {