diff --git a/charts/postgres-operator/crds/postgresqls.yaml b/charts/postgres-operator/crds/postgresqls.yaml index aead7fe69..eb628863d 100644 --- a/charts/postgres-operator/crds/postgresqls.yaml +++ b/charts/postgres-operator/crds/postgresqls.yaml @@ -515,6 +515,8 @@ spec: type: integer useLoadBalancer: # deprecated type: boolean + enableNamespacedSecret: + type: boolean users: type: object additionalProperties: diff --git a/e2e/tests/k8s_api.py b/e2e/tests/k8s_api.py index 3333636ff..d28ea69ad 100644 --- a/e2e/tests/k8s_api.py +++ b/e2e/tests/k8s_api.py @@ -197,6 +197,16 @@ class K8s: pod_phase = pods[0].status.phase time.sleep(self.RETRY_TIMEOUT_SEC) + def wait_for_namespace_creation(self, namespace='default'): + ns_found = False + while ns_found != True: + ns = self.api.core_v1.list_namespace().items + for n in ns: + if n.metadata.name == namespace: + ns_found = True + break + time.sleep(self.RETRY_TIMEOUT_SEC) + def get_logical_backup_job(self, namespace='default'): return self.api.batch_v1_beta1.list_namespaced_cron_job(namespace, label_selector="application=spilo") @@ -362,17 +372,6 @@ class K8sBase: time.sleep(self.RETRY_TIMEOUT_SEC) - def wait_for_namespace_creation(self, namespace='default'): - ns_found = False - while ns_found != True: - ns = self.api.core_v1.list_namespaces() - for n in ns: - if n == "appspace": - ns_found = True - break - - time.sleep(self.RETRY_TIMEOUT_SEC) - def get_service_type(self, svc_labels, namespace='default'): svc_type = '' svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items diff --git a/e2e/tests/test_e2e.py b/e2e/tests/test_e2e.py index bf52ba670..bd1b2b6a1 100644 --- a/e2e/tests/test_e2e.py +++ b/e2e/tests/test_e2e.py @@ -314,7 +314,7 @@ class EndToEndTestCase(unittest.TestCase): 'postgresqls', 'acid-minimal-cluster', { 'spec': { - 'enableConnectionPooler': False + c } }) @@ -592,23 +592,24 @@ class EndToEndTestCase(unittest.TestCase): Test secrets in different namespace ''' app_namespace = "appspace" - k8s = self.k8s + # k8s = self.k8s v1_appnamespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=app_namespace)) - k8s.api.core_v1.create_namespace(v1_appnamespace) - k8s.wait_for_namespace_creation(app_namespace) + self.k8s.api.core_v1.create_namespace(v1_appnamespace) + self.k8s.wait_for_namespace_creation(app_namespace) - k8s.api.custom_objects_api.patch_namespaced_custom_object( + self.k8s.api.custom_objects_api.patch_namespaced_custom_object( 'acid.zalan.do', 'v1', 'default', 'postgresqls', 'acid-minimal-cluster', { 'spec': { + 'enableNamespacedSecret': True, 'users':{ 'appspace.db_user': [], } } }) - self.eventuallyEqual(lambda: k8s.count_secrets_with_label("cluster_name=acid-minimal-cluster,application=spilo", app_namespace), - 1, "Secret not created for user in namespace", app_namespace) + self.eventuallyEqual(lambda: self.k8s.count_secrets_with_label("cluster_name=acid-minimal-cluster,application=spilo", app_namespace), + 1, "Secret not created for user in namespace") @timeout_decorator.timeout(TEST_TIMEOUT_SEC) def test_lazy_spilo_upgrade(self): diff --git a/manifests/complete-postgres-manifest.yaml b/manifests/complete-postgres-manifest.yaml index 6e2acbdd3..a474482e9 100644 --- a/manifests/complete-postgres-manifest.yaml +++ b/manifests/complete-postgres-manifest.yaml @@ -12,7 +12,10 @@ spec: dockerImage: registry.opensource.zalan.do/acid/spilo-13:2.0-p7 teamId: "acid" numberOfInstances: 2 + enableNamespacedSecret: True users: # Application/Robot users + appspace.db_user: [] + appspace.db_user.with.dots: [] zalando: - superuser - createdb diff --git a/pkg/apis/acid.zalan.do/v1/crds.go b/pkg/apis/acid.zalan.do/v1/crds.go index 83e7273e4..ae91a9f38 100644 --- a/pkg/apis/acid.zalan.do/v1/crds.go +++ b/pkg/apis/acid.zalan.do/v1/crds.go @@ -730,6 +730,9 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{ Type: "boolean", Description: "Deprecated", }, + "enableNamespacedSecret": { + Type: "boolean", + }, "users": { Type: "object", AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{ diff --git a/pkg/apis/acid.zalan.do/v1/postgresql_type.go b/pkg/apis/acid.zalan.do/v1/postgresql_type.go index 7346fb0e5..1787f5b4e 100644 --- a/pkg/apis/acid.zalan.do/v1/postgresql_type.go +++ b/pkg/apis/acid.zalan.do/v1/postgresql_type.go @@ -53,27 +53,28 @@ type PostgresSpec struct { // load balancers' source ranges are the same for master and replica services AllowedSourceRanges []string `json:"allowedSourceRanges"` - NumberOfInstances int32 `json:"numberOfInstances"` - Users map[string]UserFlags `json:"users,omitempty"` - MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"` - Clone *CloneDescription `json:"clone,omitempty"` - ClusterName string `json:"-"` - Databases map[string]string `json:"databases,omitempty"` - PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"` - SchedulerName *string `json:"schedulerName,omitempty"` - NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"` - Tolerations []v1.Toleration `json:"tolerations,omitempty"` - Sidecars []Sidecar `json:"sidecars,omitempty"` - InitContainers []v1.Container `json:"initContainers,omitempty"` - PodPriorityClassName string `json:"podPriorityClassName,omitempty"` - ShmVolume *bool `json:"enableShmVolume,omitempty"` - EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"` - LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"` - StandbyCluster *StandbyDescription `json:"standby,omitempty"` - PodAnnotations map[string]string `json:"podAnnotations,omitempty"` - ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` - TLS *TLSDescription `json:"tls,omitempty"` - AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"` + NumberOfInstances int32 `json:"numberOfInstances"` + EnableNamespacedSecret *bool `json:"enableNamespacedSecret,omitempty"` + Users map[string]UserFlags `json:"users,omitempty"` + MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"` + Clone *CloneDescription `json:"clone,omitempty"` + ClusterName string `json:"-"` + Databases map[string]string `json:"databases,omitempty"` + PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"` + SchedulerName *string `json:"schedulerName,omitempty"` + NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"` + Tolerations []v1.Toleration `json:"tolerations,omitempty"` + Sidecars []Sidecar `json:"sidecars,omitempty"` + InitContainers []v1.Container `json:"initContainers,omitempty"` + PodPriorityClassName string `json:"podPriorityClassName,omitempty"` + ShmVolume *bool `json:"enableShmVolume,omitempty"` + EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"` + LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"` + StandbyCluster *StandbyDescription `json:"standby,omitempty"` + PodAnnotations map[string]string `json:"podAnnotations,omitempty"` + ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"` + TLS *TLSDescription `json:"tls,omitempty"` + AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"` // deprecated json tags InitContainersOld []v1.Container `json:"init_containers,omitempty"` diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go index 2051dd3ef..ced184877 100644 --- a/pkg/cluster/cluster.go +++ b/pkg/cluster/cluster.go @@ -41,7 +41,7 @@ import ( var ( alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]*$") databaseNameRegexp = regexp.MustCompile("^[a-zA-Z_][a-zA-Z0-9_]*$") - userRegexp = regexp.MustCompile(`^[a-z0-9]+\.?[-_a-z0-9]+[a-z0-9]$`) + userRegexp = regexp.MustCompile(`^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$`) patroniObjectSuffixes = []string{"config", "failover", "sync"} ) @@ -1111,10 +1111,13 @@ func (c *Cluster) initRobotUsers() error { } namespace := c.Namespace - //more than one dot in the username is reported invalid by regexp - if strings.Contains(username, ".") { - splits := strings.Split(username, ".") - namespace = splits[0] + //if namespaced secrets are allowed + if c.Postgresql.Spec.EnableNamespacedSecret != nil && + *c.Postgresql.Spec.EnableNamespacedSecret { + if strings.Contains(username, ".") { + splits := strings.Split(username, ".") + namespace = splits[0] + } } flags, err := normalizeUserFlags(userFlags) diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go index 8859d2730..cb9356f47 100644 --- a/pkg/cluster/cluster_test.go +++ b/pkg/cluster/cluster_test.go @@ -870,6 +870,7 @@ func TestCrossNamespacedSecrets(t *testing.T) { Volume: acidv1.Volume{ Size: "1Gi", }, + EnableNamespacedSecret: boolToPointer(true), Users: map[string]acidv1.UserFlags{ "appspace.db_user": {}, "db_user": {}, @@ -920,11 +921,16 @@ func TestCrossNamespacedSecrets(t *testing.T) { func TestValidUsernames(t *testing.T) { testName := "test username validity" - invalidUsernames := []string{"_", ".", ".user", "appspace.", "appspace.user.extra", "user_", "_user", "-user", "user-", ",", "-", ",user", "user,", "namespace,user"} - + invalidUsernames := []string{"_", ".", ".user", "appspace.", "user_", "_user", "-user", "user-", ",", "-", ",user", "user,", "namespace,user"} + validUsernames := []string{"user", "appspace.user", "appspace.dot.user", "user_name", "app_space.user_name"} for _, username := range invalidUsernames { if isValidUsername(username) { t.Errorf("%s Invalid username is allowed: %s", testName, username) } } + for _, username := range validUsernames { + if !isValidUsername(username) { + t.Errorf("%s Valid username is not allowed: %s", testName, username) + } + } }