Extend infrastructure roles handling
Postgres Operator uses infrastructure roles to provide access to a
database for external users e.g. for monitoring purposes. Such
infrastructure roles are expected to be present in the form of k8s
secrets with the following content:
    inrole1: some_encrypted_role
    password1: some_encrypted_password
    user1: some_entrypted_name
    inrole2: some_encrypted_role
    password2: some_encrypted_password
    user2: some_entrypted_name
The format of this content is implied implicitely and not flexible
enough. In case if we do not have possibility to change the format of a
secret we want to use in the Operator, we need to recreate it in this
format.
To address this lets make the format of secret content explicitely. The
idea is to introduce a new configuration option for the Operator.
    infrastructure_roles_secrets:
    - secret: k8s_secret_name
      name: some_encrypted_name
      password: some_encrypted_password
      role: some_encrypted_role
    - secret: k8s_secret_name
      name: some_encrypted_name
      password: some_encrypted_password
      role: some_encrypted_role
This would allow Operator to use any avalable secrets to prepare
infrastructure roles. To make it backward compatible simulate the old
behaviour if the new option is not present.
The new configuration option is intended be used mainly from CRD, but
it's also available via Operator ConfigMap in a limited fashion. For
ConfigMap one can put there only a string with one secret definition in
the following format (as a string):
    infrastructure_roles_secret_name: |
        secret: k8s_secret_name,
        name: some_encrypted_name,
        password: some_encrypted_password,
        role: some_encrypted_role
			
			
