Create cross namespace secrets (#1490)
* Create cross namespace secrets * add test cases * fixes * Fixes - include namespace in secret name only when namespace is provided - use username.namespace as key to pgUsers only when namespace is provided - avoid conflict in the role creation in db by checking namespace alongwith the username * Update unit tests * Fix test case * Fixes - update regular expression for usernames - add test to allow check for valid usernames - create pg roles with namespace (if any) appended in rolename * add more test cases for valid usernames * update docs * fixes as per review comments * update e2e * fixes * Add toggle to allow namespaced secrets * update docs * comment update * Update e2e/tests/test_e2e.py * few minor fixes * fix unit tests * fix e2e * fix e2e attempt 2 * fix e2e Co-authored-by: Rafia Sabih <rafia.sabih@zalando.de> Co-authored-by: Felix Kunde <felix-kunde@gmx.de>
This commit is contained in:
parent
9668ac21a3
commit
75a9e2be38
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,10 @@ configKubernetes:
|
||||||
|
|
||||||
# Postgres pods are terminated forcefully after this timeout
|
# Postgres pods are terminated forcefully after this timeout
|
||||||
pod_terminate_grace_period: 5m
|
pod_terminate_grace_period: 5m
|
||||||
# template for database user secrets generated by the operator
|
# template for database user secrets generated by the operator,
|
||||||
|
# here username contains the namespace in the format namespace.username
|
||||||
|
# if the user is in different namespace than cluster and cross namespace secrets
|
||||||
|
# are enabled via EnableNamespacedSecret flag.
|
||||||
secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
|
secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"
|
||||||
# set user and group for the spilo container (required to run Spilo as non-root process)
|
# set user and group for the spilo container (required to run Spilo as non-root process)
|
||||||
# spilo_runasuser: 101
|
# spilo_runasuser: 101
|
||||||
|
|
|
||||||
|
|
@ -275,11 +275,14 @@ configuration they are grouped under the `kubernetes` key.
|
||||||
|
|
||||||
* **secret_name_template**
|
* **secret_name_template**
|
||||||
a template for the name of the database user secrets generated by the
|
a template for the name of the database user secrets generated by the
|
||||||
operator. `{username}` is replaced with name of the secret, `{cluster}` with
|
operator. `{namespace}` is replaced with name of the namespace (if cross
|
||||||
the name of the cluster, `{tprkind}` with the kind of CRD (formerly known as
|
namespace secrets are enabled via EnableNamespacedSecret flag, otherwise the
|
||||||
TPR) and `{tprgroup}` with the group of the CRD. No other placeholders are
|
secret is in cluster's namespace and in that case it is not present in secret
|
||||||
allowed. The default is
|
name), `{username}` is replaced with name of the secret, `{cluster}` with the
|
||||||
`{username}.{cluster}.credentials.{tprkind}.{tprgroup}`.
|
name of the cluster, `{tprkind}` with the kind of CRD (formerly known as TPR)
|
||||||
|
and `{tprgroup}` with the group of the CRD. No other placeholders are allowed.
|
||||||
|
The default is
|
||||||
|
`{namespace}.{username}.{cluster}.credentials.{tprkind}.{tprgroup}`.
|
||||||
|
|
||||||
* **cluster_domain**
|
* **cluster_domain**
|
||||||
defines the default DNS domain for the kubernetes cluster the operator is
|
defines the default DNS domain for the kubernetes cluster the operator is
|
||||||
|
|
|
||||||
19
docs/user.md
19
docs/user.md
|
|
@ -139,6 +139,25 @@ secret, without ever sharing it outside of the cluster.
|
||||||
At the moment it is not possible to define membership of the manifest role in
|
At the moment it is not possible to define membership of the manifest role in
|
||||||
other roles.
|
other roles.
|
||||||
|
|
||||||
|
To define the secrets for the users in a different namespace than that of the cluster,
|
||||||
|
one can use the flag `EnableNamespacedSecret` and declare the namespace for the
|
||||||
|
secrets in the manifest in the following manner,
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spec:
|
||||||
|
users:
|
||||||
|
#users with secret in dfferent namespace
|
||||||
|
appspace.db_user:
|
||||||
|
- createdb
|
||||||
|
```
|
||||||
|
Here, anything before the first dot is taken as the namespace and the text after
|
||||||
|
the first dot is the username. Also, the postgres roles of these usernames would
|
||||||
|
be in the form of `namespace.username`.
|
||||||
|
|
||||||
|
For such usernames, the secret is created in the given namespace and its name is
|
||||||
|
of the following form,
|
||||||
|
`{namespace}.{username}.{team}-{clustername}.credentials.postgresql.acid.zalan.do`
|
||||||
|
|
||||||
### Infrastructure roles
|
### Infrastructure roles
|
||||||
|
|
||||||
An infrastructure role is a role that should be present on every PostgreSQL
|
An infrastructure role is a role that should be present on every PostgreSQL
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,6 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
self.eventuallyEqual(lambda: self.k8s.count_running_pods("connection-pooler=acid-minimal-cluster-pooler"),
|
self.eventuallyEqual(lambda: self.k8s.count_running_pods("connection-pooler=acid-minimal-cluster-pooler"),
|
||||||
0, "Pooler pods not scaled down")
|
0, "Pooler pods not scaled down")
|
||||||
|
|
||||||
|
|
||||||
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
def test_enable_disable_connection_pooler(self):
|
def test_enable_disable_connection_pooler(self):
|
||||||
'''
|
'''
|
||||||
|
|
@ -568,6 +567,7 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
role.pop("Password", None)
|
role.pop("Password", None)
|
||||||
self.assertDictEqual(role, {
|
self.assertDictEqual(role, {
|
||||||
"Name": "robot_zmon_acid_monitoring_new",
|
"Name": "robot_zmon_acid_monitoring_new",
|
||||||
|
"Namespace":"",
|
||||||
"Flags": None,
|
"Flags": None,
|
||||||
"MemberOf": ["robot_zmon"],
|
"MemberOf": ["robot_zmon"],
|
||||||
"Parameters": None,
|
"Parameters": None,
|
||||||
|
|
@ -587,6 +587,41 @@ class EndToEndTestCase(unittest.TestCase):
|
||||||
print('Operator log: {}'.format(k8s.get_operator_log()))
|
print('Operator log: {}'.format(k8s.get_operator_log()))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@timeout_decorator.timeout(TEST_TIMEOUT_SEC)
|
||||||
|
def test_zz_cross_namespace_secrets(self):
|
||||||
|
'''
|
||||||
|
Test secrets in different namespace
|
||||||
|
'''
|
||||||
|
app_namespace = "appspace"
|
||||||
|
|
||||||
|
v1_appnamespace = client.V1Namespace(metadata=client.V1ObjectMeta(name=app_namespace))
|
||||||
|
self.k8s.api.core_v1.create_namespace(v1_appnamespace)
|
||||||
|
self.k8s.wait_for_namespace_creation(app_namespace)
|
||||||
|
|
||||||
|
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: self.k8s.count_secrets_with_label("cluster-name=acid-minimal-cluster,application=spilo", app_namespace),
|
||||||
|
1, "Secret not created for user in namespace")
|
||||||
|
|
||||||
|
#reset the flag
|
||||||
|
self.k8s.api.custom_objects_api.patch_namespaced_custom_object(
|
||||||
|
'acid.zalan.do', 'v1', 'default',
|
||||||
|
'postgresqls', 'acid-minimal-cluster',
|
||||||
|
{
|
||||||
|
'spec': {
|
||||||
|
'enableNamespacedSecret': False,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
@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,6 +12,7 @@ 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: False
|
||||||
users: # Application/Robot users
|
users: # Application/Robot users
|
||||||
zalando:
|
zalando:
|
||||||
- superuser
|
- superuser
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type PostgresSpec struct {
|
||||||
AllowedSourceRanges []string `json:"allowedSourceRanges"`
|
AllowedSourceRanges []string `json:"allowedSourceRanges"`
|
||||||
|
|
||||||
NumberOfInstances int32 `json:"numberOfInstances"`
|
NumberOfInstances int32 `json:"numberOfInstances"`
|
||||||
|
EnableNamespacedSecret *bool `json:"enableNamespacedSecret,omitempty"`
|
||||||
Users map[string]UserFlags `json:"users,omitempty"`
|
Users map[string]UserFlags `json:"users,omitempty"`
|
||||||
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
MaintenanceWindows []MaintenanceWindow `json:"maintenanceWindows,omitempty"`
|
||||||
Clone *CloneDescription `json:"clone,omitempty"`
|
Clone *CloneDescription `json:"clone,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,11 @@ func (in *PostgresSpec) DeepCopyInto(out *PostgresSpec) {
|
||||||
*out = make([]string, len(*in))
|
*out = make([]string, len(*in))
|
||||||
copy(*out, *in)
|
copy(*out, *in)
|
||||||
}
|
}
|
||||||
|
if in.EnableNamespacedSecret != nil {
|
||||||
|
in, out := &in.EnableNamespacedSecret, &out.EnableNamespacedSecret
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
if in.Users != nil {
|
if in.Users != nil {
|
||||||
in, out := &in.Users, &out.Users
|
in, out := &in.Users, &out.Users
|
||||||
*out = make(map[string]UserFlags, len(*in))
|
*out = make(map[string]UserFlags, len(*in))
|
||||||
|
|
|
||||||
|
|
@ -942,11 +942,13 @@ func (c *Cluster) initSystemUsers() {
|
||||||
c.systemUsers[constants.SuperuserKeyName] = spec.PgUser{
|
c.systemUsers[constants.SuperuserKeyName] = spec.PgUser{
|
||||||
Origin: spec.RoleOriginSystem,
|
Origin: spec.RoleOriginSystem,
|
||||||
Name: c.OpConfig.SuperUsername,
|
Name: c.OpConfig.SuperUsername,
|
||||||
|
Namespace: c.Namespace,
|
||||||
Password: util.RandomPassword(constants.PasswordLength),
|
Password: util.RandomPassword(constants.PasswordLength),
|
||||||
}
|
}
|
||||||
c.systemUsers[constants.ReplicationUserKeyName] = spec.PgUser{
|
c.systemUsers[constants.ReplicationUserKeyName] = spec.PgUser{
|
||||||
Origin: spec.RoleOriginSystem,
|
Origin: spec.RoleOriginSystem,
|
||||||
Name: c.OpConfig.ReplicationUsername,
|
Name: c.OpConfig.ReplicationUsername,
|
||||||
|
Namespace: c.Namespace,
|
||||||
Password: util.RandomPassword(constants.PasswordLength),
|
Password: util.RandomPassword(constants.PasswordLength),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -977,6 +979,7 @@ func (c *Cluster) initSystemUsers() {
|
||||||
connectionPoolerUser := spec.PgUser{
|
connectionPoolerUser := spec.PgUser{
|
||||||
Origin: spec.RoleConnectionPooler,
|
Origin: spec.RoleConnectionPooler,
|
||||||
Name: username,
|
Name: username,
|
||||||
|
Namespace: c.Namespace,
|
||||||
Flags: []string{constants.RoleFlagLogin},
|
Flags: []string{constants.RoleFlagLogin},
|
||||||
Password: util.RandomPassword(constants.PasswordLength),
|
Password: util.RandomPassword(constants.PasswordLength),
|
||||||
}
|
}
|
||||||
|
|
@ -1081,6 +1084,7 @@ func (c *Cluster) initDefaultRoles(defaultRoles map[string]string, admin, prefix
|
||||||
newRole := spec.PgUser{
|
newRole := spec.PgUser{
|
||||||
Origin: spec.RoleOriginBootstrap,
|
Origin: spec.RoleOriginBootstrap,
|
||||||
Name: roleName,
|
Name: roleName,
|
||||||
|
Namespace: c.Namespace,
|
||||||
Password: util.RandomPassword(constants.PasswordLength),
|
Password: util.RandomPassword(constants.PasswordLength),
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
MemberOf: memberOf,
|
MemberOf: memberOf,
|
||||||
|
|
@ -1105,6 +1109,17 @@ func (c *Cluster) initRobotUsers() error {
|
||||||
if c.shouldAvoidProtectedOrSystemRole(username, "manifest robot role") {
|
if c.shouldAvoidProtectedOrSystemRole(username, "manifest robot role") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
namespace := c.Namespace
|
||||||
|
|
||||||
|
//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)
|
flags, err := normalizeUserFlags(userFlags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid flags for user %q: %v", username, err)
|
return fmt.Errorf("invalid flags for user %q: %v", username, err)
|
||||||
|
|
@ -1116,6 +1131,7 @@ func (c *Cluster) initRobotUsers() error {
|
||||||
newRole := spec.PgUser{
|
newRole := spec.PgUser{
|
||||||
Origin: spec.RoleOriginManifest,
|
Origin: spec.RoleOriginManifest,
|
||||||
Name: username,
|
Name: username,
|
||||||
|
Namespace: namespace,
|
||||||
Password: util.RandomPassword(constants.PasswordLength),
|
Password: util.RandomPassword(constants.PasswordLength),
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
AdminRole: adminRole,
|
AdminRole: adminRole,
|
||||||
|
|
@ -1233,6 +1249,7 @@ func (c *Cluster) initInfrastructureRoles() error {
|
||||||
return fmt.Errorf("invalid flags for user '%v': %v", username, err)
|
return fmt.Errorf("invalid flags for user '%v': %v", username, err)
|
||||||
}
|
}
|
||||||
newRole.Flags = flags
|
newRole.Flags = flags
|
||||||
|
newRole.Namespace = c.Namespace
|
||||||
|
|
||||||
if currentRole, present := c.pgUsers[username]; present {
|
if currentRole, present := c.pgUsers[username]; present {
|
||||||
c.pgUsers[username] = c.resolveNameConflict(¤tRole, &newRole)
|
c.pgUsers[username] = c.resolveNameConflict(¤tRole, &newRole)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ import (
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
acidv1 "github.com/zalando/postgres-operator/pkg/apis/acid.zalan.do/v1"
|
||||||
|
fakeacidv1 "github.com/zalando/postgres-operator/pkg/generated/clientset/versioned/fake"
|
||||||
"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/config"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/constants"
|
"github.com/zalando/postgres-operator/pkg/util/constants"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
"github.com/zalando/postgres-operator/pkg/util/k8sutil"
|
||||||
"github.com/zalando/postgres-operator/pkg/util/teams"
|
"github.com/zalando/postgres-operator/pkg/util/teams"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -79,8 +81,8 @@ func TestInitRobotUsers(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}},
|
manifestUsers: map[string]acidv1.UserFlags{"foo": {"superuser", "createdb"}},
|
||||||
infraRoles: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Password: "bar"}},
|
infraRoles: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}},
|
||||||
result: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Password: "bar"}},
|
result: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginInfrastructure, Name: "foo", Namespace: cl.Namespace, Password: "bar"}},
|
||||||
err: nil,
|
err: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -845,3 +847,90 @@ func TestPreparedDatabases(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCrossNamespacedSecrets(t *testing.T) {
|
||||||
|
testName := "test secrets in different namespace"
|
||||||
|
clientSet := fake.NewSimpleClientset()
|
||||||
|
acidClientSet := fakeacidv1.NewSimpleClientset()
|
||||||
|
namespace := "default"
|
||||||
|
|
||||||
|
client := k8sutil.KubernetesClient{
|
||||||
|
StatefulSetsGetter: clientSet.AppsV1(),
|
||||||
|
ServicesGetter: clientSet.CoreV1(),
|
||||||
|
DeploymentsGetter: clientSet.AppsV1(),
|
||||||
|
PostgresqlsGetter: acidClientSet.AcidV1(),
|
||||||
|
SecretsGetter: clientSet.CoreV1(),
|
||||||
|
}
|
||||||
|
pg := acidv1.Postgresql{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "acid-fake-cluster",
|
||||||
|
Namespace: namespace,
|
||||||
|
},
|
||||||
|
Spec: acidv1.PostgresSpec{
|
||||||
|
Volume: acidv1.Volume{
|
||||||
|
Size: "1Gi",
|
||||||
|
},
|
||||||
|
EnableNamespacedSecret: boolToPointer(true),
|
||||||
|
Users: map[string]acidv1.UserFlags{
|
||||||
|
"appspace.db_user": {},
|
||||||
|
"db_user": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cluster = New(
|
||||||
|
Config{
|
||||||
|
OpConfig: config.Config{
|
||||||
|
ConnectionPooler: config.ConnectionPooler{
|
||||||
|
ConnectionPoolerDefaultCPURequest: "100m",
|
||||||
|
ConnectionPoolerDefaultCPULimit: "100m",
|
||||||
|
ConnectionPoolerDefaultMemoryRequest: "100Mi",
|
||||||
|
ConnectionPoolerDefaultMemoryLimit: "100Mi",
|
||||||
|
NumberOfInstances: int32ToPointer(1),
|
||||||
|
},
|
||||||
|
PodManagementPolicy: "ordered_ready",
|
||||||
|
Resources: config.Resources{
|
||||||
|
ClusterLabels: map[string]string{"application": "spilo"},
|
||||||
|
ClusterNameLabel: "cluster-name",
|
||||||
|
DefaultCPURequest: "300m",
|
||||||
|
DefaultCPULimit: "300m",
|
||||||
|
DefaultMemoryRequest: "300Mi",
|
||||||
|
DefaultMemoryLimit: "300Mi",
|
||||||
|
PodRoleLabel: "spilo-role",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, client, pg, logger, eventRecorder)
|
||||||
|
|
||||||
|
userNamespaceMap := map[string]string{
|
||||||
|
cluster.Namespace: "db_user",
|
||||||
|
"appspace": "appspace.db_user",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cluster.initRobotUsers()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Could not create secret for namespaced users with error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, u := range cluster.pgUsers {
|
||||||
|
if u.Name != userNamespaceMap[u.Namespace] {
|
||||||
|
t.Errorf("%s: Could not create namespaced user in its correct namespaces for user %s in namespace %s", testName, u.Name, u.Namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidUsernames(t *testing.T) {
|
||||||
|
testName := "test username validity"
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1547,10 +1547,11 @@ func (c *Cluster) generateUserSecrets() map[string]*v1.Secret {
|
||||||
namespace := c.Namespace
|
namespace := c.Namespace
|
||||||
for username, pgUser := range c.pgUsers {
|
for username, pgUser := range c.pgUsers {
|
||||||
//Skip users with no password i.e. human users (they'll be authenticated using pam)
|
//Skip users with no password i.e. human users (they'll be authenticated using pam)
|
||||||
secret := c.generateSingleUserSecret(namespace, pgUser)
|
secret := c.generateSingleUserSecret(pgUser.Namespace, pgUser)
|
||||||
if secret != nil {
|
if secret != nil {
|
||||||
secrets[username] = secret
|
secrets[username] = secret
|
||||||
}
|
}
|
||||||
|
namespace = pgUser.Namespace
|
||||||
}
|
}
|
||||||
/* special case for the system user */
|
/* special case for the system user */
|
||||||
for _, systemUser := range c.systemUsers {
|
for _, systemUser := range c.systemUsers {
|
||||||
|
|
@ -1590,7 +1591,7 @@ func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser)
|
||||||
secret := v1.Secret{
|
secret := v1.Secret{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: c.credentialSecretName(username),
|
Name: c.credentialSecretName(username),
|
||||||
Namespace: namespace,
|
Namespace: pgUser.Namespace,
|
||||||
Labels: lbls,
|
Labels: lbls,
|
||||||
Annotations: c.annotationsSet(nil),
|
Annotations: c.annotationsSet(nil),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func (c *Cluster) listResources() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, obj := range c.Secrets {
|
for _, obj := range c.Secrets {
|
||||||
c.logger.Infof("found secret: %q (uid: %q)", util.NameFromMeta(obj.ObjectMeta), obj.UID)
|
c.logger.Infof("found secret: %q (uid: %q) namesapce: %s", util.NameFromMeta(obj.ObjectMeta), obj.UID, obj.ObjectMeta.Namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
for role, endpoint := range c.Endpoints {
|
for role, endpoint := range c.Endpoints {
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,7 @@ func (c *Cluster) syncSecrets() error {
|
||||||
for secretUsername, secretSpec := range secrets {
|
for secretUsername, secretSpec := range secrets {
|
||||||
if secret, err = c.KubeClient.Secrets(secretSpec.Namespace).Create(context.TODO(), secretSpec, metav1.CreateOptions{}); err == nil {
|
if secret, err = c.KubeClient.Secrets(secretSpec.Namespace).Create(context.TODO(), secretSpec, metav1.CreateOptions{}); err == nil {
|
||||||
c.Secrets[secret.UID] = secret
|
c.Secrets[secret.UID] = secret
|
||||||
c.logger.Debugf("created new secret %s, uid: %s", util.NameFromMeta(secret.ObjectMeta), secret.UID)
|
c.logger.Debugf("created new secret %s, namespace: %s, uid: %s", util.NameFromMeta(secret.ObjectMeta), secretSpec.Namespace, secret.UID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if k8sutil.ResourceAlreadyExists(err) {
|
if k8sutil.ResourceAlreadyExists(err) {
|
||||||
|
|
@ -521,7 +521,7 @@ func (c *Cluster) syncSecrets() error {
|
||||||
userMap[secretUsername] = pwdUser
|
userMap[secretUsername] = pwdUser
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("could not create secret for user %s: %v", secretUsername, err)
|
return fmt.Errorf("could not create secret for user %s: in namespace %s: %v", secretUsername, secretSpec.Namespace, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -556,11 +556,17 @@ func (c *Cluster) syncRoles() (err error) {
|
||||||
|
|
||||||
// create list of database roles to query
|
// create list of database roles to query
|
||||||
for _, u := range c.pgUsers {
|
for _, u := range c.pgUsers {
|
||||||
userNames = append(userNames, u.Name)
|
pgRole := u.Name
|
||||||
|
if u.Namespace != c.Namespace && u.Namespace != "" {
|
||||||
|
// to avoid the conflict of having multiple users of same name
|
||||||
|
// but each in different namespace.
|
||||||
|
pgRole = fmt.Sprintf("%s.%s", u.Name, u.Namespace)
|
||||||
|
}
|
||||||
|
userNames = append(userNames, pgRole)
|
||||||
// add team member role name with rename suffix in case we need to rename it back
|
// add team member role name with rename suffix in case we need to rename it back
|
||||||
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
|
if u.Origin == spec.RoleOriginTeamsAPI && c.OpConfig.EnableTeamMemberDeprecation {
|
||||||
deletedUsers[u.Name+c.OpConfig.RoleDeletionSuffix] = u.Name
|
deletedUsers[pgRole+c.OpConfig.RoleDeletionSuffix] = pgRole
|
||||||
userNames = append(userNames, u.Name+c.OpConfig.RoleDeletionSuffix)
|
userNames = append(userNames, pgRole+c.OpConfig.RoleDeletionSuffix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ const (
|
||||||
type PgUser struct {
|
type PgUser struct {
|
||||||
Origin RoleOrigin `yaml:"-"`
|
Origin RoleOrigin `yaml:"-"`
|
||||||
Name string `yaml:"-"`
|
Name string `yaml:"-"`
|
||||||
|
Namespace string `yaml:"-"`
|
||||||
Password string `yaml:"-"`
|
Password string `yaml:"-"`
|
||||||
Flags []string `yaml:"user_flags"`
|
Flags []string `yaml:"user_flags"`
|
||||||
MemberOf []string `yaml:"inrole"`
|
MemberOf []string `yaml:"inrole"`
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue