diff --git a/cmd/gateway/server.go b/cmd/gateway/server.go index c6174d7dc..19fbc68e6 100644 --- a/cmd/gateway/server.go +++ b/cmd/gateway/server.go @@ -180,6 +180,17 @@ var serverCmd = &cobra.Command{ return err } + tenantClusterBindingRepository := repositories.NewTenantClusterBindingRepository(esr.NewInMemoryRepository[*projections.TenantClusterBinding]()) + tenantClusterBindingProjector := projectors.NewTenantClusterBindingProjector() + tenantClusterBindingProjectingHandler := eventhandler.NewProjectingEventHandler[*projections.TenantClusterBinding](tenantClusterBindingProjector, tenantClusterBindingRepository) + tenantClusterBindingHandlerChain := eventsourcing.UseEventHandlerMiddleware(tenantClusterBindingProjectingHandler, eventhandler.NewEventStoreReplayMiddleware(esClient), eventhandler.NewEventStoreRefreshMiddleware(esClient, refreshDuration)) + tenantClusterBindingMatcher := ebConsumer.Matcher().MatchAggregateType(aggregates.TenantClusterBinding) + if err := ebConsumer.AddHandler(ctx, tenantClusterBindingHandlerChain, tenantClusterBindingMatcher); err != nil { + return err + } + + clusterAccessRepo := repositories.NewClusterAccessRepository(tenantClusterBindingRepository, clusterRepository, userRoleBindingRepository) + if err := handler.WarmUp(ctx, esClient, aggregates.User, userHandlerChain); err != nil { return err } @@ -189,6 +200,9 @@ var serverCmd = &cobra.Command{ if err := handler.WarmUp(ctx, esClient, aggregates.Cluster, clusterHandlerChain); err != nil { return err } + if err := handler.WarmUp(ctx, esClient, aggregates.TenantClusterBinding, tenantClusterBindingHandlerChain); err != nil { + return err + } // API servers authServer, err := gateway.NewAuthServer(ctx, gatewayURL, server, policiesPath, userRoleBindingRepository) @@ -223,7 +237,7 @@ var serverCmd = &cobra.Command{ } tokenLifeTimePerRole[k] = k8sTokenValidityDuration } - clusterAuthApiServer := gateway.NewClusterAuthAPIServer(gatewayURL, signer, clusterRepository, tokenLifeTimePerRole) + clusterAuthApiServer := gateway.NewClusterAuthAPIServer(gatewayURL, signer, clusterAccessRepo, tokenLifeTimePerRole) apiTokenServer := gateway.NewAPITokenServer(gatewayURL, signer, userRepository) authMiddleware := authm.NewAuthMiddleware(authServer.AsClient(), []string{ diff --git a/go.mk b/go.mk index e6eeebce7..da930a05f 100644 --- a/go.mk +++ b/go.mk @@ -133,7 +133,7 @@ go-rebuild-mocks: .protobuf-deps $(MOCKGEN) $(MOCKGEN) -copyright_file hack/copyright.lic -destination internal/test/api/gateway/gateway_auth_client.go github.com/finleap-connect/monoskope/pkg/api/gateway GatewayAuthClient $(MOCKGEN) -copyright_file hack/copyright.lic -destination internal/test/eventsourcing/mock_handler.go github.com/finleap-connect/monoskope/pkg/eventsourcing EventHandler $(MOCKGEN) -copyright_file hack/copyright.lic -destination internal/test/eventsourcing/aggregate_store.go github.com/finleap-connect/monoskope/pkg/eventsourcing AggregateStore - $(MOCKGEN) -copyright_file hack/copyright.lic -destination internal/test/domain/repositories/repositories.go github.com/finleap-connect/monoskope/pkg/domain/repositories UserRepository,ClusterRepository + $(MOCKGEN) -copyright_file hack/copyright.lic -destination internal/test/domain/repositories/repositories.go github.com/finleap-connect/monoskope/pkg/domain/repositories UserRepository,ClusterRepository,ClusterAccessRepository go-run-: ## run cmd, e.g. `make go-run-gateway ARGS="server"` to pass arguments $(GO) run cmd/$*/*.go $(ARGS) diff --git a/go.mod b/go.mod index 55a2fb066..8c3711c06 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,8 @@ require ( sigs.k8s.io/controller-runtime v0.11.2 ) +require k8s.io/utils v0.0.0-20211116205334-6203023598ed + require ( cloud.google.com/go v0.99.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -77,7 +79,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.9.7 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/go-cmp v0.5.7 // indirect + github.com/google/go-cmp v0.5.8 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gnostic v0.5.5 // indirect @@ -128,6 +130,7 @@ require ( golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.10 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220118154757-00ab72f36ad5 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect @@ -137,7 +140,6 @@ require ( k8s.io/api v0.23.6 // indirect k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect - k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect mellium.im/sasl v0.2.1 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect diff --git a/go.sum b/go.sum index 93be890f7..8dc9e2792 100644 --- a/go.sum +++ b/go.sum @@ -568,8 +568,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -1222,8 +1223,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1530,8 +1531,9 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/gateway/auth_server_test.go b/internal/gateway/auth_server_test.go index deecbfd69..844737f7c 100644 --- a/internal/gateway/auth_server_test.go +++ b/internal/gateway/auth_server_test.go @@ -40,11 +40,10 @@ import ( var _ = Describe("Gateway Auth Server", func() { var ( - ctx = context.Background() - expectedUserId = uuid.New() - expectedRole = roles.User - expectedScope = scopes.Tenant - expectedResource = "1234" + ctx = context.Background() + expectedUserId = uuid.New() + expectedRole = roles.User + expectedScope = scopes.Tenant ) getTokenForUser := func(user *projections.User) string { @@ -63,7 +62,7 @@ var _ = Describe("Gateway Auth Server", func() { UserId: expectedUserId.String(), Role: string(expectedRole), Scope: string(expectedScope), - Resource: wrapperspb.String(expectedResource), + Resource: wrapperspb.String(testEnv.SomeTenantId.String()), }, ) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/gateway/cluster_auth_server.go b/internal/gateway/cluster_auth_server.go index 900d21b70..e132cc8e8 100644 --- a/internal/gateway/cluster_auth_server.go +++ b/internal/gateway/cluster_auth_server.go @@ -28,32 +28,32 @@ import ( type clusterAuthApiServer struct { api.UnimplementedClusterAuthServer - log logger.Logger - signer jwt.JWTSigner - clusterRepo repositories.ClusterRepository - issuer string - validity map[string]time.Duration + log logger.Logger + signer jwt.JWTSigner + clusterAccessRepo repositories.ClusterAccessRepository + issuer string + validity map[string]time.Duration } func NewClusterAuthAPIServer( issuer string, signer jwt.JWTSigner, - clusterRepo repositories.ClusterRepository, + clusterAccessRepo repositories.ClusterAccessRepository, validity map[string]time.Duration, ) api.ClusterAuthServer { s := &clusterAuthApiServer{ - log: logger.WithName("server"), - signer: signer, - clusterRepo: clusterRepo, - issuer: issuer, - validity: validity, + log: logger.WithName("server"), + signer: signer, + clusterAccessRepo: clusterAccessRepo, + issuer: issuer, + validity: validity, } return s } func (s *clusterAuthApiServer) GetAuthToken(ctx context.Context, request *api.ClusterAuthTokenRequest) (*api.ClusterAuthTokenResponse, error) { response := new(api.ClusterAuthTokenResponse) - uc := usecases.NewGetAuthTokenUsecase(request, response, s.signer, s.clusterRepo, s.issuer, s.validity) + uc := usecases.NewGetAuthTokenUsecase(request, response, s.signer, s.clusterAccessRepo, s.issuer, s.validity) err := uc.Run(ctx) if err != nil { return nil, errors.TranslateToGrpcError(err) diff --git a/internal/gateway/cluster_auth_server_test.go b/internal/gateway/cluster_auth_server_test.go index 6a33b646b..f8936a976 100644 --- a/internal/gateway/cluster_auth_server_test.go +++ b/internal/gateway/cluster_auth_server_test.go @@ -33,25 +33,22 @@ var _ = Describe("Internal/Gateway/ClusterAuthServer", func() { mdManager, err := metadata.NewDomainMetadataManager(ctx) Expect(err).ToNot(HaveOccurred()) - It("can retrieve auth url", func() { + It("can request a cluster auth token", func() { conn, err := CreateInsecureConnection(ctx, testEnv.ApiListenerAPIServer.Addr().String()) Expect(err).ToNot(HaveOccurred()) - clusters, err := testEnv.ClusterRepo.AllWith(ctx, false) - Expect(err).ToNot(HaveOccurred()) - Expect(len(clusters)).To(BeNumerically(">=", 1)) defer conn.Close() apiClient := api.NewClusterAuthClient(conn) mdManager.SetUserInformation(&metadata.UserInformation{ - Id: uuid.MustParse(testEnv.AdminUser.GetId()), - Name: testEnv.AdminUser.Name, - Email: testEnv.AdminUser.Email, + Id: uuid.MustParse(testEnv.TenantAdminUser.GetId()), + Name: testEnv.TenantAdminUser.Name, + Email: testEnv.TenantAdminUser.Email, NotBefore: time.Now().UTC(), }) response, err := apiClient.GetAuthToken(mdManager.GetOutgoingGrpcContext(), &api.ClusterAuthTokenRequest{ - ClusterId: clusters[0].Id, + ClusterId: testEnv.TestClusterId.String(), Role: string(expectedRole), }) Expect(err).ToNot(HaveOccurred()) diff --git a/internal/gateway/testenv.go b/internal/gateway/testenv.go index 08060246d..aee78cba9 100644 --- a/internal/gateway/testenv.go +++ b/internal/gateway/testenv.go @@ -65,6 +65,8 @@ type TestEnv struct { ExistingUser *projections.User NotExistingUser *projections.User PoliciesPath string + SomeTenantId uuid.UUID + TestClusterId uuid.UUID } func NewTestEnvWithParent(testeEnv *test.TestEnv) (*TestEnv, error) { @@ -158,6 +160,8 @@ func NewTestEnvWithParent(testeEnv *test.TestEnv) (*TestEnv, error) { return nil, err } + env.SomeTenantId = uuid.New() + // Setup user repo adminUser := projections.NewUserProjection(uuid.New()) adminUser.Name = "admin" @@ -184,7 +188,7 @@ func NewTestEnvWithParent(testeEnv *test.TestEnv) (*TestEnv, error) { tenantAdminRoleBinding.UserId = tenantAdminUser.Id tenantAdminRoleBinding.Role = string(roles.Admin) tenantAdminRoleBinding.Scope = string(scopes.Tenant) - tenantAdminRoleBinding.Resource = "1234" + tenantAdminRoleBinding.Resource = env.SomeTenantId.String() env.AdminUser = adminUser env.TenantAdminUser = tenantAdminUser @@ -216,8 +220,8 @@ func NewTestEnvWithParent(testeEnv *test.TestEnv) (*TestEnv, error) { } // Setup cluster repo - clusterId := uuid.New() - testCluster := projections.NewClusterProjection(clusterId) + env.TestClusterId = uuid.New() + testCluster := projections.NewClusterProjection(env.TestClusterId) testCluster.Name = "test-cluster" testCluster.DisplayName = "Test Cluster" testCluster.ApiServerAddress = "https://somecluster.io" @@ -228,11 +232,23 @@ func NewTestEnvWithParent(testeEnv *test.TestEnv) (*TestEnv, error) { return nil, err } + tenantClusterBindingId := uuid.New() + tenantClusterBinding := projections.NewTenantClusterBindingProjection(tenantClusterBindingId) + tenantClusterBinding.ClusterId = env.TestClusterId.String() + tenantClusterBinding.TenantId = env.SomeTenantId.String() + + inMemoryTemamtClusterBindingRepo := es_repos.NewInMemoryRepository[*projections.TenantClusterBinding]() + if err := inMemoryTemamtClusterBindingRepo.Upsert(context.Background(), tenantClusterBinding); err != nil { + return nil, err + } + userRoleBindingRepo := repositories.NewUserRoleBindingRepository(inMemoryUserRoleBindingRepo) userRepo := repositories.NewUserRepository(inMemoryUserRepo, repositories.NewUserRoleBindingRepository(inMemoryUserRoleBindingRepo)) + tenantClusterbindingRepo := repositories.NewTenantClusterBindingRepository(inMemoryTemamtClusterBindingRepo) env.ClusterRepo = repositories.NewClusterRepository(inMemoryClusterRepo) + gatewayApiServer := NewGatewayAPIServer(env.ClientAuthConfig, authClient, authServer, userRepo) - authApiServer := NewClusterAuthAPIServer("https://localhost", signer, env.ClusterRepo, map[string]time.Duration{ + authApiServer := NewClusterAuthAPIServer("https://localhost", signer, repositories.NewClusterAccessRepository(tenantClusterbindingRepo, env.ClusterRepo, userRoleBindingRepo), map[string]time.Duration{ "default": time.Hour * 1, }) diff --git a/internal/gateway/usecases/get_auth_token.go b/internal/gateway/usecases/get_auth_token.go index ba6e9601a..46f389543 100644 --- a/internal/gateway/usecases/get_auth_token.go +++ b/internal/gateway/usecases/get_auth_token.go @@ -21,7 +21,9 @@ import ( "time" "github.com/finleap-connect/monoskope/internal/gateway/auth" + "github.com/finleap-connect/monoskope/pkg/api/domain/projections" api "github.com/finleap-connect/monoskope/pkg/api/gateway" + "github.com/finleap-connect/monoskope/pkg/domain/errors" domainErrors "github.com/finleap-connect/monoskope/pkg/domain/errors" "github.com/finleap-connect/monoskope/pkg/domain/metadata" "github.com/finleap-connect/monoskope/pkg/domain/repositories" @@ -29,25 +31,25 @@ import ( "github.com/finleap-connect/monoskope/pkg/k8s" "github.com/finleap-connect/monoskope/pkg/logger" "github.com/finleap-connect/monoskope/pkg/usecase" - "github.com/google/uuid" "google.golang.org/protobuf/types/known/timestamppb" + "k8s.io/utils/strings/slices" ) type getAuthTokenUsecase struct { *usecase.UseCaseBase - request *api.ClusterAuthTokenRequest - result *api.ClusterAuthTokenResponse - signer jwt.JWTSigner - clusterRepo repositories.ClusterRepository - issuer string - validity map[string]time.Duration + request *api.ClusterAuthTokenRequest + result *api.ClusterAuthTokenResponse + signer jwt.JWTSigner + clusterAccessRepo repositories.ClusterAccessRepository + issuer string + validity map[string]time.Duration } func NewGetAuthTokenUsecase( request *api.ClusterAuthTokenRequest, response *api.ClusterAuthTokenResponse, signer jwt.JWTSigner, - clusterRepo repositories.ClusterRepository, + clusterAccessRepo repositories.ClusterAccessRepository, issuer string, validity map[string]time.Duration, ) usecase.UseCase { @@ -56,7 +58,7 @@ func NewGetAuthTokenUsecase( request, response, signer, - clusterRepo, + clusterAccessRepo, issuer, validity, } @@ -80,22 +82,28 @@ func (s *getAuthTokenUsecase) Run(ctx context.Context) error { } clusterId := s.request.GetClusterId() - s.Log.V(logger.DebugLevel).Info("Getting cluster by id...", "id", clusterId) - - uuid, err := uuid.Parse(clusterId) + s.Log.V(logger.DebugLevel).Info("Checking user is allowed to access cluster...", "clusterId", clusterId) + clusterAccesses, err := s.clusterAccessRepo.GetClustersAccessibleByUserId(ctx, userInfo.Id) if err != nil { return err } - cluster, err := s.clusterRepo.ById(ctx, uuid) - if err != nil { - return err + var foundClusterAccess *projections.ClusterAccess + for _, clusterAccess := range clusterAccesses { + if clusterAccess.Cluster.Id == clusterId { + foundClusterAccess = clusterAccess + break + } } - k8sRole := s.request.GetRole() - s.Log.V(logger.DebugLevel).Info("Validating role exists...", "role", k8sRole) - if err := k8s.ValidateRole(k8sRole); err != nil { - return err + if foundClusterAccess == nil { + s.Log.V(logger.DebugLevel).Info("User is not authorized to access cluster.", "clusterId", clusterId) + return errors.ErrUnauthorized + } + + if !slices.Contains(foundClusterAccess.Roles, s.request.Role) { + s.Log.V(logger.DebugLevel).Info("User is not authorized to access cluster with role.", "clusterId", clusterId, "role", s.request.Role) + return errors.ErrUnauthorized } username := strings.ToLower(strings.Split(userInfo.Email, "@")[0]) @@ -109,8 +117,8 @@ func (s *getAuthTokenUsecase) Run(ctx context.Context) error { Email: userInfo.Email, EmailVerified: true, }, &jwt.ClusterClaim{ - ClusterId: cluster.GetId(), - ClusterName: cluster.GetName(), + ClusterId: foundClusterAccess.Cluster.GetId(), + ClusterName: foundClusterAccess.Cluster.GetName(), ClusterUserName: username, ClusterRole: s.request.Role, }, s.issuer, userInfo.Id.String(), s.validity[s.request.Role]) diff --git a/internal/gateway/usecases/get_auth_token_test.go b/internal/gateway/usecases/get_auth_token_test.go index 2d4811378..ad227446c 100644 --- a/internal/gateway/usecases/get_auth_token_test.go +++ b/internal/gateway/usecases/get_auth_token_test.go @@ -20,6 +20,7 @@ import ( "github.com/finleap-connect/monoskope/internal/test" mock_repos "github.com/finleap-connect/monoskope/internal/test/domain/repositories" + api_projections "github.com/finleap-connect/monoskope/pkg/api/domain/projections" api "github.com/finleap-connect/monoskope/pkg/api/gateway" "github.com/finleap-connect/monoskope/pkg/domain/metadata" "github.com/finleap-connect/monoskope/pkg/domain/projections" @@ -62,14 +63,14 @@ var _ = Describe("GetAuthToken", func() { }) It("can retrieve openid conf", func() { - clusterRepo := mock_repos.NewMockClusterRepository(mockCtrl) + clusterAccessRepo := mock_repos.NewMockClusterAccessRepository(mockCtrl) request := &api.ClusterAuthTokenRequest{ ClusterId: expectedClusterId.String(), Role: string(k8s.DefaultRole), } result := new(api.ClusterAuthTokenResponse) - uc := NewGetAuthTokenUsecase(request, result, jwtTestEnv.CreateSigner(), clusterRepo, expectedIssuer, expectedValidity) + uc := NewGetAuthTokenUsecase(request, result, jwtTestEnv.CreateSigner(), clusterAccessRepo, expectedIssuer, expectedValidity) mdManager.SetUserInformation(&metadata.UserInformation{ Id: expectedUserId, @@ -78,23 +79,91 @@ var _ = Describe("GetAuthToken", func() { NotBefore: time.Now().UTC(), }) - userProjection := projections.NewUserProjection(expectedUserId) - userProjection.Id = expectedUserId.String() - userProjection.Name = expectedUserName - userProjection.Email = expectedUserEmail - clusterProjection := projections.NewClusterProjection(expectedClusterId) clusterProjection.Id = expectedClusterId.String() clusterProjection.Name = expectedClusterName clusterProjection.ApiServerAddress = expectedClusterApiServerAddress + clusterAccessProjection := &api_projections.ClusterAccess{ + Cluster: clusterProjection.Proto(), + Roles: []string{ + string(k8s.DefaultRole), + }, + } + ctxWithUser := mdManager.GetContext() - clusterRepo.EXPECT().ById(ctxWithUser, expectedClusterId).Return(clusterProjection, nil) + clusterAccessRepo.EXPECT().GetClustersAccessibleByUserId(ctxWithUser, expectedUserId).Return([]*api_projections.ClusterAccess{clusterAccessProjection}, nil) err := uc.Run(ctxWithUser) Expect(err).ToNot(HaveOccurred()) Expect(result).ToNot(BeNil()) Expect(result.AccessToken).ToNot(BeEmpty()) }) + It("can not retrieve openid conf", func() { + clusterAccessRepo := mock_repos.NewMockClusterAccessRepository(mockCtrl) + + request := &api.ClusterAuthTokenRequest{ + ClusterId: expectedClusterId.String(), + Role: string(k8s.DefaultRole), + } + result := new(api.ClusterAuthTokenResponse) + uc := NewGetAuthTokenUsecase(request, result, jwtTestEnv.CreateSigner(), clusterAccessRepo, expectedIssuer, expectedValidity) + + mdManager.SetUserInformation(&metadata.UserInformation{ + Id: expectedUserId, + Name: expectedUserName, + Email: expectedUserEmail, + NotBefore: time.Now().UTC(), + }) + clusterProjection := projections.NewClusterProjection(expectedClusterId) + clusterProjection.Id = expectedClusterId.String() + clusterProjection.Name = expectedClusterName + clusterProjection.ApiServerAddress = expectedClusterApiServerAddress + + ctxWithUser := mdManager.GetContext() + clusterAccessRepo.EXPECT().GetClustersAccessibleByUserId(ctxWithUser, expectedUserId).Return([]*api_projections.ClusterAccess{}, nil) + + err := uc.Run(ctxWithUser) + Expect(err).To(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(result.AccessToken).To(BeEmpty()) + }) + It("can not retrieve openid conf for admin role", func() { + clusterAccessRepo := mock_repos.NewMockClusterAccessRepository(mockCtrl) + + request := &api.ClusterAuthTokenRequest{ + ClusterId: expectedClusterId.String(), + Role: string(k8s.AdminRole), + } + result := new(api.ClusterAuthTokenResponse) + uc := NewGetAuthTokenUsecase(request, result, jwtTestEnv.CreateSigner(), clusterAccessRepo, expectedIssuer, expectedValidity) + + mdManager.SetUserInformation(&metadata.UserInformation{ + Id: expectedUserId, + Name: expectedUserName, + Email: expectedUserEmail, + NotBefore: time.Now().UTC(), + }) + + clusterProjection := projections.NewClusterProjection(expectedClusterId) + clusterProjection.Id = expectedClusterId.String() + clusterProjection.Name = expectedClusterName + clusterProjection.ApiServerAddress = expectedClusterApiServerAddress + + clusterAccessProjection := &api_projections.ClusterAccess{ + Cluster: clusterProjection.Proto(), + Roles: []string{ + string(k8s.DefaultRole), + }, + } + + ctxWithUser := mdManager.GetContext() + clusterAccessRepo.EXPECT().GetClustersAccessibleByUserId(ctxWithUser, expectedUserId).Return([]*api_projections.ClusterAccess{clusterAccessProjection}, nil) + + err := uc.Run(ctxWithUser) + Expect(err).To(HaveOccurred()) + Expect(result).ToNot(BeNil()) + Expect(result.AccessToken).To(BeEmpty()) + }) }) diff --git a/internal/test/domain/repositories/repositories.go b/internal/test/domain/repositories/repositories.go index 96299a804..0e7d9d78c 100644 --- a/internal/test/domain/repositories/repositories.go +++ b/internal/test/domain/repositories/repositories.go @@ -13,7 +13,7 @@ // limitations under the License. // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/finleap-connect/monoskope/pkg/domain/repositories (interfaces: UserRepository,ClusterRepository) +// Source: github.com/finleap-connect/monoskope/pkg/domain/repositories (interfaces: UserRepository,ClusterRepository,ClusterAccessRepository) // Package mock_repositories is a generated GoMock package. package mock_repositories @@ -22,7 +22,8 @@ import ( context "context" reflect "reflect" - projections "github.com/finleap-connect/monoskope/pkg/domain/projections" + projections "github.com/finleap-connect/monoskope/pkg/api/domain/projections" + projections0 "github.com/finleap-connect/monoskope/pkg/domain/projections" gomock "github.com/golang/mock/gomock" uuid "github.com/google/uuid" ) @@ -51,10 +52,10 @@ func (m *MockUserRepository) EXPECT() *MockUserRepositoryMockRecorder { } // All mocks base method. -func (m *MockUserRepository) All(arg0 context.Context) ([]*projections.User, error) { +func (m *MockUserRepository) All(arg0 context.Context) ([]*projections0.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "All", arg0) - ret0, _ := ret[0].([]*projections.User) + ret0, _ := ret[0].([]*projections0.User) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -66,10 +67,10 @@ func (mr *MockUserRepositoryMockRecorder) All(arg0 interface{}) *gomock.Call { } // AllWith mocks base method. -func (m *MockUserRepository) AllWith(arg0 context.Context, arg1 bool) ([]*projections.User, error) { +func (m *MockUserRepository) AllWith(arg0 context.Context, arg1 bool) ([]*projections0.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AllWith", arg0, arg1) - ret0, _ := ret[0].([]*projections.User) + ret0, _ := ret[0].([]*projections0.User) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -81,10 +82,10 @@ func (mr *MockUserRepositoryMockRecorder) AllWith(arg0, arg1 interface{}) *gomoc } // ByEmail mocks base method. -func (m *MockUserRepository) ByEmail(arg0 context.Context, arg1 string) (*projections.User, error) { +func (m *MockUserRepository) ByEmail(arg0 context.Context, arg1 string) (*projections0.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ByEmail", arg0, arg1) - ret0, _ := ret[0].(*projections.User) + ret0, _ := ret[0].(*projections0.User) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -96,10 +97,10 @@ func (mr *MockUserRepositoryMockRecorder) ByEmail(arg0, arg1 interface{}) *gomoc } // ById mocks base method. -func (m *MockUserRepository) ById(arg0 context.Context, arg1 uuid.UUID) (*projections.User, error) { +func (m *MockUserRepository) ById(arg0 context.Context, arg1 uuid.UUID) (*projections0.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ById", arg0, arg1) - ret0, _ := ret[0].(*projections.User) + ret0, _ := ret[0].(*projections0.User) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -111,10 +112,10 @@ func (mr *MockUserRepositoryMockRecorder) ById(arg0, arg1 interface{}) *gomock.C } // ByUserId mocks base method. -func (m *MockUserRepository) ByUserId(arg0 context.Context, arg1 uuid.UUID) (*projections.User, error) { +func (m *MockUserRepository) ByUserId(arg0 context.Context, arg1 uuid.UUID) (*projections0.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ByUserId", arg0, arg1) - ret0, _ := ret[0].(*projections.User) + ret0, _ := ret[0].(*projections0.User) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -155,7 +156,7 @@ func (mr *MockUserRepositoryMockRecorder) Remove(arg0, arg1 interface{}) *gomock } // Upsert mocks base method. -func (m *MockUserRepository) Upsert(arg0 context.Context, arg1 *projections.User) error { +func (m *MockUserRepository) Upsert(arg0 context.Context, arg1 *projections0.User) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Upsert", arg0, arg1) ret0, _ := ret[0].(error) @@ -192,10 +193,10 @@ func (m *MockClusterRepository) EXPECT() *MockClusterRepositoryMockRecorder { } // All mocks base method. -func (m *MockClusterRepository) All(arg0 context.Context) ([]*projections.Cluster, error) { +func (m *MockClusterRepository) All(arg0 context.Context) ([]*projections0.Cluster, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "All", arg0) - ret0, _ := ret[0].([]*projections.Cluster) + ret0, _ := ret[0].([]*projections0.Cluster) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -207,10 +208,10 @@ func (mr *MockClusterRepositoryMockRecorder) All(arg0 interface{}) *gomock.Call } // AllWith mocks base method. -func (m *MockClusterRepository) AllWith(arg0 context.Context, arg1 bool) ([]*projections.Cluster, error) { +func (m *MockClusterRepository) AllWith(arg0 context.Context, arg1 bool) ([]*projections0.Cluster, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AllWith", arg0, arg1) - ret0, _ := ret[0].([]*projections.Cluster) + ret0, _ := ret[0].([]*projections0.Cluster) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -222,10 +223,10 @@ func (mr *MockClusterRepositoryMockRecorder) AllWith(arg0, arg1 interface{}) *go } // ByClusterName mocks base method. -func (m *MockClusterRepository) ByClusterName(arg0 context.Context, arg1 string) (*projections.Cluster, error) { +func (m *MockClusterRepository) ByClusterName(arg0 context.Context, arg1 string) (*projections0.Cluster, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ByClusterName", arg0, arg1) - ret0, _ := ret[0].(*projections.Cluster) + ret0, _ := ret[0].(*projections0.Cluster) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -237,10 +238,10 @@ func (mr *MockClusterRepositoryMockRecorder) ByClusterName(arg0, arg1 interface{ } // ById mocks base method. -func (m *MockClusterRepository) ById(arg0 context.Context, arg1 uuid.UUID) (*projections.Cluster, error) { +func (m *MockClusterRepository) ById(arg0 context.Context, arg1 uuid.UUID) (*projections0.Cluster, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ById", arg0, arg1) - ret0, _ := ret[0].(*projections.Cluster) + ret0, _ := ret[0].(*projections0.Cluster) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -266,7 +267,7 @@ func (mr *MockClusterRepositoryMockRecorder) Remove(arg0, arg1 interface{}) *gom } // Upsert mocks base method. -func (m *MockClusterRepository) Upsert(arg0 context.Context, arg1 *projections.Cluster) error { +func (m *MockClusterRepository) Upsert(arg0 context.Context, arg1 *projections0.Cluster) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Upsert", arg0, arg1) ret0, _ := ret[0].(error) @@ -278,3 +279,41 @@ func (mr *MockClusterRepositoryMockRecorder) Upsert(arg0, arg1 interface{}) *gom mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upsert", reflect.TypeOf((*MockClusterRepository)(nil).Upsert), arg0, arg1) } + +// MockClusterAccessRepository is a mock of ClusterAccessRepository interface. +type MockClusterAccessRepository struct { + ctrl *gomock.Controller + recorder *MockClusterAccessRepositoryMockRecorder +} + +// MockClusterAccessRepositoryMockRecorder is the mock recorder for MockClusterAccessRepository. +type MockClusterAccessRepositoryMockRecorder struct { + mock *MockClusterAccessRepository +} + +// NewMockClusterAccessRepository creates a new mock instance. +func NewMockClusterAccessRepository(ctrl *gomock.Controller) *MockClusterAccessRepository { + mock := &MockClusterAccessRepository{ctrl: ctrl} + mock.recorder = &MockClusterAccessRepositoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockClusterAccessRepository) EXPECT() *MockClusterAccessRepositoryMockRecorder { + return m.recorder +} + +// GetClustersAccessibleByUserId mocks base method. +func (m *MockClusterAccessRepository) GetClustersAccessibleByUserId(arg0 context.Context, arg1 uuid.UUID) ([]*projections.ClusterAccess, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetClustersAccessibleByUserId", arg0, arg1) + ret0, _ := ret[0].([]*projections.ClusterAccess) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetClustersAccessibleByUserId indicates an expected call of GetClustersAccessibleByUserId. +func (mr *MockClusterAccessRepositoryMockRecorder) GetClustersAccessibleByUserId(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClustersAccessibleByUserId", reflect.TypeOf((*MockClusterAccessRepository)(nil).GetClustersAccessibleByUserId), arg0, arg1) +} diff --git a/pkg/domain/repositories/cluster_access_repo.go b/pkg/domain/repositories/cluster_access_repo.go index 135ee07a6..625adadd4 100644 --- a/pkg/domain/repositories/cluster_access_repo.go +++ b/pkg/domain/repositories/cluster_access_repo.go @@ -68,31 +68,52 @@ func (r *clusterAccessRepository) GetClustersAccessibleByUserId(ctx context.Cont return } + // check if user is system admin var isSystemAdmin = false for _, roleBinding := range roleBindings { isSystemAdmin = isSystemAdmin || (roleBinding.Scope == string(scopes.System) && roleBinding.Role == string(roles.Admin)) + } - // search rolebindings for tenant scoped bindings - if roleBinding.Scope == string(scopes.Tenant) { - // Set roles within cluster - var k8sRoles []string = []string{ - string(k8s.DefaultRole), - } - // System admins are admins in k8s clusters too - if isSystemAdmin { - k8sRoles = append(k8sRoles, string(k8s.AdminRole)) - } - if roleBinding.Role == string(roles.OnCall) { - k8sRoles = append(k8sRoles, string(k8s.OnCallRole)) - } + if isSystemAdmin { // system admins have access to all clusters + var c []*domain_projections.Cluster + c, err = r.clusterRepo.AllWith(ctx, false) + if err != nil { + return + } + for _, cluster := range c { + clusters = append(clusters, + &projections.ClusterAccess{ + Cluster: cluster.Cluster, + Roles: []string{ + string(k8s.DefaultRole), + string(k8s.OnCallRole), + string(k8s.AdminRole), + }}) + } + } else { // regular users have access based on tenant membership + for _, roleBinding := range roleBindings { + // search rolebindings for tenant scoped bindings + if roleBinding.Scope == string(scopes.Tenant) { + // Set roles within cluster + var k8sRoles []string = []string{ + string(k8s.DefaultRole), + } + // System admins are admins in k8s clusters too + if isSystemAdmin { + k8sRoles = append(k8sRoles, string(k8s.AdminRole)) + } + if roleBinding.Role == string(roles.OnCall) { + k8sRoles = append(k8sRoles, string(k8s.OnCallRole)) + } - // get accessible cluster by tenant and append - var bindings []*domain_projections.TenantClusterBinding - bindings, err = r.tenantClusterBindingRepo.GetByTenantId(ctx, uuid.MustParse(roleBinding.GetResource())) - if err != nil { - return + // get accessible cluster by tenant and append + var bindings []*domain_projections.TenantClusterBinding + bindings, err = r.tenantClusterBindingRepo.GetByTenantId(ctx, uuid.MustParse(roleBinding.GetResource())) + if err != nil { + return + } + clusters, err = r.getClustersByBindings(ctx, bindings, k8sRoles) } - clusters, err = r.getClustersByBindings(ctx, bindings, k8sRoles) } } return