From 751741df59a7ce127cc028ae3c1bba773f012e34 Mon Sep 17 00:00:00 2001 From: Monis Khan Date: Tue, 15 Aug 2017 13:32:38 -0400 Subject: [PATCH] Allow injection of policy in RBAC post start hook This change allows the RBAC PostStartHook logic to be reused with different policy data when bootstrapping the cluster. Thus any changes to the bootstrap logic are separated from the policy data. Signed-off-by: Monis Khan --- pkg/registry/rbac/rest/storage_rbac.go | 223 +++++++++++++------------ 1 file changed, 119 insertions(+), 104 deletions(-) diff --git a/pkg/registry/rbac/rest/storage_rbac.go b/pkg/registry/rbac/rest/storage_rbac.go index f38e38c0984..200bdd55c3c 100644 --- a/pkg/registry/rbac/rest/storage_rbac.go +++ b/pkg/registry/rbac/rest/storage_rbac.go @@ -134,99 +134,54 @@ func (p RESTStorageProvider) storage(version schema.GroupVersion, apiResourceCon } func (p RESTStorageProvider) PostStartHook() (string, genericapiserver.PostStartHookFunc, error) { - return PostStartHookName, PostStartHook, nil + policy := &PolicyData{ + ClusterRoles: append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...), + ClusterRoleBindings: append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...), + Roles: bootstrappolicy.NamespaceRoles(), + RoleBindings: bootstrappolicy.NamespaceRoleBindings(), + } + return PostStartHookName, policy.EnsureRBACPolicy(), nil } -func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { - // intializing roles is really important. On some e2e runs, we've seen cases where etcd is down when the server - // starts, the roles don't initialize, and nothing works. - err := wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) { +type PolicyData struct { + ClusterRoles []rbac.ClusterRole + ClusterRoleBindings []rbac.ClusterRoleBinding + Roles map[string][]rbac.Role + RoleBindings map[string][]rbac.RoleBinding +} - coreclientset, err := coreclient.NewForConfig(hookContext.LoopbackClientConfig) - if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err)) - return false, nil - } +func (p *PolicyData) EnsureRBACPolicy() genericapiserver.PostStartHookFunc { + return func(hookContext genericapiserver.PostStartHookContext) error { + // intializing roles is really important. On some e2e runs, we've seen cases where etcd is down when the server + // starts, the roles don't initialize, and nothing works. + err := wait.Poll(1*time.Second, 30*time.Second, func() (done bool, err error) { - clientset, err := rbacclient.NewForConfig(hookContext.LoopbackClientConfig) - if err != nil { - utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err)) - return false, nil - } - // Make sure etcd is responding before we start reconciling - if _, err := clientset.ClusterRoles().List(metav1.ListOptions{}); err != nil { - utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err)) - return false, nil - } - if _, err := clientset.ClusterRoleBindings().List(metav1.ListOptions{}); err != nil { - utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err)) - return false, nil - } - - // ensure bootstrap roles are created or reconciled - for _, clusterRole := range append(bootstrappolicy.ClusterRoles(), bootstrappolicy.ControllerRoles()...) { - opts := reconciliation.ReconcileRoleOptions{ - Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: &clusterRole}, - Client: reconciliation.ClusterRoleModifier{Client: clientset.ClusterRoles()}, - Confirm: true, - } - err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - result, err := opts.Run() - if err != nil { - return err - } - switch { - case result.Protected && result.Operation != reconciliation.ReconcileNone: - glog.Warningf("skipped reconcile-protected clusterrole.%s/%s with missing permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules) - case result.Operation == reconciliation.ReconcileUpdate: - glog.Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules) - case result.Operation == reconciliation.ReconcileCreate: - glog.Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name) - } - return nil - }) + coreclientset, err := coreclient.NewForConfig(hookContext.LoopbackClientConfig) if err != nil { - // don't fail on failures, try to create as many as you can - utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrole.%s/%s: %v", rbac.GroupName, clusterRole.Name, err)) + utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err)) + return false, nil } - } - // ensure bootstrap rolebindings are created or reconciled - for _, clusterRoleBinding := range append(bootstrappolicy.ClusterRoleBindings(), bootstrappolicy.ControllerRoleBindings()...) { - opts := reconciliation.ReconcileRoleBindingOptions{ - RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: &clusterRoleBinding}, - Client: reconciliation.ClusterRoleBindingClientAdapter{Client: clientset.ClusterRoleBindings()}, - Confirm: true, - } - err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { - result, err := opts.Run() - if err != nil { - return err - } - switch { - case result.Protected && result.Operation != reconciliation.ReconcileNone: - glog.Warningf("skipped reconcile-protected clusterrolebinding.%s/%s with missing subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects) - case result.Operation == reconciliation.ReconcileUpdate: - glog.Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects) - case result.Operation == reconciliation.ReconcileCreate: - glog.Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name) - case result.Operation == reconciliation.ReconcileRecreate: - glog.Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name) - } - return nil - }) + clientset, err := rbacclient.NewForConfig(hookContext.LoopbackClientConfig) if err != nil { - // don't fail on failures, try to create as many as you can - utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrolebinding.%s/%s: %v", rbac.GroupName, clusterRoleBinding.Name, err)) + utilruntime.HandleError(fmt.Errorf("unable to initialize client: %v", err)) + return false, nil + } + // Make sure etcd is responding before we start reconciling + if _, err := clientset.ClusterRoles().List(metav1.ListOptions{}); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to initialize clusterroles: %v", err)) + return false, nil + } + if _, err := clientset.ClusterRoleBindings().List(metav1.ListOptions{}); err != nil { + utilruntime.HandleError(fmt.Errorf("unable to initialize clusterrolebindings: %v", err)) + return false, nil } - } - // ensure bootstrap namespaced roles are created or reconciled - for namespace, roles := range bootstrappolicy.NamespaceRoles() { - for _, role := range roles { + // ensure bootstrap roles are created or reconciled + for _, clusterRole := range p.ClusterRoles { opts := reconciliation.ReconcileRoleOptions{ - Role: reconciliation.RoleRuleOwner{Role: &role}, - Client: reconciliation.RoleModifier{Client: clientset, NamespaceClient: coreclientset.Namespaces()}, + Role: reconciliation.ClusterRoleRuleOwner{ClusterRole: &clusterRole}, + Client: reconciliation.ClusterRoleModifier{Client: clientset.ClusterRoles()}, Confirm: true, } err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { @@ -236,27 +191,25 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { } switch { case result.Protected && result.Operation != reconciliation.ReconcileNone: - glog.Warningf("skipped reconcile-protected role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + glog.Warningf("skipped reconcile-protected clusterrole.%s/%s with missing permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules) case result.Operation == reconciliation.ReconcileUpdate: - glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + glog.Infof("updated clusterrole.%s/%s with additional permissions: %v", rbac.GroupName, clusterRole.Name, result.MissingRules) case result.Operation == reconciliation.ReconcileCreate: - glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.Name, namespace) + glog.Infof("created clusterrole.%s/%s", rbac.GroupName, clusterRole.Name) } return nil }) if err != nil { // don't fail on failures, try to create as many as you can - utilruntime.HandleError(fmt.Errorf("unable to reconcile role.%s/%s in %v: %v", rbac.GroupName, role.Name, namespace, err)) + utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrole.%s/%s: %v", rbac.GroupName, clusterRole.Name, err)) } } - } - // ensure bootstrap namespaced rolebindings are created or reconciled - for namespace, roleBindings := range bootstrappolicy.NamespaceRoleBindings() { - for _, roleBinding := range roleBindings { + // ensure bootstrap rolebindings are created or reconciled + for _, clusterRoleBinding := range p.ClusterRoleBindings { opts := reconciliation.ReconcileRoleBindingOptions{ - RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: &roleBinding}, - Client: reconciliation.RoleBindingClientAdapter{Client: clientset, NamespaceClient: coreclientset.Namespaces()}, + RoleBinding: reconciliation.ClusterRoleBindingAdapter{ClusterRoleBinding: &clusterRoleBinding}, + Client: reconciliation.ClusterRoleBindingClientAdapter{Client: clientset.ClusterRoleBindings()}, Confirm: true, } err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { @@ -266,31 +219,93 @@ func PostStartHook(hookContext genericapiserver.PostStartHookContext) error { } switch { case result.Protected && result.Operation != reconciliation.ReconcileNone: - glog.Warningf("skipped reconcile-protected rolebinding.%s/%s in %v with missing subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + glog.Warningf("skipped reconcile-protected clusterrolebinding.%s/%s with missing subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects) case result.Operation == reconciliation.ReconcileUpdate: - glog.Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + glog.Infof("updated clusterrolebinding.%s/%s with additional subjects: %v", rbac.GroupName, clusterRoleBinding.Name, result.MissingSubjects) case result.Operation == reconciliation.ReconcileCreate: - glog.Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + glog.Infof("created clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name) case result.Operation == reconciliation.ReconcileRecreate: - glog.Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + glog.Infof("recreated clusterrolebinding.%s/%s", rbac.GroupName, clusterRoleBinding.Name) } return nil }) if err != nil { // don't fail on failures, try to create as many as you can - utilruntime.HandleError(fmt.Errorf("unable to reconcile rolebinding.%s/%s in %v: %v", rbac.GroupName, roleBinding.Name, namespace, err)) + utilruntime.HandleError(fmt.Errorf("unable to reconcile clusterrolebinding.%s/%s: %v", rbac.GroupName, clusterRoleBinding.Name, err)) } } + + // ensure bootstrap namespaced roles are created or reconciled + for namespace, roles := range p.Roles { + for _, role := range roles { + opts := reconciliation.ReconcileRoleOptions{ + Role: reconciliation.RoleRuleOwner{Role: &role}, + Client: reconciliation.RoleModifier{Client: clientset, NamespaceClient: coreclientset.Namespaces()}, + Confirm: true, + } + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + result, err := opts.Run() + if err != nil { + return err + } + switch { + case result.Protected && result.Operation != reconciliation.ReconcileNone: + glog.Warningf("skipped reconcile-protected role.%s/%s in %v with missing permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + case result.Operation == reconciliation.ReconcileUpdate: + glog.Infof("updated role.%s/%s in %v with additional permissions: %v", rbac.GroupName, role.Name, namespace, result.MissingRules) + case result.Operation == reconciliation.ReconcileCreate: + glog.Infof("created role.%s/%s in %v ", rbac.GroupName, role.Name, namespace) + } + return nil + }) + if err != nil { + // don't fail on failures, try to create as many as you can + utilruntime.HandleError(fmt.Errorf("unable to reconcile role.%s/%s in %v: %v", rbac.GroupName, role.Name, namespace, err)) + } + } + } + + // ensure bootstrap namespaced rolebindings are created or reconciled + for namespace, roleBindings := range p.RoleBindings { + for _, roleBinding := range roleBindings { + opts := reconciliation.ReconcileRoleBindingOptions{ + RoleBinding: reconciliation.RoleBindingAdapter{RoleBinding: &roleBinding}, + Client: reconciliation.RoleBindingClientAdapter{Client: clientset, NamespaceClient: coreclientset.Namespaces()}, + Confirm: true, + } + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + result, err := opts.Run() + if err != nil { + return err + } + switch { + case result.Protected && result.Operation != reconciliation.ReconcileNone: + glog.Warningf("skipped reconcile-protected rolebinding.%s/%s in %v with missing subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + case result.Operation == reconciliation.ReconcileUpdate: + glog.Infof("updated rolebinding.%s/%s in %v with additional subjects: %v", rbac.GroupName, roleBinding.Name, namespace, result.MissingSubjects) + case result.Operation == reconciliation.ReconcileCreate: + glog.Infof("created rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + case result.Operation == reconciliation.ReconcileRecreate: + glog.Infof("recreated rolebinding.%s/%s in %v", rbac.GroupName, roleBinding.Name, namespace) + } + return nil + }) + if err != nil { + // don't fail on failures, try to create as many as you can + utilruntime.HandleError(fmt.Errorf("unable to reconcile rolebinding.%s/%s in %v: %v", rbac.GroupName, roleBinding.Name, namespace, err)) + } + } + } + + return true, nil + }) + // if we're never able to make it through intialization, kill the API server + if err != nil { + return fmt.Errorf("unable to initialize roles: %v", err) } - return true, nil - }) - // if we're never able to make it through intialization, kill the API server - if err != nil { - return fmt.Errorf("unable to initialize roles: %v", err) + return nil } - - return nil } func (p RESTStorageProvider) GroupName() string {