Add toggle to allow namespaced secrets
This commit is contained in:
parent
f0472fd443
commit
a992494fbf
|
|
@ -515,6 +515,8 @@ spec:
|
||||||
type: integer
|
type: integer
|
||||||
useLoadBalancer: # deprecated
|
useLoadBalancer: # deprecated
|
||||||
type: boolean
|
type: boolean
|
||||||
|
enableNamespacedSecret:
|
||||||
|
type: boolean
|
||||||
users:
|
users:
|
||||||
type: object
|
type: object
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,16 @@ class K8s:
|
||||||
pod_phase = pods[0].status.phase
|
pod_phase = pods[0].status.phase
|
||||||
time.sleep(self.RETRY_TIMEOUT_SEC)
|
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'):
|
def get_logical_backup_job(self, namespace='default'):
|
||||||
return self.api.batch_v1_beta1.list_namespaced_cron_job(namespace, label_selector="application=spilo")
|
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)
|
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'):
|
def get_service_type(self, svc_labels, namespace='default'):
|
||||||
svc_type = ''
|
svc_type = ''
|
||||||
svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
|
svcs = self.api.core_v1.list_namespaced_service(namespace, label_selector=svc_labels, limit=1).items
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
'postgresqls', 'acid-minimal-cluster',
|
'postgresqls', 'acid-minimal-cluster',
|
||||||
{
|
{
|
||||||
'spec': {
|
'spec': {
|
||||||
'enableConnectionPooler': False
|
c
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -592,23 +592,24 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
Test secrets in different namespace
|
Test secrets in different namespace
|
||||||
'''
|
'''
|
||||||
app_namespace = "appspace"
|
app_namespace = "appspace"
|
||||||
k8s = self.k8s
|
# k8s = self.k8s
|
||||||
v1_appnamespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=app_namespace))
|
v1_appnamespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=app_namespace))
|
||||||
k8s.api.core_v1.create_namespace(v1_appnamespace)
|
self.k8s.api.core_v1.create_namespace(v1_appnamespace)
|
||||||
k8s.wait_for_namespace_creation(app_namespace)
|
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',
|
'acid.zalan.do', 'v1', 'default',
|
||||||
'postgresqls', 'acid-minimal-cluster',
|
'postgresqls', 'acid-minimal-cluster',
|
||||||
{
|
{
|
||||||
'spec': {
|
'spec': {
|
||||||
|
'enableNamespacedSecret': True,
|
||||||
'users':{
|
'users':{
|
||||||
'appspace.db_user': [],
|
'appspace.db_user': [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self.eventuallyEqual(lambda: k8s.count_secrets_with_label("cluster_name=acid-minimal-cluster,application=spilo", 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", app_namespace)
|
1, "Secret not created for user in namespace")
|
||||||
|
|
||||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
def test_lazy_spilo_upgrade(self):
|
def test_lazy_spilo_upgrade(self):
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ spec:
|
||||||
dockerImage: registry.opensource.zalan.do/acid/spilo-13:2.0-p7
|
dockerImage: registry.opensource.zalan.do/acid/spilo-13:2.0-p7
|
||||||
teamId: "acid"
|
teamId: "acid"
|
||||||
numberOfInstances: 2
|
numberOfInstances: 2
|
||||||
|
enableNamespacedSecret: True
|
||||||
users: # Application/Robot users
|
users: # Application/Robot users
|
||||||
|
appspace.db_user: []
|
||||||
|
appspace.db_user.with.dots: []
|
||||||
zalando:
|
zalando:
|
||||||
- superuser
|
- superuser
|
||||||
- createdb
|
- createdb
|
||||||
|
|
|
||||||
|
|
@ -730,6 +730,9 @@ var PostgresCRDResourceValidation = apiextv1.CustomResourceValidation{
|
||||||
Type: "boolean",
|
Type: "boolean",
|
||||||
Description: "Deprecated",
|
Description: "Deprecated",
|
||||||
},
|
},
|
||||||
|
"enableNamespacedSecret": {
|
||||||
|
Type: "boolean",
|
||||||
|
},
|
||||||
"users": {
|
"users": {
|
||||||
Type: "object",
|
Type: "object",
|
||||||
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{
|
AdditionalProperties: &apiextv1.JSONSchemaPropsOrBool{
|
||||||
|
|
|
||||||
|
|
@ -53,27 +53,28 @@ type PostgresSpec struct {
|
||||||
// load balancers' source ranges are the same for master and replica services
|
// load balancers' source ranges are the same for master and replica services
|
||||||
AllowedSourceRanges []string `json:"allowedSourceRanges"`
|
AllowedSourceRanges []string `json:"allowedSourceRanges"`
|
||||||
|
|
||||||
NumberOfInstances int32 `json:"numberOfInstances"`
|
NumberOfInstances int32 `json:"numberOfInstances"`
|
||||||
Users map[string]UserFlags `json:"users,omitempty"`
|
EnableNamespacedSecret *bool `json:"enableNamespacedSecret,omitempty"`
|
||||||
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
Users map[string]UserFlags `json:"users,omitempty"`
|
||||||
Clone *CloneDescription `json:"clone,omitempty"`
|
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
||||||
ClusterName string `json:"-"`
|
Clone *CloneDescription `json:"clone,omitempty"`
|
||||||
Databases map[string]string `json:"databases,omitempty"`
|
ClusterName string `json:"-"`
|
||||||
PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"`
|
Databases map[string]string `json:"databases,omitempty"`
|
||||||
SchedulerName *string `json:"schedulerName,omitempty"`
|
PreparedDatabases map[string]PreparedDatabase `json:"preparedDatabases,omitempty"`
|
||||||
NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"`
|
SchedulerName *string `json:"schedulerName,omitempty"`
|
||||||
Tolerations []v1.Toleration `json:"tolerations,omitempty"`
|
NodeAffinity *v1.NodeAffinity `json:"nodeAffinity,omitempty"`
|
||||||
Sidecars []Sidecar `json:"sidecars,omitempty"`
|
Tolerations []v1.Toleration `json:"tolerations,omitempty"`
|
||||||
InitContainers []v1.Container `json:"initContainers,omitempty"`
|
Sidecars []Sidecar `json:"sidecars,omitempty"`
|
||||||
PodPriorityClassName string `json:"podPriorityClassName,omitempty"`
|
InitContainers []v1.Container `json:"initContainers,omitempty"`
|
||||||
ShmVolume *bool `json:"enableShmVolume,omitempty"`
|
PodPriorityClassName string `json:"podPriorityClassName,omitempty"`
|
||||||
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
ShmVolume *bool `json:"enableShmVolume,omitempty"`
|
||||||
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
EnableLogicalBackup bool `json:"enableLogicalBackup,omitempty"`
|
||||||
StandbyCluster *StandbyDescription `json:"standby,omitempty"`
|
LogicalBackupSchedule string `json:"logicalBackupSchedule,omitempty"`
|
||||||
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
|
StandbyCluster *StandbyDescription `json:"standby,omitempty"`
|
||||||
ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"`
|
PodAnnotations map[string]string `json:"podAnnotations,omitempty"`
|
||||||
TLS *TLSDescription `json:"tls,omitempty"`
|
ServiceAnnotations map[string]string `json:"serviceAnnotations,omitempty"`
|
||||||
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
|
TLS *TLSDescription `json:"tls,omitempty"`
|
||||||
|
AdditionalVolumes []AdditionalVolume `json:"additionalVolumes,omitempty"`
|
||||||
|
|
||||||
// deprecated json tags
|
// deprecated json tags
|
||||||
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
InitContainersOld []v1.Container `json:"init_containers,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import (
|
||||||
var (
|
var (
|
||||||
alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]*$")
|
alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]*$")
|
||||||
databaseNameRegexp = 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"}
|
patroniObjectSuffixes = []string{"config", "failover", "sync"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -1111,10 +1111,13 @@ func (c *Cluster) initRobotUsers() error {
|
||||||
}
|
}
|
||||||
namespace := c.Namespace
|
namespace := c.Namespace
|
||||||
|
|
||||||
//more than one dot in the username is reported invalid by regexp
|
//if namespaced secrets are allowed
|
||||||
if strings.Contains(username, ".") {
|
if c.Postgresql.Spec.EnableNamespacedSecret != nil &&
|
||||||
splits := strings.Split(username, ".")
|
*c.Postgresql.Spec.EnableNamespacedSecret {
|
||||||
namespace = splits[0]
|
if strings.Contains(username, ".") {
|
||||||
|
splits := strings.Split(username, ".")
|
||||||
|
namespace = splits[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flags, err := normalizeUserFlags(userFlags)
|
flags, err := normalizeUserFlags(userFlags)
|
||||||
|
|
|
||||||
|
|
@ -870,6 +870,7 @@ func TestCrossNamespacedSecrets(t *testing.T) {
|
||||||
Volume: acidv1.Volume{
|
Volume: acidv1.Volume{
|
||||||
Size: "1Gi",
|
Size: "1Gi",
|
||||||
},
|
},
|
||||||
|
EnableNamespacedSecret: boolToPointer(true),
|
||||||
Users: map[string]acidv1.UserFlags{
|
Users: map[string]acidv1.UserFlags{
|
||||||
"appspace.db_user": {},
|
"appspace.db_user": {},
|
||||||
"db_user": {},
|
"db_user": {},
|
||||||
|
|
@ -920,11 +921,16 @@ func TestCrossNamespacedSecrets(t *testing.T) {
|
||||||
func TestValidUsernames(t *testing.T) {
|
func TestValidUsernames(t *testing.T) {
|
||||||
testName := "test username validity"
|
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 {
|
for _, username := range invalidUsernames {
|
||||||
if isValidUsername(username) {
|
if isValidUsername(username) {
|
||||||
t.Errorf("%s Invalid username is allowed: %s", testName, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue