From 9b5e3906fd93907e02760eccdd6cdb05b8a2368d Mon Sep 17 00:00:00 2001 From: Justin Kolberg Date: Thu, 21 Jul 2022 00:11:50 -0700 Subject: [PATCH] fix seeds package Signed-off-by: Justin Kolberg --- backend/seeds/api_keys.go | 48 +++ backend/seeds/cluster_role_bindings.go | 89 +++++ backend/seeds/cluster_roles.go | 149 +++++++++ backend/seeds/namespaces.go | 46 +++ backend/seeds/seeds.go | 306 +----------------- backend/seeds/seeds_test.go | 26 +- backend/seeds/users.go | 74 +++++ backend/store/v2/storetest/namespace_store.go | 63 ++++ 8 files changed, 502 insertions(+), 299 deletions(-) create mode 100644 backend/seeds/api_keys.go create mode 100644 backend/seeds/cluster_role_bindings.go create mode 100644 backend/seeds/cluster_roles.go create mode 100644 backend/seeds/namespaces.go create mode 100644 backend/seeds/users.go create mode 100644 backend/store/v2/storetest/namespace_store.go diff --git a/backend/seeds/api_keys.go b/backend/seeds/api_keys.go new file mode 100644 index 0000000000..2ee23e368e --- /dev/null +++ b/backend/seeds/api_keys.go @@ -0,0 +1,48 @@ +package seeds + +import ( + "context" + "errors" + "fmt" + "time" + + corev2 "github.com/sensu/sensu-go/api/core/v2" + "github.com/sensu/sensu-go/backend/store" + storev2 "github.com/sensu/sensu-go/backend/store/v2" +) + +func setupAPIKeys(ctx context.Context, s storev2.Interface, config Config) error { + apiKeys := []*corev2.APIKey{} + + if config.AdminAPIKey != "" { + apiKey := adminAPIKey(config.AdminUsername, config.AdminAPIKey) + apiKeys = append(apiKeys, apiKey) + } + + for _, apiKey := range apiKeys { + name := apiKey.ObjectMeta.Name + + if err := createResource(ctx, s, apiKey); err != nil { + var alreadyExists *store.ErrAlreadyExists + if !errors.As(err, &alreadyExists) { + msg := fmt.Sprintf("could not initialize the %s api key", name) + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + logger.Warnf("%s api key already exists", name) + } + } + + return nil +} + +func adminAPIKey(username, apiKey string) *corev2.APIKey { + return &corev2.APIKey{ + ObjectMeta: corev2.ObjectMeta{ + Name: apiKey, + CreatedBy: username, + }, + Username: username, + CreatedAt: time.Now().Unix(), + } +} diff --git a/backend/seeds/cluster_role_bindings.go b/backend/seeds/cluster_role_bindings.go new file mode 100644 index 0000000000..39954d0050 --- /dev/null +++ b/backend/seeds/cluster_role_bindings.go @@ -0,0 +1,89 @@ +package seeds + +import ( + "context" + "errors" + "fmt" + + corev2 "github.com/sensu/sensu-go/api/core/v2" + "github.com/sensu/sensu-go/backend/store" + storev2 "github.com/sensu/sensu-go/backend/store/v2" +) + +func setupClusterRoleBindings(ctx context.Context, s storev2.Interface, config Config) error { + clusterRoleBindings := []*corev2.ClusterRoleBinding{ + clusterAdminClusterRoleBinding(), + systemAgentClusterRoleBinding(), + systemUserClusterRoleBinding(), + } + + for _, clusterRoleBinding := range clusterRoleBindings { + name := clusterRoleBinding.ObjectMeta.Name + + if err := createResource(ctx, s, clusterRoleBinding); err != nil { + var alreadyExists *store.ErrAlreadyExists + if !errors.As(err, &alreadyExists) { + msg := fmt.Sprintf("could not initialize the %s cluster role binding", name) + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + logger.Warnf("%s cluster role binding already exists", name) + } + } + + return nil +} + +func clusterAdminClusterRoleBinding() *corev2.ClusterRoleBinding { + // The cluster-admin ClusterRoleBinding grants permission found in the + // cluster-admin ClusterRole to any user belonging to the cluster-admins group + return &corev2.ClusterRoleBinding{ + ObjectMeta: corev2.NewObjectMeta("cluster-admin", ""), + RoleRef: corev2.RoleRef{ + Type: "ClusterRole", + Name: "cluster-admin", + }, + Subjects: []corev2.Subject{ + { + Type: "Group", + Name: "cluster-admins", + }, + }, + } +} + +func systemAgentClusterRoleBinding() *corev2.ClusterRoleBinding { + // The system:agent ClusterRoleBinding grants permission found in the + // system-agent ClusterRole to any agents belonging to the system:agents group + return &corev2.ClusterRoleBinding{ + ObjectMeta: corev2.NewObjectMeta("system:agent", ""), + RoleRef: corev2.RoleRef{ + Type: "ClusterRole", + Name: "system:agent", + }, + Subjects: []corev2.Subject{ + { + Type: "Group", + Name: "system:agents", + }, + }, + } +} + +func systemUserClusterRoleBinding() *corev2.ClusterRoleBinding { + // The system:user ClusterRoleBinding grants permission found in the + // system:user ClusterRole to any user belonging to the system:users group + return &corev2.ClusterRoleBinding{ + ObjectMeta: corev2.NewObjectMeta("system:user", ""), + RoleRef: corev2.RoleRef{ + Type: "ClusterRole", + Name: "system:user", + }, + Subjects: []corev2.Subject{ + { + Type: "Group", + Name: "system:users", + }, + }, + } +} diff --git a/backend/seeds/cluster_roles.go b/backend/seeds/cluster_roles.go new file mode 100644 index 0000000000..0413e784fe --- /dev/null +++ b/backend/seeds/cluster_roles.go @@ -0,0 +1,149 @@ +package seeds + +import ( + "context" + "errors" + "fmt" + + corev2 "github.com/sensu/sensu-go/api/core/v2" + "github.com/sensu/sensu-go/backend/store" + storev2 "github.com/sensu/sensu-go/backend/store/v2" +) + +func setupClusterRoles(ctx context.Context, s storev2.Interface, config Config) error { + clusterRoles := []*corev2.ClusterRole{ + clusterAdminClusterRole(), + adminClusterRole(), + editClusterRole(), + viewClusterRole(), + systemAgentClusterRole(), + systemUserClusterRole(), + } + + for _, clusterRole := range clusterRoles { + name := clusterRole.ObjectMeta.Name + + if err := createResource(ctx, s, clusterRole); err != nil { + var alreadyExists *store.ErrAlreadyExists + if !errors.As(err, &alreadyExists) { + msg := fmt.Sprintf("could not initialize the %s cluster role", name) + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + logger.Warnf("%s cluster role already exists", name) + } + } + + return nil +} + +func clusterAdminClusterRole() *corev2.ClusterRole { + // The cluster-admin ClusterRole gives access to perform any action on any + // resource. When used in a ClusterRoleBinding, it gives full control over + // every resource in the cluster and in all namespaces. When used in a + // RoleBinding, it gives full control over every resource in the rolebinding's + // namespace, including the namespace itself + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("cluster-admin", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{corev2.VerbAll}, + Resources: []string{corev2.ResourceAll}, + }, + }, + } +} + +func adminClusterRole() *corev2.ClusterRole { + // The admin ClusterRole is intended to be used within a namespace using a + // RoleBinding. It gives full access to most resources, including the ability + // to create Roles and RoleBindings within the namespace but does not allow + // write access to the namespace itself + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("admin", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{corev2.VerbAll}, + Resources: append(corev2.CommonCoreResources, []string{ + "roles", + "rolebindings", + }...), + }, + { + Verbs: []string{"get", "list"}, + Resources: []string{ + "namespaces", + }, + }, + }, + } +} + +func editClusterRole() *corev2.ClusterRole { + // The edit ClusterRole is intended to be used within a namespace using a + // RoleBinding. It allows read/write access to most objects in a namespace. It + // does not allow viewing or modifying roles or rolebindings. + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("edit", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{corev2.VerbAll}, + Resources: corev2.CommonCoreResources, + }, + { + Verbs: []string{"get", "list"}, + Resources: []string{ + "namespaces", + }, + }, + }, + } +} + +func viewClusterRole() *corev2.ClusterRole { + // The view ClusterRole is intended to be used within a namespace using a + // RoleBinding. It allows read-only access to see most objects in a namespace. + // It does not allow viewing roles or rolebindings. + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("view", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{"get", "list"}, + Resources: append(corev2.CommonCoreResources, []string{ + "namespaces", + }...), + }, + }, + } +} + +func systemAgentClusterRole() *corev2.ClusterRole { + // The systemAgent ClusterRole is used by Sensu agents and should not be + // modified by the users. Modification to this ClusterRole can result in + // non-functional Sensu agents. + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("system:agent", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{corev2.VerbAll}, + Resources: []string{"events"}, + }, + }, + } +} + +func systemUserClusterRole() *corev2.ClusterRole { + // The systemUser ClusterRole is used by local users and should not be + // modified by the users. Modification to his ClusterRole can result in + // non-functional Sensu users. It allows users to view themselves and change + // their own password + return &corev2.ClusterRole{ + ObjectMeta: corev2.NewObjectMeta("system:user", ""), + Rules: []corev2.Rule{ + { + Verbs: []string{"get", "update"}, + Resources: []string{corev2.LocalSelfUserResource}, + }, + }, + } +} diff --git a/backend/seeds/namespaces.go b/backend/seeds/namespaces.go new file mode 100644 index 0000000000..743abcee4d --- /dev/null +++ b/backend/seeds/namespaces.go @@ -0,0 +1,46 @@ +package seeds + +import ( + "context" + "errors" + "fmt" + + corev2 "github.com/sensu/sensu-go/api/core/v2" + corev3 "github.com/sensu/sensu-go/api/core/v3" + "github.com/sensu/sensu-go/backend/store" + storev2 "github.com/sensu/sensu-go/backend/store/v2" +) + +func setupNamespaces(ctx context.Context, s storev2.Interface, config Config) error { + namespaces := []*corev3.Namespace{ + defaultNamespace(), + } + + nsStore := s.NamespaceStore() + + for _, namespace := range namespaces { + name := namespace.Metadata.Name + + if err := nsStore.CreateIfNotExists(ctx, namespace); err != nil { + var alreadyExists *store.ErrAlreadyExists + if !errors.As(err, &alreadyExists) { + msg := fmt.Sprintf("could not initialize the %s namespace", name) + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + logger.Warnf("%s namespace already exists", name) + } + } + + return nil +} + +func defaultNamespace() *corev3.Namespace { + return &corev3.Namespace{ + Metadata: &corev2.ObjectMeta{ + Name: "default", + Labels: make(map[string]string), + Annotations: make(map[string]string), + }, + } +} diff --git a/backend/seeds/seeds.go b/backend/seeds/seeds.go index ff22f0c24e..52a19dc7c3 100644 --- a/backend/seeds/seeds.go +++ b/backend/seeds/seeds.go @@ -4,11 +4,7 @@ import ( "context" "errors" "fmt" - "time" - corev2 "github.com/sensu/sensu-go/api/core/v2" - corev3 "github.com/sensu/sensu-go/api/core/v3" - "github.com/sensu/sensu-go/backend/authentication/bcrypt" "github.com/sensu/sensu-go/backend/store" storev2 "github.com/sensu/sensu-go/backend/store/v2" ) @@ -32,52 +28,23 @@ func seedCluster(ctx context.Context, s storev2.Interface, config Config) func(c logger.Info("seeding etcd store with initial data") return func(context.Context) error { - // Create the default namespace - if err := setupDefaultNamespace(ctx, s); err != nil { - var alreadyExists *store.ErrAlreadyExists - if !errors.As(err, &alreadyExists) { - msg := "unable to setup default namespace" - logger.WithError(err).Error(msg) - return fmt.Errorf("%s: %w", msg, err) - } - logger.Warn("default namespace already exists") + if err := setupNamespaces(ctx, s, config); err != nil { + return err } - - // Create the admin user - if err := setupAdminUser(ctx, s, config.AdminUsername, config.AdminPassword, config.AdminAPIKey); err != nil { - var alreadyExists *store.ErrAlreadyExists - if !errors.As(err, &alreadyExists) { - msg := "could not initialize the admin user" - logger.WithError(err).Error(msg) - return fmt.Errorf("%s: %w", msg, err) - } - logger.Warn("admin user already exists") + if err := setupUsers(ctx, s, config); err != nil { + return err } - - // Create the agent user - if err := setupAgentUser(ctx, s, "agent", "P@ssw0rd!"); err != nil { - var alreadyExists *store.ErrAlreadyExists - if !errors.As(err, &alreadyExists) { - msg := "could not initialize the agent user" - logger.WithError(err).Error(msg) - return fmt.Errorf("%s: %w", msg, err) - } - logger.Warn("agent user already exists") + if err := setupAPIKeys(ctx, s, config); err != nil { + return err } - - // Create the default ClusterRoles - if err := setupClusterRoles(ctx, s); err != nil { - var alreadyExists *store.ErrAlreadyExists - if !errors.As(err, &alreadyExists) { - msg := "could not initialize the default ClusterRoles and Roles" - logger.WithError(err).Error(msg) - return fmt.Errorf("%s: %w", msg, err) - } - logger.Warn("default ClusterRoles and Roles already exist") + if err := setupClusterRoles(ctx, s, config); err != nil { + return err + } + if err := setupClusterRoleBindings(ctx, s, config); err != nil { + return err } - // Create the default ClusterRoleBindings - if err := setupClusterRoleBindings(ctx, s); err != nil { + if err := setupClusterRoleBindings(ctx, s, config); err != nil { var alreadyExists *store.ErrAlreadyExists if !errors.As(err, &alreadyExists) { msg := "could not initialize the default ClusterRoleBindings" @@ -105,250 +72,7 @@ func SeedInitialDataWithContext(ctx context.Context, s storev2.Interface) (err e return SeedCluster(ctx, s, config) } -func createResources(ctx context.Context, s storev2.Interface, resources []corev3.Resource) error { - for _, resource := range resources { - req := storev2.NewResourceRequestFromResource(resource) - wrapper, err := storev2.WrapResource(resource) - if err != nil { - return err - } - if err := s.CreateIfNotExists(ctx, req, wrapper); err != nil { - return err - } - } - return nil -} - -func setupDefaultNamespace(ctx context.Context, s storev2.Interface) error { - namespace := &corev3.Namespace{ - Metadata: &corev2.ObjectMeta{ - Name: "default", - Labels: make(map[string]string), - Annotations: make(map[string]string), - }, - } - req := storev2.NewResourceRequestFromResource(namespace) - wrapper, err := storev2.WrapResource(namespace) - if err != nil { - return err - } - return s.CreateIfNotExists(ctx, req, wrapper) -} - -func setupClusterRoleBindings(ctx context.Context, s storev2.Interface) error { - // The cluster-admin ClusterRoleBinding grants permission found in the - // cluster-admin ClusterRole to any user belonging to the cluster-admins group - clusterAdmin := &corev2.ClusterRoleBinding{ - ObjectMeta: corev2.NewObjectMeta("cluster-admin", ""), - RoleRef: corev2.RoleRef{ - Type: "ClusterRole", - Name: "cluster-admin", - }, - Subjects: []corev2.Subject{ - { - Type: "Group", - Name: "cluster-admins", - }, - }, - } - - // The system:agent ClusterRoleBinding grants permission found in the - // system-agent ClusterRole to any agents belonging to the system:agents group - systemAgent := &corev2.ClusterRoleBinding{ - ObjectMeta: corev2.NewObjectMeta("system:agent", ""), - RoleRef: corev2.RoleRef{ - Type: "ClusterRole", - Name: "system:agent", - }, - Subjects: []corev2.Subject{ - { - Type: "Group", - Name: "system:agents", - }, - }, - } - - // The system:user ClusterRoleBinding grants permission found in the - // system:user ClusterRole to any user belonging to the system:users group - systemUser := &corev2.ClusterRoleBinding{ - ObjectMeta: corev2.NewObjectMeta("system:user", ""), - RoleRef: corev2.RoleRef{ - Type: "ClusterRole", - Name: "system:user", - }, - Subjects: []corev2.Subject{ - { - Type: "Group", - Name: "system:users", - }, - }, - } - - resources := []corev3.Resource{ - clusterAdmin, - systemAgent, - systemUser, - } - return createResources(ctx, s, resources) -} - -func setupClusterRoles(ctx context.Context, s storev2.Interface) error { - // The cluster-admin ClusterRole gives access to perform any action on any - // resource. When used in a ClusterRoleBinding, it gives full control over - // every resource in the cluster and in all namespaces. When used in a - // RoleBinding, it gives full control over every resource in the rolebinding's - // namespace, including the namespace itself - clusterAdmin := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("cluster-admin", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{corev2.VerbAll}, - Resources: []string{corev2.ResourceAll}, - }, - }, - } - - // The admin ClusterRole is intended to be used within a namespace using a - // RoleBinding. It gives full access to most resources, including the ability - // to create Roles and RoleBindings within the namespace but does not allow - // write access to the namespace itself - admin := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("admin", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{corev2.VerbAll}, - Resources: append(corev2.CommonCoreResources, []string{ - "roles", - "rolebindings", - }...), - }, - { - Verbs: []string{"get", "list"}, - Resources: []string{ - "namespaces", - }, - }, - }, - } - - // The edit ClusterRole is intended to be used within a namespace using a - // RoleBinding. It allows read/write access to most objects in a namespace. It - // does not allow viewing or modifying roles or rolebindings. - edit := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("edit", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{corev2.VerbAll}, - Resources: corev2.CommonCoreResources, - }, - { - Verbs: []string{"get", "list"}, - Resources: []string{ - "namespaces", - }, - }, - }, - } - - // The view ClusterRole is intended to be used within a namespace using a - // RoleBinding. It allows read-only access to see most objects in a namespace. - // It does not allow viewing roles or rolebindings. - view := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("view", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{"get", "list"}, - Resources: append(corev2.CommonCoreResources, []string{ - "namespaces", - }...), - }, - }, - } - - // The systemAgent ClusterRole is used by Sensu agents and should not be - // modified by the users. Modification to this ClusterRole can result in - // non-functional Sensu agents. - systemAgent := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("system:agent", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{corev2.VerbAll}, - Resources: []string{"events"}, - }, - }, - } - - // The systemUser ClusterRole is used by local users and should not be - // modified by the users. Modification to his ClusterRole can result in - // non-functional Sensu users. It allows users to view themselves and change - // their own password - systemUser := &corev2.ClusterRole{ - ObjectMeta: corev2.NewObjectMeta("system:user", ""), - Rules: []corev2.Rule{ - { - Verbs: []string{"get", "update"}, - Resources: []string{corev2.LocalSelfUserResource}, - }, - }, - } - - resources := []corev3.Resource{ - clusterAdmin, - admin, - edit, - view, - systemAgent, - systemUser, - } - - return createResources(ctx, s, resources) -} - -func setupAdminUser(ctx context.Context, s storev2.Interface, username, password, apiKey string) error { - hash, err := bcrypt.HashPassword(password) - if err != nil { - return err - } - - resources := []corev3.Resource{} - - admin := &corev2.User{ - Username: username, - Password: hash, - PasswordHash: hash, - Groups: []string{"cluster-admins"}, - } - resources = append(resources, admin) - - if apiKey != "" { - key := &corev2.APIKey{ - ObjectMeta: corev2.ObjectMeta{ - Name: apiKey, - CreatedBy: username, - }, - Username: username, - CreatedAt: time.Now().Unix(), - } - resources = append(resources, key) - } - - return createResources(ctx, s, resources) -} - -func setupAgentUser(ctx context.Context, s storev2.Interface, username, password string) error { - hash, err := bcrypt.HashPassword("P@ssw0rd!") - if err != nil { - return err - } - - agent := &corev2.User{ - Username: username, - Password: hash, - PasswordHash: hash, - Groups: []string{"system:agents"}, - } - - return createResources(ctx, s, []corev3.Resource{ - agent, - }) +func createResource[R storev2.Resource[T], T any](ctx context.Context, s storev2.Interface, resource R) error { + resourceStore := storev2.NewGenericStore[R](s) + return resourceStore.CreateIfNotExists(ctx, resource) } diff --git a/backend/seeds/seeds_test.go b/backend/seeds/seeds_test.go index 0047411b7c..01dba23893 100644 --- a/backend/seeds/seeds_test.go +++ b/backend/seeds/seeds_test.go @@ -5,7 +5,6 @@ import ( "testing" corev2 "github.com/sensu/sensu-go/api/core/v2" - corev3 "github.com/sensu/sensu-go/api/core/v3" storev2 "github.com/sensu/sensu-go/backend/store/v2" "github.com/sensu/sensu-go/backend/store/v2/storetest" "github.com/stretchr/testify/mock" @@ -20,9 +19,13 @@ func TestSeedInitialDataWithContext(t *testing.T) { AdminPassword: "P@ssw0rd!", } - // Setup store + // Setup stores + nsStore := new(storetest.NamespaceStore) + nsStore.On("CreateIfNotExists", mock.Anything, mock.Anything).Return(nil) + s := new(storetest.Store) - s.On("CreateIfNotExists", mock.Anything, mock.Anything).Return(nil) + s.On("NamespaceStore").Return(nsStore) + s.On("CreateIfNotExists", mock.Anything, mock.Anything, mock.Anything).Return(nil) s.On("Initialize", mock.Anything, mock.Anything).Return(seedCluster(ctx, s, config)(ctx)) sErr := SeedInitialDataWithContext(ctx, s) @@ -35,24 +38,22 @@ func TestSeedInitialDataWithContext(t *testing.T) { // store names userStoreName := (&corev2.User{}).StoreName() - namespaceStoreName := (&corev3.Namespace{}).StoreName() // type metas - namespaceTypeMeta := corev2.TypeMeta{Type: "Namespace", APIVersion: "core/v3"} userTypeMeta := corev2.TypeMeta{Type: "User", APIVersion: "core/v2"} // ensure the default namespace is created - s.AssertCalled(t, "CreateIfNotExists", - storev2.NewResourceRequest(namespaceTypeMeta, "", "default", namespaceStoreName), - mock.Anything) + nsStore.AssertCalled(t, "CreateIfNotExists", context.Background(), defaultNamespace()) // ensure the admin user is created s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequest(userTypeMeta, "", "admin", userStoreName), mock.Anything) // ensure the agent user is created s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequest(userTypeMeta, "", "agent", userStoreName), mock.Anything) @@ -67,6 +68,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(clusterAdminClusterRole), mock.Anything) @@ -90,6 +92,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(adminClusterRole), mock.Anything) @@ -110,6 +113,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(editClusterRole), mock.Anything) @@ -126,6 +130,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(viewClusterRole), mock.Anything) @@ -140,6 +145,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(systemAgentClusterRole), mock.Anything) @@ -154,6 +160,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(systemUserClusterRole), mock.Anything) @@ -172,6 +179,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(clusterAdminClusterRoleBinding), mock.Anything) @@ -190,6 +198,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(systemAgentClusterRoleBinding), mock.Anything) @@ -208,6 +217,7 @@ func TestSeedInitialDataWithContext(t *testing.T) { }, } s.AssertCalled(t, "CreateIfNotExists", + context.Background(), storev2.NewResourceRequestFromResource(systemUserClusterRoleBinding), mock.Anything) } diff --git a/backend/seeds/users.go b/backend/seeds/users.go new file mode 100644 index 0000000000..c6bb9ad8ff --- /dev/null +++ b/backend/seeds/users.go @@ -0,0 +1,74 @@ +package seeds + +import ( + "context" + "errors" + "fmt" + + corev2 "github.com/sensu/sensu-go/api/core/v2" + "github.com/sensu/sensu-go/backend/authentication/bcrypt" + "github.com/sensu/sensu-go/backend/store" + storev2 "github.com/sensu/sensu-go/backend/store/v2" +) + +type userFn func() (*corev2.User, error) + +func setupUsers(ctx context.Context, s storev2.Interface, config Config) error { + userFns := []userFn{ + adminUser(config.AdminUsername, config.AdminPassword), + agentUser(), + } + + for _, userFn := range userFns { + user, err := userFn() + if err != nil { + msg := "could not build user: %w" + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + + name := user.Username + + if err := createResource(ctx, s, user); err != nil { + var alreadyExists *store.ErrAlreadyExists + if !errors.As(err, &alreadyExists) { + msg := fmt.Sprintf("could not initialize the %s user", name) + logger.WithError(err).Error(msg) + return fmt.Errorf("%s: %w", msg, err) + } + logger.Warnf("%s user already exists", name) + } + } + + return nil +} + +func buildUser(username, password string, groups []string) (*corev2.User, error) { + hash, err := bcrypt.HashPassword(password) + if err != nil { + return nil, err + } + + return &corev2.User{ + Username: username, + Password: hash, + PasswordHash: hash, + Groups: groups, + }, nil +} + +func adminUser(username, password string) userFn { + return func() (*corev2.User, error) { + return buildUser(username, password, []string{"cluster-admins"}) + } +} + +func agentUser() userFn { + username := "agent" + password := "P@ssw0rd!" + groups := []string{"system:agents"} + + return func() (*corev2.User, error) { + return buildUser(username, password, groups) + } +} diff --git a/backend/store/v2/storetest/namespace_store.go b/backend/store/v2/storetest/namespace_store.go new file mode 100644 index 0000000000..6237831023 --- /dev/null +++ b/backend/store/v2/storetest/namespace_store.go @@ -0,0 +1,63 @@ +package storetest + +import ( + "context" + + corev3 "github.com/sensu/sensu-go/api/core/v3" + "github.com/sensu/sensu-go/backend/store" + "github.com/sensu/sensu-go/backend/store/patch" + storev2 "github.com/sensu/sensu-go/backend/store/v2" + "github.com/stretchr/testify/mock" +) + +var _ storev2.NamespaceStore = new(NamespaceStore) + +type NamespaceStore struct { + mock.Mock +} + +func (s *NamespaceStore) CreateIfNotExists(ctx context.Context, namespace *corev3.Namespace) error { + args := s.Called(ctx, namespace) + return args.Error(0) +} + +func (s *NamespaceStore) CreateOrUpdate(ctx context.Context, namespace *corev3.Namespace) error { + args := s.Called(ctx, namespace) + return args.Error(0) +} + +func (s *NamespaceStore) Delete(ctx context.Context, namespace string) error { + args := s.Called(ctx, namespace) + return args.Error(0) +} + +func (s *NamespaceStore) Exists(ctx context.Context, namespace string) (bool, error) { + args := s.Called(ctx, namespace) + return args.Get(0).(bool), args.Error(1) +} + +func (s *NamespaceStore) Get(ctx context.Context, namespace string) (*corev3.Namespace, error) { + args := s.Called(ctx, namespace) + w, _ := args.Get(0).(*corev3.Namespace) + return w, args.Error(1) +} + +func (s *NamespaceStore) IsEmpty(ctx context.Context, namespace string) (bool, error) { + args := s.Called(ctx, namespace) + return args.Get(0).(bool), args.Error(1) +} + +func (s *NamespaceStore) List(ctx context.Context, pred *store.SelectionPredicate) ([]*corev3.Namespace, error) { + args := s.Called(ctx, pred) + return args.Get(0).([]*corev3.Namespace), args.Error(1) +} + +func (s *NamespaceStore) Patch(ctx context.Context, namespace string, patcher patch.Patcher, conditions *store.ETagCondition) error { + args := s.Called(ctx, namespace, patcher, conditions) + return args.Error(0) +} + +func (s *NamespaceStore) UpdateIfExists(ctx context.Context, namespace *corev3.Namespace) error { + args := s.Called(ctx, namespace) + return args.Error(0) +}