update individual role secrets from infrastructure roles (#206)

* Track origin of roles.

* Propagate changes on infrastructure roles to corresponding secrets.

When the password in the infrastructure role is updated, re-generate the
secret for that role.

Previously, the password for an infrastructure role was always fetched from
the secret, making any updates to such role a no-op after the corresponding
secret had been generated.
This commit is contained in:
Oleksii Kliukin 2018-02-23 17:24:04 +01:00 committed by GitHub
parent 7b05758893
commit 2bb7e98268
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 44 additions and 9 deletions

View File

@ -656,10 +656,12 @@ func (c *Cluster) initSystemUsers() {
// secrets, therefore, setting flags like SUPERUSER or REPLICATION // secrets, therefore, setting flags like SUPERUSER or REPLICATION
// is not necessary here // is not necessary here
c.systemUsers[constants.SuperuserKeyName] = spec.PgUser{ c.systemUsers[constants.SuperuserKeyName] = spec.PgUser{
Origin: spec.RoleOriginSystem,
Name: c.OpConfig.SuperUsername, Name: c.OpConfig.SuperUsername,
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,
Name: c.OpConfig.ReplicationUsername, Name: c.OpConfig.ReplicationUsername,
Password: util.RandomPassword(constants.PasswordLength), Password: util.RandomPassword(constants.PasswordLength),
} }
@ -680,6 +682,7 @@ func (c *Cluster) initRobotUsers() error {
} }
if _, present := c.pgUsers[username]; !present { if _, present := c.pgUsers[username]; !present {
c.pgUsers[username] = spec.PgUser{ c.pgUsers[username] = spec.PgUser{
Origin: spec.RoleOriginManifest,
Name: username, Name: username,
Password: util.RandomPassword(constants.PasswordLength), Password: util.RandomPassword(constants.PasswordLength),
Flags: flags, Flags: flags,
@ -723,6 +726,7 @@ func (c *Cluster) initHumanUsers() error {
} }
c.pgUsers[username] = spec.PgUser{ c.pgUsers[username] = spec.PgUser{
Origin: spec.RoleOriginTeamsAPI,
Name: username, Name: username,
Flags: flags, Flags: flags,
MemberOf: memberOf, MemberOf: memberOf,

View File

@ -33,9 +33,9 @@ func TestInitRobotUsers(t *testing.T) {
}{ }{
{ {
manifestUsers: map[string]spec.UserFlags{"foo": {"superuser", "createdb"}}, manifestUsers: map[string]spec.UserFlags{"foo": {"superuser", "createdb"}},
infraRoles: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar"}}, infraRoles: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginManifest, Name: "foo", Password: "bar"}},
result: map[string]spec.PgUser{"foo": {Name: "foo", Password: "bar", result: map[string]spec.PgUser{"foo": {Origin: spec.RoleOriginManifest,
Flags: []string{"CREATEDB", "LOGIN", "SUPERUSER"}}}, Name: "foo", Password: "bar", Flags: []string{"CREATEDB", "LOGIN", "SUPERUSER"}}},
err: nil, err: nil,
}, },
{ {
@ -119,10 +119,11 @@ func TestInitHumanUsers(t *testing.T) {
result map[string]spec.PgUser result map[string]spec.PgUser
}{ }{
{ {
existingRoles: map[string]spec.PgUser{"foo": {Name: "foo", Flags: []string{"NOLOGIN"}}, existingRoles: map[string]spec.PgUser{"foo": {Name: "foo", Origin: spec.RoleOriginTeamsAPI,
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}}, Flags: []string{"NOLOGIN"}}, "bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
teamRoles: []string{"foo"}, teamRoles: []string{"foo"},
result: map[string]spec.PgUser{"foo": {Name: "foo", MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}}, result: map[string]spec.PgUser{"foo": {Name: "foo", Origin: spec.RoleOriginTeamsAPI,
MemberOf: []string{cl.OpConfig.PamRoleName}, Flags: []string{"LOGIN", "SUPERUSER"}},
"bar": {Name: "bar", Flags: []string{"NOLOGIN"}}}, "bar": {Name: "bar", Flags: []string{"NOLOGIN"}}},
}, },
{ {

View File

@ -646,8 +646,13 @@ func (c *Cluster) generateUserSecrets() (secrets map[string]*v1.Secret) {
func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) *v1.Secret { func (c *Cluster) generateSingleUserSecret(namespace string, pgUser spec.PgUser) *v1.Secret {
//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)
if pgUser.Password == "" { if pgUser.Password == "" {
if pgUser.Origin != spec.RoleOriginTeamsAPI {
c.logger.Warningf("could not generate secret for a non-teamsAPI role %q: role has no password",
pgUser.Name)
}
return nil return nil
} }
username := pgUser.Name username := pgUser.Name
secret := v1.Secret{ secret := v1.Secret{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@ -322,6 +322,10 @@ func (c *Cluster) syncSecrets() error {
if err2 != nil { if err2 != nil {
return fmt.Errorf("could not get current secret: %v", err2) return fmt.Errorf("could not get current secret: %v", err2)
} }
if secretUsername != string(curSecret.Data["username"]) {
c.logger.Warningf("secret %q does not contain the role %q", secretSpec.Name, secretUsername)
continue
}
c.logger.Debugf("secret %q already exists, fetching its password", util.NameFromMeta(curSecret.ObjectMeta)) c.logger.Debugf("secret %q already exists, fetching its password", util.NameFromMeta(curSecret.ObjectMeta))
if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name { if secretUsername == c.systemUsers[constants.SuperuserKeyName].Name {
secretUsername = constants.SuperuserKeyName secretUsername = constants.SuperuserKeyName
@ -333,8 +337,17 @@ func (c *Cluster) syncSecrets() error {
userMap = c.pgUsers userMap = c.pgUsers
} }
pwdUser := userMap[secretUsername] pwdUser := userMap[secretUsername]
pwdUser.Password = string(curSecret.Data["password"]) // if this secret belongs to the infrastructure role and the password has changed - replace it in the secret
userMap[secretUsername] = pwdUser if pwdUser.Password != string(curSecret.Data["password"]) && pwdUser.Origin == spec.RoleOriginInfrastructure {
c.logger.Debugf("updating the secret %q from the infrastructure roles", secretSpec.Name)
if _, err := c.KubeClient.Secrets(secretSpec.Namespace).Update(secretSpec); err != nil {
return fmt.Errorf("could not update infrastructure role secret for role %q: %v", secretUsername, err)
}
} else {
// for non-infrastructure role - update the role with the password from the secret
pwdUser.Password = string(curSecret.Data["password"])
userMap[secretUsername] = pwdUser
}
continue continue
} else { } else {

View File

@ -116,7 +116,7 @@ Users:
// in worst case we would have one line per user // in worst case we would have one line per user
for i := 1; i <= len(data); i++ { for i := 1; i <= len(data); i++ {
properties := []string{"user", "password", "inrole"} properties := []string{"user", "password", "inrole"}
t := spec.PgUser{} t := spec.PgUser{Origin: spec.RoleOriginInfrastructure}
for _, p := range properties { for _, p := range properties {
key := fmt.Sprintf("%s%d", p, i) key := fmt.Sprintf("%s%d", p, i)
if val, present := data[key]; !present { if val, present := data[key]; !present {

View File

@ -131,6 +131,7 @@ func TestGetInfrastructureRoles(t *testing.T) {
map[string]spec.PgUser{ map[string]spec.PgUser{
"testrole": { "testrole": {
Name: "testrole", Name: "testrole",
Origin: spec.RoleOriginInfrastructure,
Password: "testpassword", Password: "testpassword",
MemberOf: []string{"testinrole"}, MemberOf: []string{"testinrole"},
}, },

View File

@ -32,6 +32,16 @@ const (
fileWithNamespace = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" fileWithNamespace = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
) )
type RoleOrigin int
const (
RoleOriginUnknown = iota
RoleOriginInfrastructure
RoleOriginManifest
RoleOriginTeamsAPI
RoleOriginSystem
)
// ClusterEvent carries the payload of the Cluster TPR events. // ClusterEvent carries the payload of the Cluster TPR events.
type ClusterEvent struct { type ClusterEvent struct {
EventTime time.Time EventTime time.Time
@ -62,6 +72,7 @@ type PodEvent struct {
// PgUser contains information about a single user. // PgUser contains information about a single user.
type PgUser struct { type PgUser struct {
Origin RoleOrigin
Name string Name string
Password string Password string
Flags []string Flags []string