mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-09-30 20:02:14 +02:00
Submit RBAC credentials during initial Event processing (#344)
* During initial Event processing submit the service account for pods and bind it to a cluster role that allows Patroni to successfully start. The cluster role is assumed to be created by the k8s cluster administrator.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/pkg/api/v1"
|
||||
rbacv1beta1 "k8s.io/client-go/pkg/apis/rbac/v1beta1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/apiserver"
|
||||
@@ -52,7 +53,9 @@ type Controller struct {
|
||||
|
||||
workerLogs map[uint32]ringlog.RingLogger
|
||||
|
||||
PodServiceAccount *v1.ServiceAccount
|
||||
PodServiceAccount *v1.ServiceAccount
|
||||
PodServiceAccountRoleBinding *rbacv1beta1.RoleBinding
|
||||
namespacesWithDefinedRBAC sync.Map
|
||||
}
|
||||
|
||||
// NewController creates a new controller
|
||||
@@ -162,6 +165,53 @@ func (c *Controller) initPodServiceAccount() {
|
||||
// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
|
||||
}
|
||||
|
||||
func (c *Controller) initRoleBinding() {
|
||||
|
||||
// service account on its own lacks any rights starting with k8s v1.8
|
||||
// operator binds it to the cluster role with sufficient priviliges
|
||||
// we assume the role is created by the k8s administrator
|
||||
if c.opConfig.PodServiceAccountRoleBindingDefinition == "" {
|
||||
c.opConfig.PodServiceAccountRoleBindingDefinition = `
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1beta1",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "zalando-postgres-operator"
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"name": "zalando-postgres-operator"
|
||||
},
|
||||
"subjects": [
|
||||
{
|
||||
"kind": "ServiceAccount",
|
||||
"name": "operator"
|
||||
}
|
||||
]
|
||||
}`
|
||||
}
|
||||
c.logger.Info("Parse role bindings")
|
||||
// re-uses k8s internal parsing. See k8s client-go issue #193 for explanation
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, groupVersionKind, err := decode([]byte(c.opConfig.PodServiceAccountRoleBindingDefinition), nil, nil)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
panic(fmt.Errorf("Unable to parse the definiton of the role binding for the pod service account definiton from the operator config map: %v", err))
|
||||
case groupVersionKind.Kind != "RoleBinding":
|
||||
panic(fmt.Errorf("role binding definiton in the operator config map defines another type of resource: %v", groupVersionKind.Kind))
|
||||
default:
|
||||
c.PodServiceAccountRoleBinding = obj.(*rbacv1beta1.RoleBinding)
|
||||
c.PodServiceAccountRoleBinding.Namespace = ""
|
||||
c.PodServiceAccountRoleBinding.Subjects[0].Name = c.PodServiceAccount.Name
|
||||
c.logger.Info("successfully parsed")
|
||||
|
||||
}
|
||||
|
||||
// actual roles bindings are deployed at the time of Postgres/Spilo cluster creation
|
||||
}
|
||||
|
||||
func (c *Controller) initController() {
|
||||
c.initClients()
|
||||
|
||||
@@ -176,6 +226,8 @@ func (c *Controller) initController() {
|
||||
}
|
||||
} else {
|
||||
c.initOperatorConfig()
|
||||
c.initPodServiceAccount()
|
||||
c.initRoleBinding()
|
||||
}
|
||||
|
||||
c.modifyConfigFromEnvironment()
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util/config"
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *Controller) readOperatorConfigurationFromCRD(configObjectNamespace, configObjectName string) (*config.OperatorConfiguration, error) {
|
||||
@@ -49,6 +50,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *config.OperatorConfigur
|
||||
|
||||
result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName
|
||||
result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition
|
||||
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
||||
result.PodTerminateGracePeriod = time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod)
|
||||
result.WatchedNamespace = fromCRD.Kubernetes.WatchedNamespace
|
||||
result.PDBNameFormat = fromCRD.Kubernetes.PDBNameFormat
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/spec"
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util"
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util/constants"
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util/k8sutil"
|
||||
"github.com/zalando-incubator/postgres-operator/pkg/util/ringlog"
|
||||
)
|
||||
|
||||
@@ -179,6 +180,11 @@ func (c *Controller) processEvent(event spec.ClusterEvent) {
|
||||
c.warnOnDeprecatedPostgreSQLSpecParameters(&event.NewSpec.Spec)
|
||||
c.mergeDeprecatedPostgreSQLSpecParameters(&event.NewSpec.Spec)
|
||||
}
|
||||
|
||||
if err := c.submitRBACCredentials(event); err != nil {
|
||||
c.logger.Warnf("Pods and/or Patroni may misfunction due to the lack of permissions: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch event.EventType {
|
||||
@@ -457,3 +463,78 @@ func (c *Controller) postgresqlDelete(obj interface{}) {
|
||||
|
||||
c.queueClusterEvent(pg, nil, spec.EventDelete)
|
||||
}
|
||||
|
||||
/*
|
||||
Ensures the pod service account and role bindings exists in a namespace before a PG cluster is created there so that a user does not have to deploy these credentials manually.
|
||||
StatefulSets require the service account to create pods; Patroni requires relevant RBAC bindings to access endpoints.
|
||||
|
||||
The operator does not sync accounts/role bindings after creation.
|
||||
*/
|
||||
func (c *Controller) submitRBACCredentials(event spec.ClusterEvent) error {
|
||||
|
||||
namespace := event.NewSpec.GetNamespace()
|
||||
if _, ok := c.namespacesWithDefinedRBAC.Load(namespace); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := c.createPodServiceAccount(namespace); err != nil {
|
||||
return fmt.Errorf("could not create pod service account %v : %v", c.opConfig.PodServiceAccountName, err)
|
||||
}
|
||||
|
||||
if err := c.createRoleBindings(namespace); err != nil {
|
||||
return fmt.Errorf("could not create role binding %v : %v", c.PodServiceAccountRoleBinding.Name, err)
|
||||
}
|
||||
|
||||
c.namespacesWithDefinedRBAC.Store(namespace, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) createPodServiceAccount(namespace string) error {
|
||||
|
||||
podServiceAccountName := c.opConfig.PodServiceAccountName
|
||||
_, err := c.KubeClient.ServiceAccounts(namespace).Get(podServiceAccountName, metav1.GetOptions{})
|
||||
if k8sutil.ResourceNotFound(err) {
|
||||
|
||||
c.logger.Infof(fmt.Sprintf("creating pod service account in the namespace %v", namespace))
|
||||
|
||||
// get a separate copy of service account
|
||||
// to prevent a race condition when setting a namespace for many clusters
|
||||
sa := *c.PodServiceAccount
|
||||
if _, err = c.KubeClient.ServiceAccounts(namespace).Create(&sa); err != nil {
|
||||
return fmt.Errorf("cannot deploy the pod service account %v defined in the config map to the %v namespace: %v", podServiceAccountName, namespace, err)
|
||||
}
|
||||
|
||||
c.logger.Infof("successfully deployed the pod service account %v to the %v namespace", podServiceAccountName, namespace)
|
||||
} else if k8sutil.ResourceAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) createRoleBindings(namespace string) error {
|
||||
|
||||
podServiceAccountName := c.opConfig.PodServiceAccountName
|
||||
podServiceAccountRoleBindingName := c.PodServiceAccountRoleBinding.Name
|
||||
|
||||
_, err := c.KubeClient.RoleBindings(namespace).Get(podServiceAccountRoleBindingName, metav1.GetOptions{})
|
||||
if k8sutil.ResourceNotFound(err) {
|
||||
|
||||
c.logger.Infof("Creating the role binding %v in the namespace %v", podServiceAccountRoleBindingName, namespace)
|
||||
|
||||
// get a separate copy of role binding
|
||||
// to prevent a race condition when setting a namespace for many clusters
|
||||
rb := *c.PodServiceAccountRoleBinding
|
||||
_, err = c.KubeClient.RoleBindings(namespace).Create(&rb)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot bind the pod service account %q defined in the config map to the cluster role in the %q namespace: %v", podServiceAccountName, namespace, err)
|
||||
}
|
||||
|
||||
c.logger.Infof("successfully deployed the role binding for the pod service account %q to the %q namespace", podServiceAccountName, namespace)
|
||||
|
||||
} else if k8sutil.ResourceAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user