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:
Felix Kunde 2020-01-09 13:06:59 +01:00
parent 702a194c41
commit cb96454a56
24 changed files with 382 additions and 129 deletions

View File

@ -148,6 +148,8 @@ spec:
type: string
pod_service_account_name:
type: string
pod_service_account_role_definition:
type: string
pod_service_account_role_binding_definition:
type: string
pod_terminate_grace_period:

View File

@ -9,6 +9,7 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
rules:
# all verbs allowed for custom operator resources
- apiGroups:
- acid.zalan.do
resources:
@ -16,7 +17,15 @@ rules:
- postgresqls/status
- operatorconfigurations
verbs:
- "*"
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
# to create or get/update CRDs when starting up
- apiGroups:
- apiextensions.k8s.io
resources:
@ -26,12 +35,14 @@ rules:
- get
- patch
- update
# to read configuration from ConfigMaps
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
# to manage endpoints which are also used by Patroni
- apiGroups:
- ""
resources:
@ -43,7 +54,9 @@ rules:
- get
- list
- patch
- watch # needed if zalando-postgres-operator account is used for pods as well
- update
- watch
# to CRUD secrets for database access
- apiGroups:
- ""
resources:
@ -53,6 +66,7 @@ rules:
- update
- delete
- get
# to check nodes for node readiness label
- apiGroups:
- ""
resources:
@ -61,6 +75,7 @@ rules:
- get
- list
- watch
# to read or delete existing PVCs. Creation via StatefulSet
- apiGroups:
- ""
resources:
@ -69,6 +84,7 @@ rules:
- delete
- get
- list
# to read existing PVs. Creation should be done via dynamic provisioning
- apiGroups:
- ""
resources:
@ -77,6 +93,7 @@ rules:
- get
- list
- update # only for resizing AWS volumes
# to watch Spilo pods and do rolling updates. Creation via StatefulSet
- apiGroups:
- ""
resources:
@ -85,14 +102,17 @@ rules:
- delete
- get
- list
- watch
- patch
- update
- watch
# to resize the filesystem in Spilo pods when increasing volume size
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
# to CRUD services to point to Postgres cluster instances
- apiGroups:
- ""
resources:
@ -102,6 +122,7 @@ rules:
- delete
- get
- patch
# to CRUD the StatefulSet which controls the Postgres cluster instances
- apiGroups:
- apps
resources:
@ -112,12 +133,26 @@ rules:
- get
- list
- patch
# to CRUD cron jobs for logical backups
- apiGroups:
- batch
resources:
- cronjobs
verbs:
- create
- delete
- get
- list
- patch
- update
# to get namespaces operator resources can run in
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
# to define PDBs. Update happens via delete/create
- apiGroups:
- policy
resources:
@ -126,6 +161,7 @@ rules:
- create
- delete
- get
# to create ServiceAccounts in each namespace the operator watches
- apiGroups:
- ""
resources:
@ -133,30 +169,22 @@ rules:
verbs:
- get
- create
# to create roles and role bindings to the pod service account
- apiGroups:
- "rbac.authorization.k8s.io"
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- get
- create
# needed to grant to namespaced roles when pods run in privileged mode
- apiGroups:
- "rbac.authorization.k8s.io"
- extensions
resources:
- clusterroles
verbs:
- bind
- podsecuritypolicies
resourceNames:
- {{ include "postgres-operator.serviceAccountName" . }}
- apiGroups:
- batch
resources:
- cronjobs # enables logical backups
- privileged
verbs:
- create
- delete
- get
- list
- patch
- update
- use
{{ end }}

View File

