mirror of
https://github.com/zalando/postgres-operator.git
synced 2026-10-04 20:21:54 +02:00
improve RBAC setup for postgres pods
changed the default name, updated helm chart and docs allow role to use privileged podsecurtitypolicies
This commit is contained in:
+111
-11
@@ -57,6 +57,7 @@ type Controller struct {
|
||||
workerLogs map[uint32]ringlog.RingLogger
|
||||
|
||||
PodServiceAccount *v1.ServiceAccount
|
||||
PodServiceAccountRole *rbacv1.Role
|
||||
PodServiceAccountRoleBinding *rbacv1.RoleBinding
|
||||
}
|
||||
|
||||
@@ -161,11 +162,12 @@ func (c *Controller) initPodServiceAccount() {
|
||||
|
||||
if c.opConfig.PodServiceAccountDefinition == "" {
|
||||
c.opConfig.PodServiceAccountDefinition = `
|
||||
{ "apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": {
|
||||
"name": "operator"
|
||||
}
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ServiceAccount",
|
||||
"metadata": {
|
||||
"name": "postgres-pod"
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
@@ -175,13 +177,13 @@ func (c *Controller) initPodServiceAccount() {
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
panic(fmt.Errorf("Unable to parse pod service account definition from the operator config map: %v", err))
|
||||
panic(fmt.Errorf("Unable to parse pod service account definition from the operator configuration: %v", err))
|
||||
case groupVersionKind.Kind != "ServiceAccount":
|
||||
panic(fmt.Errorf("pod service account definition in the operator config map defines another type of resource: %v", groupVersionKind.Kind))
|
||||
panic(fmt.Errorf("pod service account definition in the operator configuration defines another type of resource: %v", groupVersionKind.Kind))
|
||||
default:
|
||||
c.PodServiceAccount = obj.(*v1.ServiceAccount)
|
||||
if c.PodServiceAccount.Name != c.opConfig.PodServiceAccountName {
|
||||
c.logger.Warnf("in the operator config map, the pod service account name %v does not match the name %v given in the account definition; using the former for consistency", c.opConfig.PodServiceAccountName, c.PodServiceAccount.Name)
|
||||
c.logger.Warnf("in the operator configuration, the pod service account name %v does not match the name %v given in the account definition; using the former for consistency", c.opConfig.PodServiceAccountName, c.PodServiceAccount.Name)
|
||||
c.PodServiceAccount.Name = c.opConfig.PodServiceAccountName
|
||||
}
|
||||
c.PodServiceAccount.Namespace = ""
|
||||
@@ -190,6 +192,100 @@ func (c *Controller) initPodServiceAccount() {
|
||||
// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
|
||||
}
|
||||
|
||||
func (c *Controller) initRole() {
|
||||
|
||||
// service account on its own lacks any rights starting with k8s v1.8
|
||||
// operator binds it to the namespaced role with sufficient privileges
|
||||
if c.opConfig.PodServiceAccountRoleDefinition == "" {
|
||||
c.opConfig.PodServiceAccountRoleDefinition = fmt.Sprintf(`
|
||||
{
|
||||
"apiVersion": "rbac.authorization.k8s.io/v1",
|
||||
"kind": "Role",
|
||||
"metadata": {
|
||||
"name": "%s"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"endpoints"
|
||||
],
|
||||
"verbs": [
|
||||
"create",
|
||||
"delete",
|
||||
"deletecollection",
|
||||
"get",
|
||||
"list",
|
||||
"patch",
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"pods"
|
||||
],
|
||||
"verbs": [
|
||||
"get",
|
||||
"list",
|
||||
"patch",
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
""
|
||||
],
|
||||
"resources": [
|
||||
"services"
|
||||
],
|
||||
"verbs": [
|
||||
"create"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apiGroups": [
|
||||
"extensions"
|
||||
],
|
||||
"resources": [
|
||||
"podsecuritypolicies"
|
||||
],
|
||||
"resourceNames": [
|
||||
"privileged"
|
||||
],
|
||||
"verbs": [
|
||||
"use"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, c.PodServiceAccount.Name)
|
||||
}
|
||||
c.logger.Info("Parse roles")
|
||||
// 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.PodServiceAccountRoleDefinition), nil, nil)
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
panic(fmt.Errorf("unable to parse the role definition from the operator configuration: %v", err))
|
||||
case groupVersionKind.Kind != "Role":
|
||||
panic(fmt.Errorf("role definition in the operator configuration defines another type of resource: %v", groupVersionKind.Kind))
|
||||
default:
|
||||
c.PodServiceAccountRole = obj.(*rbacv1.Role)
|
||||
c.PodServiceAccountRole.Namespace = ""
|
||||
c.logger.Info("successfully parsed")
|
||||
|
||||
}
|
||||
|
||||
// actual roles bindings 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
|
||||
@@ -205,7 +301,7 @@ func (c *Controller) initRoleBinding() {
|
||||
},
|
||||
"roleRef": {
|
||||
"apiGroup": "rbac.authorization.k8s.io",
|
||||
"kind": "ClusterRole",
|
||||
"kind": "Role",
|
||||
"name": "%s"
|
||||
},
|
||||
"subjects": [
|
||||
@@ -223,9 +319,9 @@ func (c *Controller) initRoleBinding() {
|
||||
|
||||
switch {
|
||||
case err != nil:
|
||||
panic(fmt.Errorf("Unable to parse the definition of the role binding for the pod service account definition from the operator config map: %v", err))
|
||||
panic(fmt.Errorf("unable to parse the role binding definition from the operator configuration: %v", err))
|
||||
case groupVersionKind.Kind != "RoleBinding":
|
||||
panic(fmt.Errorf("role binding definition in the operator config map defines another type of resource: %v", groupVersionKind.Kind))
|
||||
panic(fmt.Errorf("role binding definition in the operator configuration defines another type of resource: %v", groupVersionKind.Kind))
|
||||
default:
|
||||
c.PodServiceAccountRoleBinding = obj.(*rbacv1.RoleBinding)
|
||||
c.PodServiceAccountRoleBinding.Namespace = ""
|
||||
@@ -253,6 +349,10 @@ func (c *Controller) initController() {
|
||||
}
|
||||
c.initPodServiceAccount()
|
||||
c.initRoleBinding()
|
||||
// init role only if binding references a role
|
||||
if c.PodServiceAccountRoleBinding != nil && c.PodServiceAccountRoleBinding.RoleRef.Kind == "Role" {
|
||||
c.initRole()
|
||||
}
|
||||
|
||||
c.modifyConfigFromEnvironment()
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ func (c *Controller) importConfigurationFromCRD(fromCRD *acidv1.OperatorConfigur
|
||||
result.CustomPodAnnotations = fromCRD.Kubernetes.CustomPodAnnotations
|
||||
result.PodServiceAccountName = fromCRD.Kubernetes.PodServiceAccountName
|
||||
result.PodServiceAccountDefinition = fromCRD.Kubernetes.PodServiceAccountDefinition
|
||||
result.PodServiceAccountRoleDefinition = fromCRD.Kubernetes.PodServiceAccountRoleDefinition
|
||||
result.PodServiceAccountRoleBindingDefinition = fromCRD.Kubernetes.PodServiceAccountRoleBindingDefinition
|
||||
result.PodEnvironmentConfigMap = fromCRD.Kubernetes.PodEnvironmentConfigMap
|
||||
result.PodTerminateGracePeriod = time.Duration(fromCRD.Kubernetes.PodTerminateGracePeriod)
|
||||
|
||||
@@ -493,23 +493,31 @@ func (c *Controller) postgresqlDelete(obj interface{}) {
|
||||
}
|
||||
|
||||
/*
|
||||
Ensures the pod service account and role bindings exists in a namespace
|
||||
Ensures the pod service account, role 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.
|
||||
The operator does not sync accounts/roles/role bindings after creation.
|
||||
*/
|
||||
func (c *Controller) submitRBACCredentials(event ClusterEvent) error {
|
||||
|
||||
namespace := event.NewSpec.GetNamespace()
|
||||
|
||||
if err := c.createPodServiceAccount(namespace); err != nil {
|
||||
return fmt.Errorf("could not create pod service account %v : %v", c.opConfig.PodServiceAccountName, err)
|
||||
return fmt.Errorf("could not create pod service account %q : %v", c.opConfig.PodServiceAccountName, err)
|
||||
}
|
||||
|
||||
// create role only if binding references a role
|
||||
// if not role is empty and we rely on an existing cluster role
|
||||
if c.PodServiceAccountRole != nil {
|
||||
if err := c.createRole(namespace); err != nil {
|
||||
return fmt.Errorf("could not create role %q : %v", c.PodServiceAccountRole.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.createRoleBindings(namespace); err != nil {
|
||||
return fmt.Errorf("could not create role binding %v : %v", c.PodServiceAccountRoleBinding.Name, err)
|
||||
return fmt.Errorf("could not create role binding %q : %v", c.PodServiceAccountRoleBinding.Name, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -517,19 +525,39 @@ func (c *Controller) submitRBACCredentials(event ClusterEvent) error {
|
||||
func (c *Controller) createPodServiceAccount(namespace string) error {
|
||||
|
||||
podServiceAccountName := c.opConfig.PodServiceAccountName
|
||||
// get a separate copy of service account
|
||||
// to prevent a race condition when setting a namespace for many clusters
|
||||
sa := *c.PodServiceAccount
|
||||
|
||||
_, 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
|
||||
c.logger.Infof(fmt.Sprintf("creating pod service account %q in the %q namespace", podServiceAccountName, namespace))
|
||||
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)
|
||||
return fmt.Errorf("cannot deploy the pod service account %q defined in the configuration to the %q namespace: %v", podServiceAccountName, namespace, err)
|
||||
}
|
||||
c.logger.Infof("successfully deployed the pod service account %q to the %q namespace", podServiceAccountName, namespace)
|
||||
} else if k8sutil.ResourceAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.logger.Infof("successfully deployed the pod service account %v to the %v namespace", podServiceAccountName, namespace)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Controller) createRole(namespace string) error {
|
||||
|
||||
podServiceAccountRoleName := c.PodServiceAccountRole.Name
|
||||
// get a separate copy of the role
|
||||
// to prevent a race condition when setting a namespace for many clusters
|
||||
role := *c.PodServiceAccountRole
|
||||
|
||||
_, err := c.KubeClient.Roles(namespace).Get(podServiceAccountRoleName, metav1.GetOptions{})
|
||||
if k8sutil.ResourceNotFound(err) {
|
||||
c.logger.Infof("creating role %q in the %q namespace", podServiceAccountRoleName, namespace)
|
||||
_, err = c.KubeClient.Roles(namespace).Create(&role)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create role %q in the %q namespace: %v", podServiceAccountRoleName, namespace, err)
|
||||
}
|
||||
c.logger.Infof("successfully deployed role %q to the %q namespace", podServiceAccountRoleName, namespace)
|
||||
} else if k8sutil.ResourceAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
@@ -541,22 +569,18 @@ func (c *Controller) createRoleBindings(namespace string) error {
|
||||
|
||||
podServiceAccountName := c.opConfig.PodServiceAccountName
|
||||
podServiceAccountRoleBindingName := c.PodServiceAccountRoleBinding.Name
|
||||
// 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).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
|
||||
c.logger.Infof("creating the role binding %q in the %q namespace", podServiceAccountRoleBindingName, namespace)
|
||||
_, 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)
|
||||
return fmt.Errorf("cannot bind the pod service account %q defined in the configuration 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user