Feature/infrastructure roles (#91)
* Add infrastructure roles configured globally. Those are the roles defined in the operator itself. The operator's configuration refers to the secret containing role names, passwords and membership information. While they are referred to as roles, in reality those are users. In addition, improve the regex to filter out invalid users and make sure user secret names are compatible with DNS name spec. Add an example manifest for the infrastructure roles.
This commit is contained in:
parent
b8fba429df
commit
71b93b4cc2
|
|
@ -0,0 +1,15 @@
|
|||
apiVersion: v1
|
||||
data:
|
||||
# robot_zmon_acid_monitoring
|
||||
user1: cm9ib3Rfem1vbl9hY2lkX21vbml0b3Jpbmc=
|
||||
# robot_zmon
|
||||
inrole1: cm9ib3Rfem1vbg==
|
||||
# testuser
|
||||
user2: dGVzdHVzZXI=
|
||||
# foobar
|
||||
password2: Zm9vYmFy
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: postgresql-infrastructure-roles
|
||||
namespace: default
|
||||
type: Opaque
|
||||
|
|
@ -58,3 +58,5 @@ spec:
|
|||
value: "db.example.com"
|
||||
- name: PGOP_DNS_NAME_FORMAT
|
||||
value: "%s.%s.staging.%s"
|
||||
- name: PGOP_INFRASTRUCTURE_ROLES_SECRET_NAME
|
||||
value: "postgresql-infrastructure-roles"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import (
|
|||
|
||||
var (
|
||||
alphaNumericRegexp = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9]*$")
|
||||
userRegexp = regexp.MustCompile(`^[a-z0-9]([-_a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-_a-z0-9]*[a-z0-9])?)*$`)
|
||||
)
|
||||
|
||||
//TODO: remove struct duplication
|
||||
|
|
@ -39,6 +40,7 @@ type Config struct {
|
|||
EtcdClient etcdclient.KeysAPI
|
||||
TeamsAPIClient *teams.TeamsAPI
|
||||
OpConfig *config.Config
|
||||
InfrastructureRoles map[string]spec.PgUser // inherited from the controller
|
||||
}
|
||||
|
||||
type kubeResources struct {
|
||||
|
|
@ -122,6 +124,11 @@ func (c *Cluster) SetStatus(status spec.PostgresStatus) {
|
|||
|
||||
func (c *Cluster) initUsers() error {
|
||||
c.initSystemUsers()
|
||||
|
||||
if err := c.initInfrastructureRoles(); err != nil {
|
||||
return fmt.Errorf("Can't init infrastructure roles: %s", err)
|
||||
}
|
||||
|
||||
if err := c.initRobotUsers(); err != nil {
|
||||
return fmt.Errorf("Can't init robot users: %s", err)
|
||||
}
|
||||
|
|
@ -130,6 +137,8 @@ func (c *Cluster) initUsers() error {
|
|||
return fmt.Errorf("Can't init human users: %s", err)
|
||||
}
|
||||
|
||||
c.logger.Debugf("Initialized users: %# v", util.Pretty(c.pgUsers))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -400,3 +409,14 @@ func (c *Cluster) initHumanUsers() error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cluster) initInfrastructureRoles() error {
|
||||
// add infrastucture roles from the operator's definition
|
||||
for username, data := range c.InfrastructureRoles {
|
||||
if !isValidUsername(username) {
|
||||
return fmt.Errorf("Invalid username: '%s'", username)
|
||||
}
|
||||
c.pgUsers[username] = data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ func (c *Cluster) createPgUser(user spec.PgUser) (isHuman bool, err error) {
|
|||
if addLoginFlag {
|
||||
flags = append(flags, "LOGIN")
|
||||
}
|
||||
|
||||
if !isHuman && user.MemberOf != "" {
|
||||
flags = append(flags, fmt.Sprintf("IN ROLE \"%s\"", user.MemberOf))
|
||||
}
|
||||
userFlags := strings.Join(flags, " ")
|
||||
userPassword := fmt.Sprintf("ENCRYPTED PASSWORD '%s'", util.PGUserPassword(user))
|
||||
if user.Password == "" {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
func isValidUsername(username string) bool {
|
||||
return alphaNumericRegexp.MatchString(username)
|
||||
return userRegexp.MatchString(username)
|
||||
}
|
||||
|
||||
func normalizeUserFlags(userFlags []string) (flags []string, err error) {
|
||||
|
|
@ -218,8 +218,10 @@ func (c *Cluster) dnsName() string {
|
|||
}
|
||||
|
||||
func (c *Cluster) credentialSecretName(username string) string {
|
||||
// secret must consist of lower case alphanumeric characters, '-' or '.',
|
||||
// and must start and end with an alphanumeric character
|
||||
return fmt.Sprintf(constants.UserSecretTemplate,
|
||||
username,
|
||||
strings.Replace(username, "_", "-", -1),
|
||||
c.Metadata.Name,
|
||||
constants.TPRName,
|
||||
constants.TPRVendor)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ type Config struct {
|
|||
RestClient *rest.RESTClient
|
||||
EtcdClient etcdclient.KeysAPI
|
||||
TeamsAPIClient *teams.TeamsAPI
|
||||
InfrastructureRoles map[string]spec.PgUser
|
||||
}
|
||||
|
||||
type Controller struct {
|
||||
|
|
@ -74,6 +75,11 @@ func (c *Controller) initController() {
|
|||
}
|
||||
|
||||
c.TeamsAPIClient.RefreshTokenAction = c.getOAuthToken
|
||||
if infraRoles, err := c.getInfrastructureRoles(); err != nil {
|
||||
c.logger.Warningf("Can't get infrastructure roles: %s", err)
|
||||
} else {
|
||||
c.InfrastructureRoles = infraRoles
|
||||
}
|
||||
|
||||
// Postgresqls
|
||||
clusterLw := &cache.ListWatch{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
extv1beta "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/cluster"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/spec"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/constants"
|
||||
"github.bus.zalan.do/acid/postgres-operator/pkg/util/k8sutil"
|
||||
)
|
||||
|
|
@ -19,6 +20,7 @@ func (c *Controller) makeClusterConfig() cluster.Config {
|
|||
EtcdClient: c.EtcdClient,
|
||||
TeamsAPIClient: c.TeamsAPIClient,
|
||||
OpConfig: c.opConfig,
|
||||
InfrastructureRoles: c.InfrastructureRoles,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -71,3 +73,48 @@ func (c *Controller) createTPR() error {
|
|||
|
||||
return k8sutil.WaitTPRReady(restClient, c.opConfig.TPR.ReadyWaitInterval, c.opConfig.TPR.ReadyWaitTimeout, c.PodNamespace)
|
||||
}
|
||||
|
||||
func (c *Controller) getInfrastructureRoles() (result map[string]spec.PgUser, err error) {
|
||||
if c.opConfig.InfrastructureRolesSecretName == "" {
|
||||
// we don't have infrastructure roles defined, bail out
|
||||
return nil, nil
|
||||
}
|
||||
infraRolesSecret, err := c.KubeClient.Secrets(api.NamespaceDefault).Get(c.opConfig.InfrastructureRolesSecretName)
|
||||
if err != nil {
|
||||
c.logger.Debugf("Infrastructure roles secret name: %s", c.opConfig.InfrastructureRolesSecretName)
|
||||
return nil, fmt.Errorf("Can't get infrastructure roles Secret: %s", err)
|
||||
}
|
||||
data := infraRolesSecret.Data
|
||||
result = make(map[string]spec.PgUser)
|
||||
Users:
|
||||
// in worst case we would have one line per user
|
||||
for i := 1; i <= len(data); i++ {
|
||||
properties := []string{"user", "password", "inrole"}
|
||||
t := spec.PgUser{}
|
||||
for _, p := range properties {
|
||||
key := fmt.Sprintf("%s%d", p, i)
|
||||
if val, present := data[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 = s
|
||||
default:
|
||||
c.logger.Warnf("Unknown key %s", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.Name != "" {
|
||||
result[t.Name] = t
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,4 +37,5 @@ type PgUser struct {
|
|||
Name string
|
||||
Password string
|
||||
Flags []string
|
||||
MemberOf string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ type Auth struct {
|
|||
PamConfiguration string `split_words:"true" default:"https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees"`
|
||||
TeamsAPIUrl string `envconfig:"teams_api_url" default:"https://teams.example.com/api/"`
|
||||
OAuthTokenSecretName string `envconfig:"oauth_token_secret_name" default:"postgresql-operator"`
|
||||
InfrastructureRolesSecretName string `split_words:"true"`
|
||||
SuperUsername string `split_words:"true" default:"postgres"`
|
||||
ReplicationUsername string `split_words:"true" default:"replication"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package teams
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in New Issue