@ -14,8 +14,6 @@ roleRef:
name: {{ include "postgres-operator.serviceAccountName" . }}
subjects:
- kind: ServiceAccount
# note: the cluster role binding needs to be defined
# for every namespace the operator service account lives in.
name: {{ include "postgres-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{ end }}

View File

@ -9,7 +9,6 @@ metadata:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/instance: {{ .Release.Name }}
data:
pod_service_account_name: {{ include "postgres-operator.serviceAccountName" . }}
{{ toYaml .Values.configGeneral | indent 2 }}
{{ toYaml .Values.configUsers | indent 2 }}
{{ toYaml .Values.configKubernetes | indent 2 }}

View File

@ -14,7 +14,6 @@ configuration:
{{ toYaml .Values.configUsers | indent 4 }}
kubernetes:
oauth_token_secret_name: {{ template "postgres-operator.fullname" . }}
pod_service_account_name: {{ include "postgres-operator.serviceAccountName" . }}
{{ toYaml .Values.configKubernetes | indent 4 }}
postgres_pod_resources:
{{ toYaml .Values.configPostgresPodResources | indent 4 }}

View File

@ -100,6 +100,17 @@ configKubernetes:
pod_management_policy: "ordered_ready"
# label assigned to the Postgres pods (and services/endpoints)
pod_role_label: spilo-role
# service account definition as JSON/YAML string to be used by postgres cluster pods
# pod_service_account_definition: ""
# name of service account to be used by postgres cluster pods
pod_service_account_name: "postgres-pod"
# role definition as JSON/YAML string to be used by postgres cluster pods
# pod_service_account_role_definition: ""
# role binding definition as JSON/YAML string to be used by pod service account
# pod_service_account_role_binding_definition: ""
# Postgres pods are terminated forcefully after this timeout
pod_terminate_grace_period: 5m
# template for database user secrets generated by the operator

View File

@ -93,6 +93,17 @@ configKubernetes:
pod_management_policy: "ordered_ready"
# label assigned to the Postgres pods (and services/endpoints)
pod_role_label: spilo-role
# service account definition as JSON/YAML string to be used by postgres cluster pods
# pod_service_account_definition: ""
# name of service account to be used by postgres cluster pods
pod_service_account_name: "postgres-pod"
# role definition as JSON/YAML string to be used by postgres cluster pods
# pod_service_account_role_definition: ""
# role binding definition as JSON/YAML string to be used by pod service account
# pod_service_account_role_binding_definition: ""
# Postgres pods are terminated forcefully after this timeout
pod_terminate_grace_period: 5m
# template for database user secrets generated by the operator

View File

@ -47,6 +47,12 @@ patching the CRD manifest:
zk8 patch crd postgresqls.acid.zalan.do -p '{"spec":{"validation": null}}'
```
## Non-default cluster domain
If your cluster uses a DNS domain other than the default `cluster.local`, this
needs to be set in the operator configuration (`cluster_domain` variable). This
is used by the operator to connect to the clusters after creation.
## Namespaces
### Select the namespace to deploy to
@ -89,30 +95,6 @@ lacks access rights to any of them (except K8s system namespaces like
'list pods' execute at the cluster scope and fail at the first violation of
access rights.
The watched namespace also needs to have a (possibly different) service account
in the case database pods need to talk to the K8s API (e.g. when using
K8s-native configuration of Patroni). The operator checks that the
`pod_service_account_name` exists in the target namespace, and, if not, deploys
there the `pod_service_account_definition` from the operator
[`Config`](../pkg/util/config/config.go) with the default value of:
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: operator
```
In this definition, the operator overwrites the account's name to match
`pod_service_account_name` and the `default` namespace to match the target
namespace. The operator performs **no** further syncing of this account.
## Non-default cluster domain
If your cluster uses a DNS domain other than the default `cluster.local`, this
needs to be set in the operator configuration (`cluster_domain` variable). This
is used by the operator to connect to the clusters after creation.
## Role-based access control for the operator
The manifest [`operator-service-account-rbac.yaml`](../manifests/operator-service-account-rbac.yaml)
@ -127,14 +109,19 @@ kubectl create -f manifests/postgres-operator.yaml
kubectl create -f manifests/minimal-postgres-manifest.yaml
```
### Service account and cluster roles
### Namespaced service account and roles
Note that the service account is named `zalando-postgres-operator`. You may have
to change the `service_account_name` in the operator ConfigMap and
`serviceAccountName` in the `postgres-operator` deployment appropriately. This
is done intentionally to avoid breaking those setups that already work with the
default `operator` account. In the future the operator should ideally be run
under the `zalando-postgres-operator` service account.
For each namespace the operator watches it creates (or reads) a service account
to be used by the Postgres Pods when a new cluster is deployed. This service
account is bound to a namespaced Role via RoleBinding, which are also created
(or read) by the operator. The name and definitions of these resources can be
[configured](reference/operator_parameters.md#kubernetes-resources).
Note, that the operator performs **no** further syncing of them.
Until v1.3.1, RoleBindings pointed to the operator ClusterRole by default. This
can still be configured but is not recommended as the Postgres Pods should only
run with the least privileges required for Patroni to work. By default, the
namespaced RBAC resources are named `postgres-pod`.
### Give K8s users access to create/list `postgresqls`

View File

@ -152,21 +152,29 @@ configuration they are grouped under the `kubernetes` key.
service account used by Patroni running on individual Pods to communicate
with the operator. Required even if native Kubernetes support in Patroni is
not used, because Patroni keeps pod labels in sync with the instance role.
The default is `operator`.
The default is `postgres-pod`.
* **pod_service_account_definition**
The operator tries to create the pod Service Account in the namespace that
doesn't define such an account using the YAML definition provided by this
option. If not defined, a simple definition that contains only the name will
be used. The default is empty.
on Postgres cluster creation the operator tries to create the service account
for the Postgres pods if it does not exist in the namespace. The internal
default service account definition (defines only the name) can be overwritten
with this parameter. Make sure to provide a valid YAML or JSON string. The
default is empty.
* **pod_service_account_role_definition**
operator will try to create a role in the namespace to be used by pod service
account. The internal default definition contains permissions to manage pods
and endpoints necessary for Patroni to work. Therefore, when overwriting the
definition with this parameter make sure to provide sufficient access rights
in a valid YAML/JSON string. The default is empty.
* **pod_service_account_role_binding_definition**
This definition must bind pod service account to a role with permission
sufficient for the pods to start and for Patroni to access K8s endpoints;
service account on its own lacks any such rights starting with K8s v1.8. If
not explicitly defined by the user, a simple definition that binds the
account to the operator's own 'zalando-postgres-operator' cluster role will
be used. The default is empty.
the created service account and role are referenced with a role binding. When
overwriting its definition with this parameters using a valid YAML/JSON string
check that the specified service account and role either exist in the K8s
cluster or will be created by the operator. While it's possible to also
reference cluster roles, the binding itself can only be of kind `RoleBinding`,
not `ClusterRoleBinding`. The default is empty.
* **pod_terminate_grace_period**
Postgres pods are [terminated forcefully](https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods)

View File

@ -63,7 +63,10 @@ data:
pod_label_wait_timeout: 10m
pod_management_policy: "ordered_ready"
pod_role_label: spilo-role
pod_service_account_name: "zalando-postgres-operator"
# pod_service_account_definition: ""
pod_service_account_name: "postgres-pod"
# pod_service_account_role_definition: ""
# pod_service_account_role_binding_definition: '{"apiVersion":"rbac.authorization.k8s.io/v1","kind":"RoleBinding","metadata":{"name":"postgres-pod"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind": "ClusterRole","name": "postgres-pod"},"subjects":[{"kind": "ServiceAccount","name": "postgres-pod"}]}'
pod_terminate_grace_period: 5m
# postgres_superuser_teams: "postgres_superusers"
# protected_role_names: "admin"

View File

@ -1,14 +1,14 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: zalando-postgres-operator
name: postgres-operator
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: zalando-postgres-operator
name: postgres-operator
rules:
# all verbs allowed for custom operator resources
- apiGroups:
@ -18,7 +18,14 @@ rules:
- postgresqls/status
- operatorconfigurations
verbs:
- "*"
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
# to create or get/update CRDs when starting up
- apiGroups:
- apiextensions.k8s.io
@ -48,7 +55,8 @@ rules:
- get
- list
- patch
- watch # needed if zalando-postgres-operator account is used for pods as well
- update
- watch
# to CRUD secrets for database access
- apiGroups:
- ""
@ -95,8 +103,9 @@ rules:
- delete
- get
- list
- watch
- patch
- update
- watch
# to resize the filesystem in Spilo pods when increasing volume size
- apiGroups:
- ""
@ -126,6 +135,18 @@ rules:
- get
- list
- patch
# to CRUD cron jobs for logical backups
- apiGroups:
- batch
resources:
- cronjobs
verbs:
- create
- delete
- get
- list
- patch
- update
# to get namespaces operator resources can run in
- apiGroups:
- ""
@ -150,39 +171,35 @@ rules:
verbs:
- get
- create
# to create role bindings to the operator service account
# to create roles and role bindings to the pod service account
- apiGroups:
- "rbac.authorization.k8s.io"
- rbac.authorization.k8s.io
resources:
- rolebindings
- roles
verbs:
- get
- create
# to CRUD cron jobs for logical backups
# needed to grant to namespaced roles when pods run in privileged mode
- apiGroups:
- batch
- extensions
resources:
- cronjobs
- podsecuritypolicies
resourceNames:
- privileged
verbs:
- create
- delete
- get
- list
- patch
- update
- use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: zalando-postgres-operator
name: postgres-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: zalando-postgres-operator
name: postgres-operator
subjects:
- kind: ServiceAccount
# note: the cluster role binding needs to be defined
# for every namespace the operator service account lives in.
name: zalando-postgres-operator
name: postgres-operator
namespace: default

View File

@ -124,6 +124,8 @@ spec:
type: string
pod_service_account_name:
type: string
pod_service_account_role_definition:
type: string
pod_service_account_role_binding_definition:
type: string
pod_terminate_grace_period:

View File

@ -12,7 +12,7 @@ spec:
labels:
name: postgres-operator
spec:
serviceAccountName: zalando-postgres-operator
serviceAccountName: postgres-operator
containers:
- name: postgres-operator
image: registry.opensource.zalan.do/acid/postgres-operator:v1.3.1

View File

@ -0,0 +1,49 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: postgres-pod
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: postgres-pod
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

View File

@ -45,7 +45,8 @@ configuration:
# pod_priority_class_name: ""
pod_role_label: spilo-role
# pod_service_account_definition: ""
pod_service_account_name: zalando-postgres-operator
pod_service_account_name: postgres-pod
# pod_service_account_role_definition: ""
# pod_service_account_role_binding_definition: ""
pod_terminate_grace_period: 5m
secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}"

View File

@ -11,7 +11,14 @@ rules:
- postgresqls
- postgresqls/status
verbs:
- "*"
- create
- delete
- deletecollection
- get
- list
- patch
- update
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
@ -48,4 +55,3 @@ rules:
- get
- list
- watch

View File

@ -771,6 +771,9 @@ var OperatorConfigCRDResourceValidation = apiextv1beta1.CustomResourceValidation
"pod_service_account_name": {
Type: "string",
},
"pod_service_account_role_definition": {
Type: "string",
},
"pod_service_account_role_binding_definition": {
Type: "string",
},

View File

@ -43,6 +43,7 @@ type KubernetesMetaConfiguration struct {
PodServiceAccountName string `json:"pod_service_account_name,omitempty"`
// TODO: change it to the proper json
PodServiceAccountDefinition string `json:"pod_service_account_definition,omitempty"`
PodServiceAccountRoleDefinition string `json:"pod_service_account_role_definition,omitempty"`
PodServiceAccountRoleBindingDefinition string `json:"pod_service_account_role_binding_definition,omitempty"`
PodTerminateGracePeriod Duration `json:"pod_terminate_grace_period,omitempty"`
SpiloPrivileged bool `json:"spilo_privileged,omitempty"`

View File

@ -45,6 +45,7 @@ type Config struct {
RestConfig *rest.Config
InfrastructureRoles map[string]spec.PgUser // inherited from the controller
PodServiceAccount *v1.ServiceAccount
PodServiceAccountRole *rbacv1.Role
PodServiceAccountRoleBinding *rbacv1.RoleBinding
}

View File

@ -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()

View File

@ -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)

View File

@ -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
}

View File

@ -91,14 +91,14 @@ type Config struct {
Scalyr
LogicalBackup
WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16"`
Sidecars map[string]string `name:"sidecar_docker_images"`
// default name `operator` enables backward compatibility with the older ServiceAccountName field
PodServiceAccountName string `name:"pod_service_account_name" default:"operator"`
WatchedNamespace string `name:"watched_namespace"` // special values: "*" means 'watch all namespaces', the empty string "" means 'watch a namespace where operator is deployed to'
EtcdHost string `name:"etcd_host" default:""` // special values: the empty string "" means Patroni will use K8s as a DCS
DockerImage string `name:"docker_image" default:"registry.opensource.zalan.do/acid/spilo-cdp-12:1.6-p16"`
Sidecars map[string]string `name:"sidecar_docker_images"`
PodServiceAccountName string `name:"pod_service_account_name" default:"postgres-pod"`
// value of this string must be valid JSON or YAML; see initPodServiceAccount
PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""`
PodServiceAccountRoleDefinition string `name:"pod_service_account_role_definition" default:""`
PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding_definition" default:""`
MasterPodMoveTimeout time.Duration `name:"master_pod_move_timeout" default:"20m"`
DbHostedZone string `name:"db_hosted_zone" default:"db.example.com"`

View File

@ -39,6 +39,7 @@ type KubernetesClient struct {
corev1.NamespacesGetter
corev1.ServiceAccountsGetter
appsv1.StatefulSetsGetter
rbacv1.RolesGetter
rbacv1.RoleBindingsGetter
policyv1beta1.PodDisruptionBudgetsGetter
apiextbeta1.CustomResourceDefinitionsGetter
@ -103,6 +104,7 @@ func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) {
kubeClient.StatefulSetsGetter = client.AppsV1()
kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1beta1()
kubeClient.RESTClient = client.CoreV1().RESTClient()
kubeClient.RolesGetter = client.RbacV1()
kubeClient.RoleBindingsGetter = client.RbacV1()
kubeClient.CronJobsGetter = client.BatchV1beta1()