diff --git a/docs/user.md b/docs/user.md index 3683fdf61..54af617a9 100644 --- a/docs/user.md +++ b/docs/user.md @@ -150,21 +150,44 @@ user. There are two ways to define them: #### Infrastructure roles secret -The infrastructure roles secret is specified by the `infrastructure_roles_secret_name` -parameter. The role definition looks like this (values are base64 encoded): +Infrastructure roles can be specified by the `infrastructure_roles_secrets` +parameter where you can reference multiple existing secrets. Prior to `v1.6.0` +the operator could only reference one secret with the +`infrastructure_roles_secret_name` option. However, this secret could contain +multiple roles using the same set of keys with incremented indexes. ```yaml -user1: ZGJ1c2Vy -password1: c2VjcmV0 -inrole1: b3BlcmF0b3I= +apiVersion: v1 +kind: Secret +metadata: + name: postgresql-infrastructure-roles +data: + user1: ZGJ1c2Vy + password1: c2VjcmV0 + inrole1: b3BlcmF0b3I= + user2: ... ``` The block above describes the infrastructure role 'dbuser' with password -'secret' that is a member of the 'operator' role. For the following definitions -one must increase the index, i.e. the next role will be defined as 'user2' and -so on. The resulting role will automatically be a login role. +'secret' that is a member of the 'operator' role. The resulting role will +automatically be a login role. -Note that with definitions that solely use the infrastructure roles secret +With the new option the user can configure the names of secret keys that +contain the user name, password etc. If the secret uses a template for +multiple roles as described above, the `template` flag must be set to `true`. +The secret itself is referenced by the `secretname` key. Please, refer to the +example manifests to understand who `infrastructure_roles_secrets` has to be +configured for the [configmap](../manifests/configmap.yaml) (can only +reference one existing secret) or [CRD configuration](../manifests/postgresql-operator-default-configuration.yaml) +(reference multiple secret definitions in an array). + +If both `infrastructure_roles_secret_name` and `infrastructure_roles_secrets` +are defined the operator will create roles for both of them. So make sure, +they do not collide. To migrate to the new format use the same names for the +secret keys as in the example above and unset the +`infrastructure_roles_secret_name`. + +Note, that with definitions that solely use the infrastructure roles secret there is no way to specify role options (like superuser or nologin) or role memberships. This is where the ConfigMap comes into play. diff --git a/manifests/configmap.yaml b/manifests/configmap.yaml index d1c1b3d17..78dd470bd 100644 --- a/manifests/configmap.yaml +++ b/manifests/configmap.yaml @@ -47,7 +47,8 @@ data: # etcd_host: "" # gcp_credentials: "" # kubernetes_use_configmaps: "false" - # infrastructure_roles_secret_name: postgresql-infrastructure-roles + # infrastructure_roles_secret_name: "postgresql-infrastructure-roles" + # infrastructure_roles_secrets: "secretname:monitoring-roles,userkey:user,passwordkey:password,rolekey:inrole,template:true" # inherited_labels: application,environment # kube_iam_role: "" # log_s3_bucket: "" diff --git a/manifests/postgresql-operator-default-configuration.yaml b/manifests/postgresql-operator-default-configuration.yaml index f7eba1f6c..c0ebe29a2 100644 --- a/manifests/postgresql-operator-default-configuration.yaml +++ b/manifests/postgresql-operator-default-configuration.yaml @@ -39,6 +39,16 @@ configuration: enable_pod_disruption_budget: true enable_sidecars: true # infrastructure_roles_secret_name: "postgresql-infrastructure-roles" + # infrastructure_roles_secrets: + # - secretname: "monitoring-roles" + # userkey: "user" + # passwordkey: "password" + # rolekey: "inrole" + # template: true + # - secretname: "other-infrastructure-role" + # userkey: "other-user-key" + # passwordkey: "other-password-key" + # template: false # inherited_labels: # - application # - environment diff --git a/pkg/controller/operator_config.go b/pkg/controller/operator_config.go index 5ee419db9..d115aa118 100644 --- a/pkg/controller/operator_config.go +++ b/pkg/controller/operator_config.go @@ -79,10 +79,10 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur result.InfrastructureRoles = append( result.InfrastructureRoles, &config.InfrastructureRole{ - SecretName: secret.SecretName, - Name: secret.Name, - Role: secret.Role, - Password: secret.Password, + SecretName: secret.SecretName, + UserKey: secret.UserKey, + RoleKey: secret.RoleKey, + PasswordKey: secret.PasswordKey, }) } } diff --git a/pkg/controller/util.go b/pkg/controller/util.go index 50402cf5e..c87d4745c 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strconv" "strings" v1 "k8s.io/api/core/v1" @@ -132,6 +133,8 @@ func (c *Controller) getInfrastructureRoleDefinitions() []*config.Infrastructure // form key1: value1, key2: value2), which has to be used together with // an old secret name. + var secretName spec.NamespacedName + var err error propertySep := "," valueSep := ":" @@ -139,11 +142,6 @@ func (c *Controller) getInfrastructureRoleDefinitions() []*config.Infrastructure // convert it to a proper definition properties := strings.Split(c.opConfig.InfrastructureRolesDefs, propertySep) - roleDef = config.InfrastructureRole{ - SecretName: c.opConfig.InfrastructureRolesSecretName, - Template: false, - } - for _, property := range properties { values := strings.Split(property, valueSep) if len(values) < 2 { @@ -153,15 +151,24 @@ func (c *Controller) getInfrastructureRoleDefinitions() []*config.Infrastructure value := strings.TrimSpace(values[1]) switch name { - case "name": - roleDef.Name = value - case "password": - roleDef.Password = value - case "role": - roleDef.Role = value + case "secretname": + if err = secretName.DecodeWorker(value, "default"); err != nil { + c.logger.Warningf("Could not marshal secret name %s: %v", value, err) + } else { + roleDef.SecretName = secretName + } + case "userkey": + roleDef.UserKey = value + case "passwordkey": + roleDef.PasswordKey = value + case "rolekey": + roleDef.RoleKey = value + case "template": + if roleDef.Template, err = strconv.ParseBool(value); err != nil { + c.logger.Warningf("Could not extract template information %s: %v", value, err) + } default: - c.logger.Warningf("Role description is not known: %s", - c.opConfig.InfrastructureRolesSecretName) + c.logger.Warningf("Role description is not known: %s", properties) } } } else { @@ -169,17 +176,17 @@ func (c *Controller) getInfrastructureRoleDefinitions() []*config.Infrastructure // via existing definition structure and remember that it's just a // template, the real values are in user1,password1,inrole1 etc. roleDef = config.InfrastructureRole{ - SecretName: c.opConfig.InfrastructureRolesSecretName, - Name: "user", - Password: "password", - Role: "inrole", - Template: true, + SecretName: c.opConfig.InfrastructureRolesSecretName, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: true, } } - if roleDef.Name != "" && - roleDef.Password != "" && - roleDef.Role != "" { + if roleDef.UserKey != "" && + roleDef.PasswordKey != "" && + roleDef.RoleKey != "" { rolesDefs = append(rolesDefs, &roleDef) } @@ -280,9 +287,9 @@ func (c *Controller) getInfrastructureRole( Users: for i := 1; i <= len(secretData); i++ { properties := []string{ - infraRole.Name, - infraRole.Password, - infraRole.Role, + infraRole.UserKey, + infraRole.PasswordKey, + infraRole.RoleKey, } t := spec.PgUser{Origin: spec.RoleOriginInfrastructure} for _, p := range properties { @@ -327,9 +334,9 @@ func (c *Controller) getInfrastructureRole( return nil, fmt.Errorf("could not decode yaml role: %v", err) } } else { - roleDescr.Name = string(secretData[infraRole.Name]) - roleDescr.Password = string(secretData[infraRole.Password]) - roleDescr.MemberOf = append(roleDescr.MemberOf, string(secretData[infraRole.Role])) + roleDescr.Name = string(secretData[infraRole.UserKey]) + roleDescr.Password = string(secretData[infraRole.PasswordKey]) + roleDescr.MemberOf = append(roleDescr.MemberOf, string(secretData[infraRole.RoleKey])) } if roleDescr.Valid() { diff --git a/pkg/controller/util_test.go b/pkg/controller/util_test.go index de11c18aa..fd756a0c7 100644 --- a/pkg/controller/util_test.go +++ b/pkg/controller/util_test.go @@ -132,11 +132,11 @@ func TestOldInfrastructureRoleFormat(t *testing.T) { roles, errors := utilTestController.getInfrastructureRoles( []*config.InfrastructureRole{ &config.InfrastructureRole{ - SecretName: test.secretName, - Name: "user", - Password: "password", - Role: "inrole", - Template: true, + SecretName: test.secretName, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: true, }, }) @@ -231,11 +231,11 @@ func TestNewInfrastructureRoleFormat(t *testing.T) { definitions := []*config.InfrastructureRole{} for _, secret := range test.secrets { definitions = append(definitions, &config.InfrastructureRole{ - SecretName: secret, - Name: "user", - Password: "password", - Role: "inrole", - Template: false, + SecretName: secret, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: false, }) } @@ -287,10 +287,10 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesNewSecretName, }, - Name: "user", - Password: "password", - Role: "inrole", - Template: false, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: false, }, }, spec.NamespacedName{}, @@ -301,10 +301,10 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesNewSecretName, }, - Name: "user", - Password: "password", - Role: "inrole", - Template: false, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: false, }, }, }, @@ -322,10 +322,10 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesOldSecretName, }, - Name: "user", - Password: "password", - Role: "inrole", - Template: true, + UserKey: "user", + PasswordKey: "password", + RoleKey: "inrole", + Template: true, }, }, }, @@ -336,17 +336,17 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesOldSecretName, }, - "name: test-user, password: test-password, role: test-role", + "secretname: infrastructureroles-old-test, userkey: test-user, passwordkey: test-password, rolekey: test-role, template: false", []*config.InfrastructureRole{ &config.InfrastructureRole{ SecretName: spec.NamespacedName{ Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesOldSecretName, }, - Name: "test-user", - Password: "test-password", - Role: "test-role", - Template: false, + UserKey: "test-user", + PasswordKey: "test-password", + RoleKey: "test-role", + Template: false, }, }, }, @@ -364,7 +364,7 @@ func TestInfrastructureRoleDefinitions(t *testing.T) { { []*config.InfrastructureRole{}, spec.NamespacedName{}, - "name: test-user, password: test-password, role: test-role", + "userkey: test-user, passwordkey: test-password, rolekey: test-role, template: false", []*config.InfrastructureRole{}, }, } diff --git a/pkg/util/config/config.go b/pkg/util/config/config.go index e44d703cf..5f262107f 100644 --- a/pkg/util/config/config.go +++ b/pkg/util/config/config.go @@ -57,18 +57,18 @@ type InfrastructureRole struct { // configmap with an extra information SecretName spec.NamespacedName - Name string - Password string - Role string + UserKey string + PasswordKey string + RoleKey string // This field point out the detailed yaml definition of the role, if exists Details string // Specify if a secret contains multiple fields in the following format: // - // %(name)idx: ... - // %(password)idx: ... - // %(role)idx: ... + // %(userkey)idx: ... + // %(passwordkey)idx: ... + // %(rolekey)idx: ... // // If it does, Name/Password/Role are interpreted not as unique field // names, but as a template.