Prototype automatic submission of role bindings
This commit is contained in:
		
							parent
							
								
									6ee0349536
								
							
						
					
					
						commit
						9a51f34dea
					
				|  | @ -17,6 +17,7 @@ import ( | ||||||
| 	"k8s.io/client-go/pkg/api/v1" | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| 	"k8s.io/client-go/pkg/apis/apps/v1beta1" | 	"k8s.io/client-go/pkg/apis/apps/v1beta1" | ||||||
| 	policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" | 	policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" | ||||||
|  | 	rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1" | ||||||
| 	"k8s.io/client-go/rest" | 	"k8s.io/client-go/rest" | ||||||
| 	"k8s.io/client-go/tools/cache" | 	"k8s.io/client-go/tools/cache" | ||||||
| 
 | 
 | ||||||
|  | @ -39,10 +40,11 @@ var ( | ||||||
| 
 | 
 | ||||||
| // Config contains operator-wide clients and configuration used from a cluster. TODO: remove struct duplication.
 | // Config contains operator-wide clients and configuration used from a cluster. TODO: remove struct duplication.
 | ||||||
| type Config struct { | type Config struct { | ||||||
| 	OpConfig            config.Config | 	OpConfig                     config.Config | ||||||
| 	RestConfig          *rest.Config | 	RestConfig                   *rest.Config | ||||||
| 	InfrastructureRoles map[string]spec.PgUser // inherited from the controller
 | 	InfrastructureRoles          map[string]spec.PgUser // inherited from the controller
 | ||||||
| 	PodServiceAccount   *v1.ServiceAccount | 	PodServiceAccount            *v1.ServiceAccount | ||||||
|  | 	PodServiceAccountRoleBinding *rbac.RoleBinding | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type kubeResources struct { | type kubeResources struct { | ||||||
|  | @ -200,7 +202,7 @@ func (c *Cluster) initUsers() error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|   Ensures the service account required by StatefulSets to create pods exists in a namespace before a PG cluster is created there so that a user does not have to deploy the account manually. |   Ensures the service account required by StatefulSets to create pods exists in a namespace and has correct access rights before a PG cluster is created there so that a user does not have to deploy and configure the account manually. | ||||||
| 
 | 
 | ||||||
|   The operator does not sync these accounts after creation. |   The operator does not sync these accounts after creation. | ||||||
| */ | */ | ||||||
|  | @ -229,7 +231,31 @@ func (c *Cluster) createPodServiceAccounts() error { | ||||||
| 		c.logger.Infof("successfully found the service account %q used to create pods to the namespace %q", podServiceAccountName, c.Namespace) | 		c.logger.Infof("successfully found the service account %q used to create pods to the namespace %q", podServiceAccountName, c.Namespace) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	podServiceAccountRoleBindingName := c.Config.PodServiceAccountRoleBinding.Name | ||||||
|  | 	_, err = c.KubeClient.RoleBindings(c.Namespace).Get(podServiceAccountRoleBindingName, metav1.GetOptions{}) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 
 | ||||||
|  | 		c.setProcessName(fmt.Sprintf("binding the pod service account in the namespace %v to the cluster role", c.Namespace)) | ||||||
|  | 
 | ||||||
|  | 		c.logger.Infof("the role binding %q for the pod service account %q cannot be retrieved in the namespace %q. Trying to deploy the role binding.", podServiceAccountRoleBindingName, podServiceAccountName, c.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(c.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, c.Namespace, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		c.logger.Infof("successfully deployed the role binding for the pod service account %q to the %q namespace", podServiceAccountName, c.Namespace) | ||||||
|  | 
 | ||||||
|  | 	} else { | ||||||
|  | 		c.logger.Infof("successfully found the role binding %q for the service account %q used to create pods to the namespace %q", podServiceAccountRoleBindingName, podServiceAccountName, c.Namespace) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	return nil | 	return nil | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Create creates the new kubernetes objects associated with the cluster.
 | // Create creates the new kubernetes objects associated with the cluster.
 | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import ( | ||||||
| 	"k8s.io/apimachinery/pkg/types" | 	"k8s.io/apimachinery/pkg/types" | ||||||
| 	"k8s.io/client-go/kubernetes/scheme" | 	"k8s.io/client-go/kubernetes/scheme" | ||||||
| 	"k8s.io/client-go/pkg/api/v1" | 	"k8s.io/client-go/pkg/api/v1" | ||||||
|  | 	rbac "k8s.io/client-go/pkg/apis/rbac/v1beta1" | ||||||
| 	"k8s.io/client-go/tools/cache" | 	"k8s.io/client-go/tools/cache" | ||||||
| 
 | 
 | ||||||
| 	"github.com/zalando-incubator/postgres-operator/pkg/apiserver" | 	"github.com/zalando-incubator/postgres-operator/pkg/apiserver" | ||||||
|  | @ -52,7 +53,8 @@ type Controller struct { | ||||||
| 
 | 
 | ||||||
| 	workerLogs map[uint32]ringlog.RingLogger | 	workerLogs map[uint32]ringlog.RingLogger | ||||||
| 
 | 
 | ||||||
| 	PodServiceAccount *v1.ServiceAccount | 	PodServiceAccount            *v1.ServiceAccount | ||||||
|  | 	PodServiceAccountRoleBinding *rbac.RoleBinding | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewController creates a new controller
 | // NewController creates a new controller
 | ||||||
|  | @ -158,7 +160,49 @@ func (c *Controller) initPodServiceAccount() { | ||||||
| 		c.PodServiceAccount.Namespace = "" | 		c.PodServiceAccount.Namespace = "" | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// actual service accounts are deployed at the time of Postgres/Spilo cluster creation
 | 	// 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
 | ||||||
|  | 	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.(*rbac.RoleBinding) | ||||||
|  | 		c.PodServiceAccountRoleBinding.Namespace = "" | ||||||
|  | 		c.PodServiceAccountRoleBinding.Subjects[0].Name = c.PodServiceAccount.Name | ||||||
|  | 		c.logger.Info("successfully parsed") | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// actual service accounts and roles bindings are deployed at the time of Postgres/Spilo cluster creation
 | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Controller) initController() { | func (c *Controller) initController() { | ||||||
|  |  | ||||||
|  | @ -74,19 +74,20 @@ type Config struct { | ||||||
| 	// default name `operator` enables backward compatibility with the older ServiceAccountName field
 | 	// default name `operator` enables backward compatibility with the older ServiceAccountName field
 | ||||||
| 	PodServiceAccountName string `name:"pod_service_account_name" default:"operator"` | 	PodServiceAccountName string `name:"pod_service_account_name" default:"operator"` | ||||||
| 	// value of this string must be valid JSON or YAML; see initPodServiceAccount
 | 	// value of this string must be valid JSON or YAML; see initPodServiceAccount
 | ||||||
| 	PodServiceAccountDefinition string `name:"pod_service_account_definition" default:""` | 	PodServiceAccountDefinition            string `name:"pod_service_account_definition" default:""` | ||||||
| 	DbHostedZone                string `name:"db_hosted_zone" default:"db.example.com"` | 	PodServiceAccountRoleBindingDefinition string `name:"pod_service_account_role_binding" default:""` | ||||||
| 	EtcdScope                   string `name:"etcd_scope" default:"service"` | 	DbHostedZone                           string `name:"db_hosted_zone" default:"db.example.com"` | ||||||
| 	WALES3Bucket                string `name:"wal_s3_bucket"` | 	EtcdScope                              string `name:"etcd_scope" default:"service"` | ||||||
| 	LogS3Bucket                 string `name:"log_s3_bucket"` | 	WALES3Bucket                           string `name:"wal_s3_bucket"` | ||||||
| 	KubeIAMRole                 string `name:"kube_iam_role"` | 	LogS3Bucket                            string `name:"log_s3_bucket"` | ||||||
| 	DebugLogging                bool   `name:"debug_logging" default:"true"` | 	KubeIAMRole                            string `name:"kube_iam_role"` | ||||||
| 	EnableDBAccess              bool   `name:"enable_database_access" default:"true"` | 	DebugLogging                           bool   `name:"debug_logging" default:"true"` | ||||||
| 	EnableTeamsAPI              bool   `name:"enable_teams_api" default:"true"` | 	EnableDBAccess                         bool   `name:"enable_database_access" default:"true"` | ||||||
| 	EnableTeamSuperuser         bool   `name:"enable_team_superuser" default:"false"` | 	EnableTeamsAPI                         bool   `name:"enable_teams_api" default:"true"` | ||||||
| 	TeamAdminRole               string `name:"team_admin_role" default:"admin"` | 	EnableTeamSuperuser                    bool   `name:"enable_team_superuser" default:"false"` | ||||||
| 	EnableMasterLoadBalancer    bool   `name:"enable_master_load_balancer" default:"true"` | 	TeamAdminRole                          string `name:"team_admin_role" default:"admin"` | ||||||
| 	EnableReplicaLoadBalancer   bool   `name:"enable_replica_load_balancer" default:"false"` | 	EnableMasterLoadBalancer               bool   `name:"enable_master_load_balancer" default:"true"` | ||||||
|  | 	EnableReplicaLoadBalancer              bool   `name:"enable_replica_load_balancer" default:"false"` | ||||||
| 	// deprecated and kept for backward compatibility
 | 	// deprecated and kept for backward compatibility
 | ||||||
| 	EnableLoadBalancer       *bool             `name:"enable_load_balancer"` | 	EnableLoadBalancer       *bool             `name:"enable_load_balancer"` | ||||||
| 	MasterDNSNameFormat      stringTemplate    `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` | 	MasterDNSNameFormat      stringTemplate    `name:"master_dns_name_format" default:"{cluster}.{team}.{hostedzone}"` | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/zalando-incubator/postgres-operator/pkg/util/constants" | ||||||
| 	apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" | 	apiextclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" | ||||||
| 	apiextbeta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" | 	apiextbeta1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1beta1" | ||||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||||
|  | @ -13,13 +14,12 @@ import ( | ||||||
| 	"k8s.io/client-go/kubernetes/typed/apps/v1beta1" | 	"k8s.io/client-go/kubernetes/typed/apps/v1beta1" | ||||||
| 	v1core "k8s.io/client-go/kubernetes/typed/core/v1" | 	v1core "k8s.io/client-go/kubernetes/typed/core/v1" | ||||||
| 	policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1" | 	policyv1beta1 "k8s.io/client-go/kubernetes/typed/policy/v1beta1" | ||||||
|  | 	rbacv1beta1 "k8s.io/client-go/kubernetes/typed/rbac/v1beta1" | ||||||
| 	"k8s.io/client-go/pkg/api" | 	"k8s.io/client-go/pkg/api" | ||||||
| 	"k8s.io/client-go/pkg/api/v1" | 	"k8s.io/client-go/pkg/api/v1" | ||||||
| 	policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" | 	policybeta1 "k8s.io/client-go/pkg/apis/policy/v1beta1" | ||||||
| 	"k8s.io/client-go/rest" | 	"k8s.io/client-go/rest" | ||||||
| 	"k8s.io/client-go/tools/clientcmd" | 	"k8s.io/client-go/tools/clientcmd" | ||||||
| 
 |  | ||||||
| 	"github.com/zalando-incubator/postgres-operator/pkg/util/constants" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // KubernetesClient describes getters for Kubernetes objects
 | // KubernetesClient describes getters for Kubernetes objects
 | ||||||
|  | @ -35,6 +35,8 @@ type KubernetesClient struct { | ||||||
| 	v1core.NamespacesGetter | 	v1core.NamespacesGetter | ||||||
| 	v1core.ServiceAccountsGetter | 	v1core.ServiceAccountsGetter | ||||||
| 	v1beta1.StatefulSetsGetter | 	v1beta1.StatefulSetsGetter | ||||||
|  | 
 | ||||||
|  | 	rbacv1beta1.RoleBindingsGetter | ||||||
| 	policyv1beta1.PodDisruptionBudgetsGetter | 	policyv1beta1.PodDisruptionBudgetsGetter | ||||||
| 	apiextbeta1.CustomResourceDefinitionsGetter | 	apiextbeta1.CustomResourceDefinitionsGetter | ||||||
| 
 | 
 | ||||||
|  | @ -83,6 +85,7 @@ func NewFromConfig(cfg *rest.Config) (KubernetesClient, error) { | ||||||
| 	kubeClient.StatefulSetsGetter = client.AppsV1beta1() | 	kubeClient.StatefulSetsGetter = client.AppsV1beta1() | ||||||
| 	kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1beta1() | 	kubeClient.PodDisruptionBudgetsGetter = client.PolicyV1beta1() | ||||||
| 	kubeClient.RESTClient = client.CoreV1().RESTClient() | 	kubeClient.RESTClient = client.CoreV1().RESTClient() | ||||||
|  | 	kubeClient.RoleBindingsGetter = client.RbacV1beta1() | ||||||
| 
 | 
 | ||||||
| 	cfg2 := *cfg | 	cfg2 := *cfg | ||||||
| 	cfg2.GroupVersion = &schema.GroupVersion{ | 	cfg2.GroupVersion = &schema.GroupVersion{ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue