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-02-17 11:37:59 +01:00
parent 702a194c41
commit cb96454a56
24 changed files with 382 additions and 129 deletions
+111 -11
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()
+1
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)
+45 -21
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
}