This commit is contained in:
		
							parent
							
								
									c10d30903e
								
							
						
					
					
						commit
						bd576942f2
					
				| 
						 | 
					@ -45,28 +45,29 @@ type PostgresUsersConfiguration struct {
 | 
				
			||||||
type KubernetesMetaConfiguration struct {
 | 
					type KubernetesMetaConfiguration struct {
 | 
				
			||||||
	PodServiceAccountName string `json:"pod_service_account_name,omitempty"`
 | 
						PodServiceAccountName string `json:"pod_service_account_name,omitempty"`
 | 
				
			||||||
	// TODO: change it to the proper json
 | 
						// TODO: change it to the proper json
 | 
				
			||||||
	PodServiceAccountDefinition            string                `json:"pod_service_account_definition,omitempty"`
 | 
						PodServiceAccountDefinition            string                       `json:"pod_service_account_definition,omitempty"`
 | 
				
			||||||
	PodServiceAccountRoleBindingDefinition string                `json:"pod_service_account_role_binding_definition,omitempty"`
 | 
						PodServiceAccountRoleBindingDefinition string                       `json:"pod_service_account_role_binding_definition,omitempty"`
 | 
				
			||||||
	PodTerminateGracePeriod                Duration              `json:"pod_terminate_grace_period,omitempty"`
 | 
						PodTerminateGracePeriod                Duration                     `json:"pod_terminate_grace_period,omitempty"`
 | 
				
			||||||
	SpiloPrivileged                        bool                  `json:"spilo_privileged,omitempty"`
 | 
						SpiloPrivileged                        bool                         `json:"spilo_privileged,omitempty"`
 | 
				
			||||||
	SpiloFSGroup                           *int64                `json:"spilo_fsgroup,omitempty"`
 | 
						SpiloFSGroup                           *int64                       `json:"spilo_fsgroup,omitempty"`
 | 
				
			||||||
	WatchedNamespace                       string                `json:"watched_namespace,omitempty"`
 | 
						WatchedNamespace                       string                       `json:"watched_namespace,omitempty"`
 | 
				
			||||||
	PDBNameFormat                          config.StringTemplate `json:"pdb_name_format,omitempty"`
 | 
						PDBNameFormat                          config.StringTemplate        `json:"pdb_name_format,omitempty"`
 | 
				
			||||||
	EnablePodDisruptionBudget              *bool                 `json:"enable_pod_disruption_budget,omitempty"`
 | 
						EnablePodDisruptionBudget              *bool                        `json:"enable_pod_disruption_budget,omitempty"`
 | 
				
			||||||
	StorageResizeMode                      string                `json:"storage_resize_mode,omitempty"`
 | 
						StorageResizeMode                      string                       `json:"storage_resize_mode,omitempty"`
 | 
				
			||||||
	EnableInitContainers                   *bool                 `json:"enable_init_containers,omitempty"`
 | 
						EnableInitContainers                   *bool                        `json:"enable_init_containers,omitempty"`
 | 
				
			||||||
	EnableSidecars                         *bool                 `json:"enable_sidecars,omitempty"`
 | 
						EnableSidecars                         *bool                        `json:"enable_sidecars,omitempty"`
 | 
				
			||||||
	SecretNameTemplate                     config.StringTemplate `json:"secret_name_template,omitempty"`
 | 
						SecretNameTemplate                     config.StringTemplate        `json:"secret_name_template,omitempty"`
 | 
				
			||||||
	ClusterDomain                          string                `json:"cluster_domain,omitempty"`
 | 
						ClusterDomain                          string                       `json:"cluster_domain,omitempty"`
 | 
				
			||||||
	OAuthTokenSecretName                   spec.NamespacedName   `json:"oauth_token_secret_name,omitempty"`
 | 
						OAuthTokenSecretName                   spec.NamespacedName          `json:"oauth_token_secret_name,omitempty"`
 | 
				
			||||||
	InfrastructureRolesSecretName          spec.NamespacedName   `json:"infrastructure_roles_secret_name,omitempty"`
 | 
						InfrastructureRolesSecretName          spec.NamespacedName          `json:"infrastructure_roles_secret_name,omitempty"`
 | 
				
			||||||
	PodRoleLabel                           string                `json:"pod_role_label,omitempty"`
 | 
						InfrastructureRolesDefs                []*config.InfrastructureRole `json:"infrastructure_roles_secrets,omitempty"`
 | 
				
			||||||
	ClusterLabels                          map[string]string     `json:"cluster_labels,omitempty"`
 | 
						PodRoleLabel                           string                       `json:"pod_role_label,omitempty"`
 | 
				
			||||||
	InheritedLabels                        []string              `json:"inherited_labels,omitempty"`
 | 
						ClusterLabels                          map[string]string            `json:"cluster_labels,omitempty"`
 | 
				
			||||||
	DownscalerAnnotations                  []string              `json:"downscaler_annotations,omitempty"`
 | 
						InheritedLabels                        []string                     `json:"inherited_labels,omitempty"`
 | 
				
			||||||
	ClusterNameLabel                       string                `json:"cluster_name_label,omitempty"`
 | 
						DownscalerAnnotations                  []string                     `json:"downscaler_annotations,omitempty"`
 | 
				
			||||||
	NodeReadinessLabel                     map[string]string     `json:"node_readiness_label,omitempty"`
 | 
						ClusterNameLabel                       string                       `json:"cluster_name_label,omitempty"`
 | 
				
			||||||
	CustomPodAnnotations                   map[string]string     `json:"custom_pod_annotations,omitempty"`
 | 
						NodeReadinessLabel                     map[string]string            `json:"node_readiness_label,omitempty"`
 | 
				
			||||||
 | 
						CustomPodAnnotations                   map[string]string            `json:"custom_pod_annotations,omitempty"`
 | 
				
			||||||
	// TODO: use a proper toleration structure?
 | 
						// TODO: use a proper toleration structure?
 | 
				
			||||||
	PodToleration              map[string]string   `json:"toleration,omitempty"`
 | 
						PodToleration              map[string]string   `json:"toleration,omitempty"`
 | 
				
			||||||
	PodEnvironmentConfigMap    spec.NamespacedName `json:"pod_environment_configmap,omitempty"`
 | 
						PodEnvironmentConfigMap    spec.NamespacedName `json:"pod_environment_configmap,omitempty"`
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -300,7 +300,8 @@ func (c *Controller) initController() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	c.logger.Infof("config: %s", c.opConfig.MustMarshal())
 | 
						c.logger.Infof("config: %s", c.opConfig.MustMarshal())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if infraRoles, err := c.getInfrastructureRoles(&c.opConfig.InfrastructureRolesSecretName); err != nil {
 | 
						roleDefs := c.getInfrastructureRoleDefinitions()
 | 
				
			||||||
 | 
						if infraRoles, err := c.getInfrastructureRoles(roleDefs); err != nil {
 | 
				
			||||||
		c.logger.Warningf("could not get infrastructure roles: %v", err)
 | 
							c.logger.Warningf("could not get infrastructure roles: %v", err)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		c.config.InfrastructureRoles = infraRoles
 | 
							c.config.InfrastructureRoles = infraRoles
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,7 +70,22 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
 | 
				
			||||||
	result.EnableSidecars = util.CoalesceBool(fromCRD.Kubernetes.EnableSidecars, util.True())
 | 
						result.EnableSidecars = util.CoalesceBool(fromCRD.Kubernetes.EnableSidecars, util.True())
 | 
				
			||||||
	result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate
 | 
						result.SecretNameTemplate = fromCRD.Kubernetes.SecretNameTemplate
 | 
				
			||||||
	result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName
 | 
						result.OAuthTokenSecretName = fromCRD.Kubernetes.OAuthTokenSecretName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName
 | 
						result.InfrastructureRolesSecretName = fromCRD.Kubernetes.InfrastructureRolesSecretName
 | 
				
			||||||
 | 
						if fromCRD.Kubernetes.InfrastructureRolesDefs != nil {
 | 
				
			||||||
 | 
							result.InfrastructureRoles = []*config.InfrastructureRole{}
 | 
				
			||||||
 | 
							for _, secret := range fromCRD.Kubernetes.InfrastructureRolesDefs {
 | 
				
			||||||
 | 
								result.InfrastructureRoles = append(
 | 
				
			||||||
 | 
									result.InfrastructureRoles,
 | 
				
			||||||
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
 | 
										Secret:   secret.Secret,
 | 
				
			||||||
 | 
										Name:     secret.Name,
 | 
				
			||||||
 | 
										Role:     secret.Role,
 | 
				
			||||||
 | 
										Password: secret.Password,
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result.PodRoleLabel = util.Coalesce(fromCRD.Kubernetes.PodRoleLabel, "spilo-role")
 | 
						result.PodRoleLabel = util.Coalesce(fromCRD.Kubernetes.PodRoleLabel, "spilo-role")
 | 
				
			||||||
	result.ClusterLabels = util.CoalesceStrMap(fromCRD.Kubernetes.ClusterLabels, map[string]string{"application": "spilo"})
 | 
						result.ClusterLabels = util.CoalesceStrMap(fromCRD.Kubernetes.ClusterLabels, map[string]string{"application": "spilo"})
 | 
				
			||||||
	result.InheritedLabels = fromCRD.Kubernetes.InheritedLabels
 | 
						result.InheritedLabels = fromCRD.Kubernetes.InheritedLabels
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
						apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 | 
				
			||||||
| 
						 | 
					@ -109,8 +110,158 @@ func readDecodedRole(s string) (*spec.PgUser, error) {
 | 
				
			||||||
	return &result, nil
 | 
						return &result, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Controller) getInfrastructureRoles(rolesSecret *spec.NamespacedName) (map[string]spec.PgUser, error) {
 | 
					var emptyNamespacedName = (spec.NamespacedName{})
 | 
				
			||||||
	if *rolesSecret == (spec.NamespacedName{}) {
 | 
					
 | 
				
			||||||
 | 
					// Return information about what secrets we need to use to create
 | 
				
			||||||
 | 
					// infrastructure roles and in which format are they. This is done in
 | 
				
			||||||
 | 
					// compatible way, so that the previous logic is not changed, and handles both
 | 
				
			||||||
 | 
					// configuration in ConfigMap & CRD.
 | 
				
			||||||
 | 
					func (c *Controller) getInfrastructureRoleDefinitions() []*config.InfrastructureRole {
 | 
				
			||||||
 | 
						var roleDef config.InfrastructureRole
 | 
				
			||||||
 | 
						rolesDefs := c.opConfig.InfrastructureRoles
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.opConfig.InfrastructureRolesSecretName == emptyNamespacedName {
 | 
				
			||||||
 | 
							// All the other possibilities require secret name to be present, so if
 | 
				
			||||||
 | 
							// it is not, then nothing else to be done here.
 | 
				
			||||||
 | 
							return rolesDefs
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// check if we can extract something from the configmap config option
 | 
				
			||||||
 | 
						if c.opConfig.InfrastructureRolesDefs != "" {
 | 
				
			||||||
 | 
							// The configmap option could contain either a role description (in the
 | 
				
			||||||
 | 
							// form key1: value1, key2: value2), which has to be used together with
 | 
				
			||||||
 | 
							// an old secret name.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							propertySep := ","
 | 
				
			||||||
 | 
							valueSep := ":"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The field contains the format in which secret is written, let's
 | 
				
			||||||
 | 
							// convert it to a proper definition
 | 
				
			||||||
 | 
							properties := strings.Split(c.opConfig.InfrastructureRolesDefs, propertySep)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							roleDef = config.InfrastructureRole{
 | 
				
			||||||
 | 
								Secret:   c.opConfig.InfrastructureRolesSecretName,
 | 
				
			||||||
 | 
								Template: false,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, property := range properties {
 | 
				
			||||||
 | 
								values := strings.Split(property, valueSep)
 | 
				
			||||||
 | 
								if len(values) < 2 {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								name := strings.TrimSpace(values[0])
 | 
				
			||||||
 | 
								value := strings.TrimSpace(values[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch name {
 | 
				
			||||||
 | 
								case "name":
 | 
				
			||||||
 | 
									roleDef.Name = value
 | 
				
			||||||
 | 
								case "password":
 | 
				
			||||||
 | 
									roleDef.Password = value
 | 
				
			||||||
 | 
								case "role":
 | 
				
			||||||
 | 
									roleDef.Role = value
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									c.logger.Warningf("Role description is not known: %s",
 | 
				
			||||||
 | 
										c.opConfig.InfrastructureRolesSecretName)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// At this point we deal with the old format, let's replicate it
 | 
				
			||||||
 | 
							// via existing definition structure and remember that it's just a
 | 
				
			||||||
 | 
							// template, the real values are in user1,password1,inrole1 etc.
 | 
				
			||||||
 | 
							roleDef = config.InfrastructureRole{
 | 
				
			||||||
 | 
								Secret:   c.opConfig.InfrastructureRolesSecretName,
 | 
				
			||||||
 | 
								Name:     "user",
 | 
				
			||||||
 | 
								Password: "password",
 | 
				
			||||||
 | 
								Role:     "inrole",
 | 
				
			||||||
 | 
								Template: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if roleDef.Name != "" &&
 | 
				
			||||||
 | 
							roleDef.Password != "" &&
 | 
				
			||||||
 | 
							roleDef.Role != "" {
 | 
				
			||||||
 | 
							rolesDefs = append(rolesDefs, &roleDef)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return rolesDefs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Controller) getInfrastructureRoles(
 | 
				
			||||||
 | 
						rolesSecrets []*config.InfrastructureRole) (
 | 
				
			||||||
 | 
						map[string]spec.PgUser, []error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var errors []error
 | 
				
			||||||
 | 
						var noRolesProvided = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						roles := []spec.PgUser{}
 | 
				
			||||||
 | 
						uniqRoles := map[string]spec.PgUser{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// To be compatible with the legacy implementation we need to return nil if
 | 
				
			||||||
 | 
						// the provided secret name is empty. The equivalent situation in the
 | 
				
			||||||
 | 
						// current implementation is an empty rolesSecrets slice or all its items
 | 
				
			||||||
 | 
						// are empty.
 | 
				
			||||||
 | 
						for _, role := range rolesSecrets {
 | 
				
			||||||
 | 
							if role.Secret != emptyNamespacedName {
 | 
				
			||||||
 | 
								noRolesProvided = false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if noRolesProvided {
 | 
				
			||||||
 | 
							return nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, secret := range rolesSecrets {
 | 
				
			||||||
 | 
							infraRoles, err := c.getInfrastructureRole(secret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil || infraRoles == nil {
 | 
				
			||||||
 | 
								c.logger.Debugf("Cannot get infrastructure role: %+v", *secret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									errors = append(errors, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for _, r := range infraRoles {
 | 
				
			||||||
 | 
								roles = append(roles, r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, r := range roles {
 | 
				
			||||||
 | 
							if _, exists := uniqRoles[r.Name]; exists {
 | 
				
			||||||
 | 
								msg := "Conflicting infrastructure roles: roles[%s] = (%q, %q)"
 | 
				
			||||||
 | 
								c.logger.Debugf(msg, r.Name, uniqRoles[r.Name], r)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							uniqRoles[r.Name] = r
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return uniqRoles, errors
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Generate list of users representing one infrastructure role based on its
 | 
				
			||||||
 | 
					// description in various K8S objects. An infrastructure role could be
 | 
				
			||||||
 | 
					// described by a secret and optionally a config map. The former should contain
 | 
				
			||||||
 | 
					// the secret information, i.e. username, password, role. The latter could
 | 
				
			||||||
 | 
					// contain an extensive description of the role and even override an
 | 
				
			||||||
 | 
					// information obtained from the secret (except a password).
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This function returns a list of users to be compatible with the previous
 | 
				
			||||||
 | 
					// behaviour, since we don't know how many users are actually encoded in the
 | 
				
			||||||
 | 
					// secret if it's a "template" role. If the provided role is not a template
 | 
				
			||||||
 | 
					// one, the result would be a list with just one user in it.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// FIXME: This dependency on two different objects is rather unnecessary
 | 
				
			||||||
 | 
					// complicated, so let's get rid of it via deprecation process.
 | 
				
			||||||
 | 
					func (c *Controller) getInfrastructureRole(
 | 
				
			||||||
 | 
						infraRole *config.InfrastructureRole) (
 | 
				
			||||||
 | 
						[]spec.PgUser, error) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rolesSecret := infraRole.Secret
 | 
				
			||||||
 | 
						roles := []spec.PgUser{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if rolesSecret == (spec.NamespacedName{}) {
 | 
				
			||||||
		// we don't have infrastructure roles defined, bail out
 | 
							// we don't have infrastructure roles defined, bail out
 | 
				
			||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -119,52 +270,84 @@ func (c *Controller) getInfrastructureRoles(rolesSecret *spec.NamespacedName) (m
 | 
				
			||||||
		Secrets(rolesSecret.Namespace).
 | 
							Secrets(rolesSecret.Namespace).
 | 
				
			||||||
		Get(context.TODO(), rolesSecret.Name, metav1.GetOptions{})
 | 
							Get(context.TODO(), rolesSecret.Name, metav1.GetOptions{})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Debugf("infrastructure roles secret name: %q", *rolesSecret)
 | 
							msg := "could not get infrastructure roles secret %s/%s: %v"
 | 
				
			||||||
		return nil, fmt.Errorf("could not get infrastructure roles secret: %v", err)
 | 
							return nil, fmt.Errorf(msg, rolesSecret.Namespace, rolesSecret.Name, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	secretData := infraRolesSecret.Data
 | 
						secretData := infraRolesSecret.Data
 | 
				
			||||||
	result := make(map[string]spec.PgUser)
 | 
					
 | 
				
			||||||
Users:
 | 
						if infraRole.Template {
 | 
				
			||||||
	// in worst case we would have one line per user
 | 
						Users:
 | 
				
			||||||
	for i := 1; i <= len(secretData); i++ {
 | 
							for i := 1; i <= len(secretData); i++ {
 | 
				
			||||||
		properties := []string{"user", "password", "inrole"}
 | 
								properties := []string{
 | 
				
			||||||
		t := spec.PgUser{Origin: spec.RoleOriginInfrastructure}
 | 
									infraRole.Name,
 | 
				
			||||||
		for _, p := range properties {
 | 
									infraRole.Password,
 | 
				
			||||||
			key := fmt.Sprintf("%s%d", p, i)
 | 
									infraRole.Role,
 | 
				
			||||||
			if val, present := secretData[key]; !present {
 | 
					 | 
				
			||||||
				if p == "user" {
 | 
					 | 
				
			||||||
					// exit when the user name with the next sequence id is absent
 | 
					 | 
				
			||||||
					break Users
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				s := string(val)
 | 
					 | 
				
			||||||
				switch p {
 | 
					 | 
				
			||||||
				case "user":
 | 
					 | 
				
			||||||
					t.Name = s
 | 
					 | 
				
			||||||
				case "password":
 | 
					 | 
				
			||||||
					t.Password = s
 | 
					 | 
				
			||||||
				case "inrole":
 | 
					 | 
				
			||||||
					t.MemberOf = append(t.MemberOf, s)
 | 
					 | 
				
			||||||
				default:
 | 
					 | 
				
			||||||
					c.logger.Warningf("unknown key %q", p)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			delete(secretData, key)
 | 
								t := spec.PgUser{Origin: spec.RoleOriginInfrastructure}
 | 
				
			||||||
 | 
								for _, p := range properties {
 | 
				
			||||||
 | 
									key := fmt.Sprintf("%s%d", p, i)
 | 
				
			||||||
 | 
									if val, present := secretData[key]; !present {
 | 
				
			||||||
 | 
										if p == "user" {
 | 
				
			||||||
 | 
											// exit when the user name with the next sequence id is
 | 
				
			||||||
 | 
											// absent
 | 
				
			||||||
 | 
											break Users
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										s := string(val)
 | 
				
			||||||
 | 
										switch p {
 | 
				
			||||||
 | 
										case "user":
 | 
				
			||||||
 | 
											t.Name = s
 | 
				
			||||||
 | 
										case "password":
 | 
				
			||||||
 | 
											t.Password = s
 | 
				
			||||||
 | 
										case "inrole":
 | 
				
			||||||
 | 
											t.MemberOf = append(t.MemberOf, s)
 | 
				
			||||||
 | 
										default:
 | 
				
			||||||
 | 
											c.logger.Warningf("unknown key %q", p)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									// XXX: This is a part of the original implementation, which is
 | 
				
			||||||
 | 
									// rather obscure. Why do we delete this key? Wouldn't it be
 | 
				
			||||||
 | 
									// used later in comparison for configmap?
 | 
				
			||||||
 | 
									delete(secretData, key)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								roles = append(roles, t)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							roleDescr := &spec.PgUser{Origin: spec.RoleOriginInfrastructure}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if details, exists := secretData[infraRole.Details]; exists {
 | 
				
			||||||
 | 
								if err := yaml.Unmarshal(details, &roleDescr); err != nil {
 | 
				
			||||||
 | 
									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]))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if t.Name != "" {
 | 
							if roleDescr.Name == "" {
 | 
				
			||||||
			if t.Password == "" {
 | 
								msg := "infrastructure role %q has no name defined and is ignored"
 | 
				
			||||||
				c.logger.Warningf("infrastructure role %q has no password defined and is ignored", t.Name)
 | 
								c.logger.Warningf(msg, roleDescr.Name)
 | 
				
			||||||
				continue
 | 
								return nil, nil
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			result[t.Name] = t
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if roleDescr.Password == "" {
 | 
				
			||||||
 | 
								msg := "infrastructure role %q has no password defined and is ignored"
 | 
				
			||||||
 | 
								c.logger.Warningf(msg, roleDescr.Name)
 | 
				
			||||||
 | 
								return nil, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							roles = append(roles, *roleDescr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// perhaps we have some map entries with usernames, passwords, let's check if we have those users in the configmap
 | 
						// Now plot twist. We need to check if there is a configmap with the same
 | 
				
			||||||
	if infraRolesMap, err := c.KubeClient.ConfigMaps(rolesSecret.Namespace).Get(
 | 
						// name and extract a role description if it exists.
 | 
				
			||||||
		context.TODO(), rolesSecret.Name, metav1.GetOptions{}); err == nil {
 | 
						infraRolesMap, err := c.KubeClient.
 | 
				
			||||||
 | 
							ConfigMaps(rolesSecret.Namespace).
 | 
				
			||||||
 | 
							Get(context.TODO(), rolesSecret.Name, metav1.GetOptions{})
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
		// we have a configmap with username - json description, let's read and decode it
 | 
							// we have a configmap with username - json description, let's read and decode it
 | 
				
			||||||
		for role, s := range infraRolesMap.Data {
 | 
							for role, s := range infraRolesMap.Data {
 | 
				
			||||||
			roleDescr, err := readDecodedRole(s)
 | 
								roleDescr, err := readDecodedRole(s)
 | 
				
			||||||
| 
						 | 
					@ -182,20 +365,12 @@ Users:
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			roleDescr.Name = role
 | 
								roleDescr.Name = role
 | 
				
			||||||
			roleDescr.Origin = spec.RoleOriginInfrastructure
 | 
								roleDescr.Origin = spec.RoleOriginInfrastructure
 | 
				
			||||||
			result[role] = *roleDescr
 | 
								roles = append(roles, *roleDescr)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if len(secretData) > 0 {
 | 
						// TODO: check for role collisions
 | 
				
			||||||
		c.logger.Warningf("%d unprocessed entries in the infrastructure roles secret,"+
 | 
						return roles, nil
 | 
				
			||||||
			" checking configmap %v", len(secretData), rolesSecret.Name)
 | 
					 | 
				
			||||||
		c.logger.Info(`infrastructure role entries should be in the {key}{id} format,` +
 | 
					 | 
				
			||||||
			` where {key} can be either of "user", "password", "inrole" and the {id}` +
 | 
					 | 
				
			||||||
			` a monotonically increasing integer starting with 1`)
 | 
					 | 
				
			||||||
		c.logger.Debugf("unprocessed entries: %#v", secretData)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return result, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName {
 | 
					func (c *Controller) podClusterName(pod *v1.Pod) spec.NamespacedName {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,20 +8,25 @@ import (
 | 
				
			||||||
	b64 "encoding/base64"
 | 
						b64 "encoding/base64"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/spec"
 | 
						"github.com/zalando/postgres-operator/pkg/spec"
 | 
				
			||||||
 | 
						"github.com/zalando/postgres-operator/pkg/util/config"
 | 
				
			||||||
	"github.com/zalando/postgres-operator/pkg/util/k8sutil"
 | 
						"github.com/zalando/postgres-operator/pkg/util/k8sutil"
 | 
				
			||||||
	v1 "k8s.io/api/core/v1"
 | 
						v1 "k8s.io/api/core/v1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	testInfrastructureRolesSecretName = "infrastructureroles-test"
 | 
						testInfrastructureRolesOldSecretName = "infrastructureroles-old-test"
 | 
				
			||||||
 | 
						testInfrastructureRolesNewSecretName = "infrastructureroles-new-test"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newUtilTestController() *Controller {
 | 
					func newUtilTestController() *Controller {
 | 
				
			||||||
	controller := NewController(&spec.ControllerConfig{}, "util-test")
 | 
						controller := NewController(&spec.ControllerConfig{}, "util-test")
 | 
				
			||||||
	controller.opConfig.ClusterNameLabel = "cluster-name"
 | 
						controller.opConfig.ClusterNameLabel = "cluster-name"
 | 
				
			||||||
	controller.opConfig.InfrastructureRolesSecretName =
 | 
						controller.opConfig.InfrastructureRolesSecretName =
 | 
				
			||||||
		spec.NamespacedName{Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesSecretName}
 | 
							spec.NamespacedName{
 | 
				
			||||||
 | 
								Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
								Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	controller.opConfig.Workers = 4
 | 
						controller.opConfig.Workers = 4
 | 
				
			||||||
	controller.KubeClient = k8sutil.NewMockKubernetesClient()
 | 
						controller.KubeClient = k8sutil.NewMockKubernetesClient()
 | 
				
			||||||
	return controller
 | 
						return controller
 | 
				
			||||||
| 
						 | 
					@ -80,24 +85,32 @@ func TestClusterWorkerID(t *testing.T) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestGetInfrastructureRoles(t *testing.T) {
 | 
					// Test functionality of getting infrastructure roles from their description in
 | 
				
			||||||
 | 
					// corresponding secrets. Here we test only common stuff (e.g. when a secret do
 | 
				
			||||||
 | 
					// not exist, or empty) and the old format.
 | 
				
			||||||
 | 
					func TestOldInfrastructureRoleFormat(t *testing.T) {
 | 
				
			||||||
	var testTable = []struct {
 | 
						var testTable = []struct {
 | 
				
			||||||
		secretName    spec.NamespacedName
 | 
							secretName     spec.NamespacedName
 | 
				
			||||||
		expectedRoles map[string]spec.PgUser
 | 
							expectedRoles  map[string]spec.PgUser
 | 
				
			||||||
		expectedError error
 | 
							expectedErrors []error
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 | 
								// empty secret name
 | 
				
			||||||
			spec.NamespacedName{},
 | 
								spec.NamespacedName{},
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
			nil,
 | 
								nil,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 | 
								// secret does not exist
 | 
				
			||||||
			spec.NamespacedName{Namespace: v1.NamespaceDefault, Name: "null"},
 | 
								spec.NamespacedName{Namespace: v1.NamespaceDefault, Name: "null"},
 | 
				
			||||||
			nil,
 | 
								map[string]spec.PgUser{},
 | 
				
			||||||
			fmt.Errorf(`could not get infrastructure roles secret: NotFound`),
 | 
								[]error{fmt.Errorf(`could not get infrastructure roles secret default/null: NotFound`)},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			spec.NamespacedName{Namespace: v1.NamespaceDefault, Name: testInfrastructureRolesSecretName},
 | 
								spec.NamespacedName{
 | 
				
			||||||
 | 
									Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
									Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
			map[string]spec.PgUser{
 | 
								map[string]spec.PgUser{
 | 
				
			||||||
				"testrole": {
 | 
									"testrole": {
 | 
				
			||||||
					Name:     "testrole",
 | 
										Name:     "testrole",
 | 
				
			||||||
| 
						 | 
					@ -116,15 +129,268 @@ func TestGetInfrastructureRoles(t *testing.T) {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, test := range testTable {
 | 
						for _, test := range testTable {
 | 
				
			||||||
		roles, err := utilTestController.getInfrastructureRoles(&test.secretName)
 | 
							roles, errors := utilTestController.getInfrastructureRoles(
 | 
				
			||||||
		if err != test.expectedError {
 | 
								[]*config.InfrastructureRole{
 | 
				
			||||||
			if err != nil && test.expectedError != nil && err.Error() == test.expectedError.Error() {
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
				continue
 | 
										Secret:   test.secretName,
 | 
				
			||||||
			}
 | 
										Name:     "user",
 | 
				
			||||||
			t.Errorf("expected error '%v' does not match the actual error '%v'", test.expectedError, err)
 | 
										Password: "password",
 | 
				
			||||||
 | 
										Role:     "inrole",
 | 
				
			||||||
 | 
										Template: true,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(errors) != len(test.expectedErrors) {
 | 
				
			||||||
 | 
								t.Errorf("expected error '%v' does not match the actual error '%v'",
 | 
				
			||||||
 | 
									test.expectedErrors, errors)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for idx := range errors {
 | 
				
			||||||
 | 
								err := errors[idx]
 | 
				
			||||||
 | 
								expectedErr := test.expectedErrors[idx]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != expectedErr {
 | 
				
			||||||
 | 
									if err != nil && expectedErr != nil && err.Error() == expectedErr.Error() {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									t.Errorf("expected error '%v' does not match the actual error '%v'",
 | 
				
			||||||
 | 
										expectedErr, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if !reflect.DeepEqual(roles, test.expectedRoles) {
 | 
							if !reflect.DeepEqual(roles, test.expectedRoles) {
 | 
				
			||||||
			t.Errorf("expected roles output %v does not match the actual %v", test.expectedRoles, roles)
 | 
								t.Errorf("expected roles output %#v does not match the actual %#v",
 | 
				
			||||||
 | 
									test.expectedRoles, roles)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test functionality of getting infrastructure roles from their description in
 | 
				
			||||||
 | 
					// corresponding secrets. Here we test the new format.
 | 
				
			||||||
 | 
					func TestNewInfrastructureRoleFormat(t *testing.T) {
 | 
				
			||||||
 | 
						var testTable = []struct {
 | 
				
			||||||
 | 
							secrets        []spec.NamespacedName
 | 
				
			||||||
 | 
							expectedRoles  map[string]spec.PgUser
 | 
				
			||||||
 | 
							expectedErrors []error
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// one secret with one configmap
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]spec.NamespacedName{
 | 
				
			||||||
 | 
									spec.NamespacedName{
 | 
				
			||||||
 | 
										Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
										Name:      testInfrastructureRolesNewSecretName,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								map[string]spec.PgUser{
 | 
				
			||||||
 | 
									"new-test-role": {
 | 
				
			||||||
 | 
										Name:     "new-test-role",
 | 
				
			||||||
 | 
										Origin:   spec.RoleOriginInfrastructure,
 | 
				
			||||||
 | 
										Password: "new-test-password",
 | 
				
			||||||
 | 
										MemberOf: []string{"new-test-inrole"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"new-foobar": {
 | 
				
			||||||
 | 
										Name:     "new-foobar",
 | 
				
			||||||
 | 
										Origin:   spec.RoleOriginInfrastructure,
 | 
				
			||||||
 | 
										Password: b64.StdEncoding.EncodeToString([]byte("password")),
 | 
				
			||||||
 | 
										MemberOf: nil,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// multiple standalone secrets
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]spec.NamespacedName{
 | 
				
			||||||
 | 
									spec.NamespacedName{
 | 
				
			||||||
 | 
										Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
										Name:      "infrastructureroles-new-test1",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									spec.NamespacedName{
 | 
				
			||||||
 | 
										Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
										Name:      "infrastructureroles-new-test2",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								map[string]spec.PgUser{
 | 
				
			||||||
 | 
									"new-test-role1": {
 | 
				
			||||||
 | 
										Name:     "new-test-role1",
 | 
				
			||||||
 | 
										Origin:   spec.RoleOriginInfrastructure,
 | 
				
			||||||
 | 
										Password: "new-test-password1",
 | 
				
			||||||
 | 
										MemberOf: []string{"new-test-inrole1"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"new-test-role2": {
 | 
				
			||||||
 | 
										Name:     "new-test-role2",
 | 
				
			||||||
 | 
										Origin:   spec.RoleOriginInfrastructure,
 | 
				
			||||||
 | 
										Password: "new-test-password2",
 | 
				
			||||||
 | 
										MemberOf: []string{"new-test-inrole2"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range testTable {
 | 
				
			||||||
 | 
							definitions := []*config.InfrastructureRole{}
 | 
				
			||||||
 | 
							for _, secret := range test.secrets {
 | 
				
			||||||
 | 
								definitions = append(definitions, &config.InfrastructureRole{
 | 
				
			||||||
 | 
									Secret:   secret,
 | 
				
			||||||
 | 
									Name:     "user",
 | 
				
			||||||
 | 
									Password: "password",
 | 
				
			||||||
 | 
									Role:     "inrole",
 | 
				
			||||||
 | 
									Template: false,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							roles, errors := utilTestController.getInfrastructureRoles(definitions)
 | 
				
			||||||
 | 
							if len(errors) != len(test.expectedErrors) {
 | 
				
			||||||
 | 
								t.Errorf("expected error does not match the actual error:\n%+v\n%+v",
 | 
				
			||||||
 | 
									test.expectedErrors, errors)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Stop and do not do any further checks
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for idx := range errors {
 | 
				
			||||||
 | 
								err := errors[idx]
 | 
				
			||||||
 | 
								expectedErr := test.expectedErrors[idx]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if err != expectedErr {
 | 
				
			||||||
 | 
									if err != nil && expectedErr != nil && err.Error() == expectedErr.Error() {
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									t.Errorf("expected error '%v' does not match the actual error '%v'",
 | 
				
			||||||
 | 
										expectedErr, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !reflect.DeepEqual(roles, test.expectedRoles) {
 | 
				
			||||||
 | 
								t.Errorf("expected roles output/the actual:\n%#v\n%#v",
 | 
				
			||||||
 | 
									test.expectedRoles, roles)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tests for getting correct infrastructure roles definitions from present
 | 
				
			||||||
 | 
					// configuration. E.g. in which secrets for which roles too look. The biggest
 | 
				
			||||||
 | 
					// point here is compatibility of old and new formats of defining
 | 
				
			||||||
 | 
					// infrastructure roles.
 | 
				
			||||||
 | 
					func TestInfrastructureRoleDefinitions(t *testing.T) {
 | 
				
			||||||
 | 
						var testTable = []struct {
 | 
				
			||||||
 | 
							rolesDefs      []*config.InfrastructureRole
 | 
				
			||||||
 | 
							roleSecretName spec.NamespacedName
 | 
				
			||||||
 | 
							roleSecrets    string
 | 
				
			||||||
 | 
							expectedDefs   []*config.InfrastructureRole
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// only new format
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{
 | 
				
			||||||
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
 | 
										Secret: spec.NamespacedName{
 | 
				
			||||||
 | 
											Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
											Name:      testInfrastructureRolesNewSecretName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Name:     "user",
 | 
				
			||||||
 | 
										Password: "password",
 | 
				
			||||||
 | 
										Role:     "inrole",
 | 
				
			||||||
 | 
										Template: false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								spec.NamespacedName{},
 | 
				
			||||||
 | 
								"",
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{
 | 
				
			||||||
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
 | 
										Secret: spec.NamespacedName{
 | 
				
			||||||
 | 
											Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
											Name:      testInfrastructureRolesNewSecretName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Name:     "user",
 | 
				
			||||||
 | 
										Password: "password",
 | 
				
			||||||
 | 
										Role:     "inrole",
 | 
				
			||||||
 | 
										Template: false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// only old format
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
								spec.NamespacedName{
 | 
				
			||||||
 | 
									Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
									Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"",
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{
 | 
				
			||||||
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
 | 
										Secret: spec.NamespacedName{
 | 
				
			||||||
 | 
											Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
											Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Name:     "user",
 | 
				
			||||||
 | 
										Password: "password",
 | 
				
			||||||
 | 
										Role:     "inrole",
 | 
				
			||||||
 | 
										Template: true,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// only configmap format
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
								spec.NamespacedName{
 | 
				
			||||||
 | 
									Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
									Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"name: test-user, password: test-password, role: test-role",
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{
 | 
				
			||||||
 | 
									&config.InfrastructureRole{
 | 
				
			||||||
 | 
										Secret: spec.NamespacedName{
 | 
				
			||||||
 | 
											Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
											Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Name:     "test-user",
 | 
				
			||||||
 | 
										Password: "test-password",
 | 
				
			||||||
 | 
										Role:     "test-role",
 | 
				
			||||||
 | 
										Template: false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// incorrect configmap format
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
								spec.NamespacedName{
 | 
				
			||||||
 | 
									Namespace: v1.NamespaceDefault,
 | 
				
			||||||
 | 
									Name:      testInfrastructureRolesOldSecretName,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"wrong-format",
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// configmap without a secret
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
								spec.NamespacedName{},
 | 
				
			||||||
 | 
								"name: test-user, password: test-password, role: test-role",
 | 
				
			||||||
 | 
								[]*config.InfrastructureRole{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range testTable {
 | 
				
			||||||
 | 
							t.Logf("Test: %+v", test)
 | 
				
			||||||
 | 
							utilTestController.opConfig.InfrastructureRoles = test.rolesDefs
 | 
				
			||||||
 | 
							utilTestController.opConfig.InfrastructureRolesSecretName = test.roleSecretName
 | 
				
			||||||
 | 
							utilTestController.opConfig.InfrastructureRolesDefs = test.roleSecrets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defs := utilTestController.getInfrastructureRoleDefinitions()
 | 
				
			||||||
 | 
							if len(defs) != len(test.expectedDefs) {
 | 
				
			||||||
 | 
								t.Errorf("expected definitions does not match the actual:\n%#v\n%#v",
 | 
				
			||||||
 | 
									test.expectedDefs, defs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Stop and do not do any further checks
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for idx := range defs {
 | 
				
			||||||
 | 
								def := defs[idx]
 | 
				
			||||||
 | 
								expectedDef := test.expectedDefs[idx]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(def, expectedDef) {
 | 
				
			||||||
 | 
									t.Errorf("expected definition/the actual:\n%#v\n%#v",
 | 
				
			||||||
 | 
										expectedDef, def)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,16 +51,42 @@ type Resources struct {
 | 
				
			||||||
	ShmVolume               *bool               `name:"enable_shm_volume" default:"true"`
 | 
						ShmVolume               *bool               `name:"enable_shm_volume" default:"true"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type InfrastructureRole struct {
 | 
				
			||||||
 | 
						// Name of a secret which describes the role, and optionally name of a
 | 
				
			||||||
 | 
						// configmap with an extra information
 | 
				
			||||||
 | 
						Secret spec.NamespacedName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Name     string
 | 
				
			||||||
 | 
						Password string
 | 
				
			||||||
 | 
						Role     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: ...
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// If it does, Name/Password/Role are interpreted not as unique field
 | 
				
			||||||
 | 
						// names, but as a template.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Template bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Auth describes authentication specific configuration parameters
 | 
					// Auth describes authentication specific configuration parameters
 | 
				
			||||||
type Auth struct {
 | 
					type Auth struct {
 | 
				
			||||||
	SecretNameTemplate            StringTemplate      `name:"secret_name_template" default:"{username}.{cluster}.credentials.{tprkind}.{tprgroup}"`
 | 
						SecretNameTemplate            StringTemplate        `name:"secret_name_template" default:"{username}.{cluster}.credentials.{tprkind}.{tprgroup}"`
 | 
				
			||||||
	PamRoleName                   string              `name:"pam_role_name" default:"zalandos"`
 | 
						PamRoleName                   string                `name:"pam_role_name" default:"zalandos"`
 | 
				
			||||||
	PamConfiguration              string              `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
 | 
						PamConfiguration              string                `name:"pam_configuration" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
 | 
				
			||||||
	TeamsAPIUrl                   string              `name:"teams_api_url" default:"https://teams.example.com/api/"`
 | 
						TeamsAPIUrl                   string                `name:"teams_api_url" default:"https://teams.example.com/api/"`
 | 
				
			||||||
	OAuthTokenSecretName          spec.NamespacedName `name:"oauth_token_secret_name" default:"postgresql-operator"`
 | 
						OAuthTokenSecretName          spec.NamespacedName   `name:"oauth_token_secret_name" default:"postgresql-operator"`
 | 
				
			||||||
	InfrastructureRolesSecretName spec.NamespacedName `name:"infrastructure_roles_secret_name"`
 | 
						InfrastructureRolesSecretName spec.NamespacedName   `name:"infrastructure_roles_secret_name"`
 | 
				
			||||||
	SuperUsername                 string              `name:"super_username" default:"postgres"`
 | 
						InfrastructureRoles           []*InfrastructureRole `name:"-"`
 | 
				
			||||||
	ReplicationUsername           string              `name:"replication_username" default:"standby"`
 | 
						InfrastructureRolesDefs       string                `name:"infrastructure_roles_secrets"`
 | 
				
			||||||
 | 
						SuperUsername                 string                `name:"super_username" default:"postgres"`
 | 
				
			||||||
 | 
						ReplicationUsername           string                `name:"replication_username" default:"standby"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Scalyr holds the configuration for the Scalyr Agent sidecar for log shipping:
 | 
					// Scalyr holds the configuration for the Scalyr Agent sidecar for log shipping:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,31 +271,73 @@ func SameLogicalBackupJob(cur, new *batchv1beta1.CronJob) (match bool, reason st
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *mockSecret) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.Secret, error) {
 | 
					func (c *mockSecret) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.Secret, error) {
 | 
				
			||||||
	if name != "infrastructureroles-test" {
 | 
						oldFormatSecret := &v1.Secret{}
 | 
				
			||||||
		return nil, fmt.Errorf("NotFound")
 | 
						oldFormatSecret.Name = "testcluster"
 | 
				
			||||||
	}
 | 
						oldFormatSecret.Data = map[string][]byte{
 | 
				
			||||||
	secret := &v1.Secret{}
 | 
					 | 
				
			||||||
	secret.Name = "testcluster"
 | 
					 | 
				
			||||||
	secret.Data = map[string][]byte{
 | 
					 | 
				
			||||||
		"user1":     []byte("testrole"),
 | 
							"user1":     []byte("testrole"),
 | 
				
			||||||
		"password1": []byte("testpassword"),
 | 
							"password1": []byte("testpassword"),
 | 
				
			||||||
		"inrole1":   []byte("testinrole"),
 | 
							"inrole1":   []byte("testinrole"),
 | 
				
			||||||
		"foobar":    []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
 | 
							"foobar":    []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return secret, nil
 | 
					
 | 
				
			||||||
 | 
						newFormatSecret := &v1.Secret{}
 | 
				
			||||||
 | 
						newFormatSecret.Name = "test-secret-new-format"
 | 
				
			||||||
 | 
						newFormatSecret.Data = map[string][]byte{
 | 
				
			||||||
 | 
							"user":       []byte("new-test-role"),
 | 
				
			||||||
 | 
							"password":   []byte("new-test-password"),
 | 
				
			||||||
 | 
							"inrole":     []byte("new-test-inrole"),
 | 
				
			||||||
 | 
							"new-foobar": []byte(b64.StdEncoding.EncodeToString([]byte("password"))),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						secrets := map[string]*v1.Secret{
 | 
				
			||||||
 | 
							"infrastructureroles-old-test": oldFormatSecret,
 | 
				
			||||||
 | 
							"infrastructureroles-new-test": newFormatSecret,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for idx := 1; idx <= 2; idx++ {
 | 
				
			||||||
 | 
							newFormatStandaloneSecret := &v1.Secret{}
 | 
				
			||||||
 | 
							newFormatStandaloneSecret.Name = fmt.Sprintf("test-secret-new-format%d", idx)
 | 
				
			||||||
 | 
							newFormatStandaloneSecret.Data = map[string][]byte{
 | 
				
			||||||
 | 
								"user":     []byte(fmt.Sprintf("new-test-role%d", idx)),
 | 
				
			||||||
 | 
								"password": []byte(fmt.Sprintf("new-test-password%d", idx)),
 | 
				
			||||||
 | 
								"inrole":   []byte(fmt.Sprintf("new-test-inrole%d", idx)),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							secrets[fmt.Sprintf("infrastructureroles-new-test%d", idx)] =
 | 
				
			||||||
 | 
								newFormatStandaloneSecret
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if secret, exists := secrets[name]; exists {
 | 
				
			||||||
 | 
							return secret, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("NotFound")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *mockConfigMap) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
 | 
					func (c *mockConfigMap) Get(ctx context.Context, name string, options metav1.GetOptions) (*v1.ConfigMap, error) {
 | 
				
			||||||
	if name != "infrastructureroles-test" {
 | 
						oldFormatConfigmap := &v1.ConfigMap{}
 | 
				
			||||||
		return nil, fmt.Errorf("NotFound")
 | 
						oldFormatConfigmap.Name = "testcluster"
 | 
				
			||||||
	}
 | 
						oldFormatConfigmap.Data = map[string]string{
 | 
				
			||||||
	configmap := &v1.ConfigMap{}
 | 
					 | 
				
			||||||
	configmap.Name = "testcluster"
 | 
					 | 
				
			||||||
	configmap.Data = map[string]string{
 | 
					 | 
				
			||||||
		"foobar": "{}",
 | 
							"foobar": "{}",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return configmap, nil
 | 
					
 | 
				
			||||||
 | 
						newFormatConfigmap := &v1.ConfigMap{}
 | 
				
			||||||
 | 
						newFormatConfigmap.Name = "testcluster"
 | 
				
			||||||
 | 
						newFormatConfigmap.Data = map[string]string{
 | 
				
			||||||
 | 
							"new-foobar": "{}",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						configmaps := map[string]*v1.ConfigMap{
 | 
				
			||||||
 | 
							"infrastructureroles-old-test": oldFormatConfigmap,
 | 
				
			||||||
 | 
							"infrastructureroles-new-test": newFormatConfigmap,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if configmap, exists := configmaps[name]; exists {
 | 
				
			||||||
 | 
							return configmap, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil, fmt.Errorf("NotFound")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Secrets to be mocked
 | 
					// Secrets to be mocked
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue