diff --git a/cmd/kube-controller-manager/app/certificates.go b/cmd/kube-controller-manager/app/certificates.go index 062454b243b27..cd2d0c98ba292 100644 --- a/cmd/kube-controller-manager/app/certificates.go +++ b/cmd/kube-controller-manager/app/certificates.go @@ -31,6 +31,7 @@ import ( "k8s.io/controller-manager/controller" "k8s.io/klog/v2" "k8s.io/kubernetes/cmd/kube-controller-manager/names" + "k8s.io/kubernetes/openshift-kube-controller-manager/servicecacertpublisher" "k8s.io/kubernetes/pkg/controller/certificates/approver" "k8s.io/kubernetes/pkg/controller/certificates/cleaner" ctbpublisher "k8s.io/kubernetes/pkg/controller/certificates/clustertrustbundlepublisher" @@ -298,3 +299,24 @@ func getKubeAPIServerCAFileContents(controllerContext ControllerContext) ([]byte return rootCA, nil } + +func newServiceCACertPublisher() *ControllerDescriptor { + return &ControllerDescriptor{ + name: names.ServiceCACertificatePublisherController, + aliases: []string{"service-ca-cert-publisher"}, + initFunc: startServiceCACertPublisher, + } +} + +func startServiceCACertPublisher(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) { + sac, err := servicecacertpublisher.NewPublisher( + controllerContext.InformerFactory.Core().V1().ConfigMaps(), + controllerContext.InformerFactory.Core().V1().Namespaces(), + controllerContext.ClientBuilder.ClientOrDie("service-ca-cert-publisher"), + ) + if err != nil { + return nil, true, fmt.Errorf("error creating service CA certificate publisher: %v", err) + } + go sac.Run(1, ctx.Done()) + return nil, true, nil +} diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index f5c73db7b8ef7..9a05925fc8199 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -133,7 +133,7 @@ controller, and serviceaccounts controller.`, return err } cliflag.PrintFlags(cmd.Flags()) - + if err := SetUpPreferredHostForOpenShift(s); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) @@ -508,9 +508,7 @@ func ControllersDisabledByDefault() []string { controllersDisabledByDefault = append(controllersDisabledByDefault, name) } } - sort.Strings(controllersDisabledByDefault) - return controllersDisabledByDefault } @@ -596,6 +594,7 @@ func NewControllerDescriptors() map[string]*ControllerDescriptor { register(newTTLAfterFinishedControllerDescriptor()) register(newRootCACertificatePublisherControllerDescriptor()) register(newKubeAPIServerSignerClusterTrustBundledPublisherDescriptor()) + register(newServiceCACertPublisher()) register(newEphemeralVolumeControllerDescriptor()) // feature gated diff --git a/cmd/kube-controller-manager/app/controllermanager_test.go b/cmd/kube-controller-manager/app/controllermanager_test.go index 88af44ed6d6fa..66bb3c5fcabfc 100644 --- a/cmd/kube-controller-manager/app/controllermanager_test.go +++ b/cmd/kube-controller-manager/app/controllermanager_test.go @@ -90,6 +90,7 @@ func TestControllerNamesDeclaration(t *testing.T) { names.TTLAfterFinishedController, names.RootCACertificatePublisherController, names.KubeAPIServerClusterTrustBundlePublisherController, + names.ServiceCACertificatePublisherController, names.EphemeralVolumeController, names.StorageVersionGarbageCollectorController, names.ResourceClaimController, diff --git a/cmd/kube-controller-manager/names/controller_names.go b/cmd/kube-controller-manager/names/controller_names.go index db88935c4c791..79d2f4c56062d 100644 --- a/cmd/kube-controller-manager/names/controller_names.go +++ b/cmd/kube-controller-manager/names/controller_names.go @@ -77,6 +77,7 @@ const ( PersistentVolumeProtectionController = "persistentvolume-protection-controller" TTLAfterFinishedController = "ttl-after-finished-controller" RootCACertificatePublisherController = "root-ca-certificate-publisher-controller" + ServiceCACertificatePublisherController = "service-ca-certificate-publisher-controller" KubeAPIServerClusterTrustBundlePublisherController = "kube-apiserver-serving-clustertrustbundle-publisher-controller" EphemeralVolumeController = "ephemeral-volume-controller" StorageVersionGarbageCollectorController = "storageversion-garbage-collector-controller" diff --git a/openshift-kube-controller-manager/servicecacertpublisher/metrics.go b/openshift-kube-controller-manager/servicecacertpublisher/metrics.go new file mode 100644 index 0000000000000..e6867784043a8 --- /dev/null +++ b/openshift-kube-controller-manager/servicecacertpublisher/metrics.go @@ -0,0 +1,56 @@ +package servicecacertpublisher + +import ( + "strconv" + "sync" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +// ServiceCACertPublisher - subsystem name used by service_ca_cert_publisher +const ServiceCACertPublisher = "service_ca_cert_publisher" + +var ( + syncCounter = metrics.NewCounterVec( + &metrics.CounterOpts{ + Subsystem: ServiceCACertPublisher, + Name: "sync_total", + Help: "Number of namespace syncs happened in service ca cert publisher.", + StabilityLevel: metrics.ALPHA, + }, + []string{"code"}, + ) + syncLatency = metrics.NewHistogramVec( + &metrics.HistogramOpts{ + Subsystem: ServiceCACertPublisher, + Name: "sync_duration_seconds", + Help: "Number of namespace syncs happened in service ca cert publisher.", + Buckets: metrics.ExponentialBuckets(0.001, 2, 15), + StabilityLevel: metrics.ALPHA, + }, + []string{"code"}, + ) +) + +func recordMetrics(start time.Time, ns string, err error) { + code := "500" + if err == nil { + code = "200" + } else if se, ok := err.(*apierrors.StatusError); ok && se.Status().Code != 0 { + code = strconv.Itoa(int(se.Status().Code)) + } + syncLatency.WithLabelValues(code).Observe(time.Since(start).Seconds()) + syncCounter.WithLabelValues(code).Inc() +} + +var once sync.Once + +func registerMetrics() { + once.Do(func() { + legacyregistry.MustRegister(syncCounter) + legacyregistry.MustRegister(syncLatency) + }) +} diff --git a/openshift-kube-controller-manager/servicecacertpublisher/metrics_test.go b/openshift-kube-controller-manager/servicecacertpublisher/metrics_test.go new file mode 100644 index 0000000000000..75f7297e3ff59 --- /dev/null +++ b/openshift-kube-controller-manager/servicecacertpublisher/metrics_test.go @@ -0,0 +1,81 @@ +package servicecacertpublisher + +import ( + "errors" + "strings" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/component-base/metrics/testutil" +) + +func TestSyncCounter(t *testing.T) { + testCases := []struct { + desc string + err error + metrics []string + want string + }{ + { + desc: "nil error", + err: nil, + metrics: []string{ + "service_ca_cert_publisher_sync_total", + }, + want: ` +# HELP service_ca_cert_publisher_sync_total [ALPHA] Number of namespace syncs happened in service ca cert publisher. +# TYPE service_ca_cert_publisher_sync_total counter +service_ca_cert_publisher_sync_total{code="200"} 1 + `, + }, + { + desc: "kube api error", + err: apierrors.NewNotFound(corev1.Resource("configmap"), "test-configmap"), + metrics: []string{ + "service_ca_cert_publisher_sync_total", + }, + want: ` +# HELP service_ca_cert_publisher_sync_total [ALPHA] Number of namespace syncs happened in service ca cert publisher. +# TYPE service_ca_cert_publisher_sync_total counter +service_ca_cert_publisher_sync_total{code="404"} 1 + `, + }, + { + desc: "kube api error without code", + err: &apierrors.StatusError{}, + metrics: []string{ + "service_ca_cert_publisher_sync_total", + }, + want: ` +# HELP service_ca_cert_publisher_sync_total [ALPHA] Number of namespace syncs happened in service ca cert publisher. +# TYPE service_ca_cert_publisher_sync_total counter +service_ca_cert_publisher_sync_total{code="500"} 1 + `, + }, + { + desc: "general error", + err: errors.New("test"), + metrics: []string{ + "service_ca_cert_publisher_sync_total", + }, + want: ` +# HELP service_ca_cert_publisher_sync_total [ALPHA] Number of namespace syncs happened in service ca cert publisher. +# TYPE service_ca_cert_publisher_sync_total counter +service_ca_cert_publisher_sync_total{code="500"} 1 + `, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + recordMetrics(time.Now(), "test-ns", tc.err) + defer syncCounter.Reset() + if err := testutil.GatherAndCompare(legacyregistry.DefaultGatherer, strings.NewReader(tc.want), tc.metrics...); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/openshift-kube-controller-manager/servicecacertpublisher/publisher.go b/openshift-kube-controller-manager/servicecacertpublisher/publisher.go new file mode 100644 index 0000000000000..823724eae097e --- /dev/null +++ b/openshift-kube-controller-manager/servicecacertpublisher/publisher.go @@ -0,0 +1,216 @@ +package servicecacertpublisher + +import ( + "context" + "fmt" + "reflect" + "time" + + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + coreinformers "k8s.io/client-go/informers/core/v1" + clientset "k8s.io/client-go/kubernetes" + corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" +) + +// ServiceCACertConfigMapName is name of the configmap which stores certificates +// to validate service serving certificates issued by the service ca operator. +const ServiceCACertConfigMapName = "openshift-service-ca.crt" + +func init() { + registerMetrics() +} + +// NewPublisher construct a new controller which would manage the configmap +// which stores certificates in each namespace. It will make sure certificate +// configmap exists in each namespace. +func NewPublisher(cmInformer coreinformers.ConfigMapInformer, nsInformer coreinformers.NamespaceInformer, cl clientset.Interface) (*Publisher, error) { + e := &Publisher{ + client: cl, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "service_ca_cert_publisher"), + } + + cmInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + DeleteFunc: e.configMapDeleted, + UpdateFunc: e.configMapUpdated, + }) + e.cmLister = cmInformer.Lister() + e.cmListerSynced = cmInformer.Informer().HasSynced + + nsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: e.namespaceAdded, + UpdateFunc: e.namespaceUpdated, + }) + e.nsListerSynced = nsInformer.Informer().HasSynced + + e.syncHandler = e.syncNamespace + + return e, nil +} + +// Publisher manages certificate ConfigMap objects inside Namespaces +type Publisher struct { + client clientset.Interface + + // To allow injection for testing. + syncHandler func(key string) error + + cmLister corelisters.ConfigMapLister + cmListerSynced cache.InformerSynced + + nsListerSynced cache.InformerSynced + + queue workqueue.RateLimitingInterface +} + +// Run starts process +func (c *Publisher) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Infof("Starting service CA certificate configmap publisher") + defer klog.Infof("Shutting down service CA certificate configmap publisher") + + if !cache.WaitForNamedCacheSync("crt configmap", stopCh, c.cmListerSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *Publisher) configMapDeleted(obj interface{}) { + cm, err := convertToCM(obj) + if err != nil { + utilruntime.HandleError(err) + return + } + if cm.Name != ServiceCACertConfigMapName { + return + } + c.queue.Add(cm.Namespace) +} + +func (c *Publisher) configMapUpdated(_, newObj interface{}) { + cm, err := convertToCM(newObj) + if err != nil { + utilruntime.HandleError(err) + return + } + if cm.Name != ServiceCACertConfigMapName { + return + } + c.queue.Add(cm.Namespace) +} + +func (c *Publisher) namespaceAdded(obj interface{}) { + namespace := obj.(*v1.Namespace) + c.queue.Add(namespace.Name) +} + +func (c *Publisher) namespaceUpdated(oldObj interface{}, newObj interface{}) { + newNamespace := newObj.(*v1.Namespace) + if newNamespace.Status.Phase != v1.NamespaceActive { + return + } + c.queue.Add(newNamespace.Name) +} + +func (c *Publisher) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when +// it's time to quit. +func (c *Publisher) processNextWorkItem() bool { + key, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(key) + + if err := c.syncHandler(key.(string)); err != nil { + utilruntime.HandleError(fmt.Errorf("syncing %q failed: %v", key, err)) + c.queue.AddRateLimited(key) + return true + } + + c.queue.Forget(key) + return true +} + +func (c *Publisher) syncNamespace(ns string) (err error) { + startTime := time.Now() + defer func() { + recordMetrics(startTime, ns, err) + klog.V(4).Infof("Finished syncing namespace %q (%v)", ns, time.Since(startTime)) + }() + + annotations := map[string]string{ + // This annotation prompts the service ca operator to inject + // the service ca bundle into the configmap. + "service.beta.openshift.io/inject-cabundle": "true", + } + + cm, err := c.cmLister.ConfigMaps(ns).Get(ServiceCACertConfigMapName) + switch { + case apierrors.IsNotFound(err): + _, err = c.client.CoreV1().ConfigMaps(ns).Create(context.TODO(), &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceCACertConfigMapName, + Annotations: annotations, + }, + // Create new configmaps with the field referenced by the default + // projected volume. This ensures that pods - including the pod for + // service ca operator - will be able to start during initial + // deployment before the service ca operator has responded to the + // injection annotation. + Data: map[string]string{ + "service-ca.crt": "", + }, + }, metav1.CreateOptions{}) + // don't retry a create if the namespace doesn't exist or is terminating + if apierrors.IsNotFound(err) || apierrors.HasStatusCause(err, v1.NamespaceTerminatingCause) { + return nil + } + return err + case err != nil: + return err + } + + if reflect.DeepEqual(cm.Annotations, annotations) { + return nil + } + + // copy so we don't modify the cache's instance of the configmap + cm = cm.DeepCopy() + cm.Annotations = annotations + + _, err = c.client.CoreV1().ConfigMaps(ns).Update(context.TODO(), cm, metav1.UpdateOptions{}) + return err +} + +func convertToCM(obj interface{}) (*v1.ConfigMap, error) { + cm, ok := obj.(*v1.ConfigMap) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + return nil, fmt.Errorf("couldn't get object from tombstone %#v", obj) + } + cm, ok = tombstone.Obj.(*v1.ConfigMap) + if !ok { + return nil, fmt.Errorf("tombstone contained object that is not a ConfigMap %#v", obj) + } + } + return cm, nil +} diff --git a/openshift-kube-controller-manager/servicecacertpublisher/publisher_test.go b/openshift-kube-controller-manager/servicecacertpublisher/publisher_test.go new file mode 100644 index 0000000000000..23373a555e304 --- /dev/null +++ b/openshift-kube-controller-manager/servicecacertpublisher/publisher_test.go @@ -0,0 +1,161 @@ +package servicecacertpublisher + +import ( + "reflect" + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + "k8s.io/kubernetes/pkg/controller" +) + +func TestConfigMapCreation(t *testing.T) { + ns := metav1.NamespaceDefault + + caConfigMap := defaultCrtConfigMapPtr() + addAnnotationCM := defaultCrtConfigMapPtr() + addAnnotationCM.Annotations["test"] = "test" + modifyAnnotationCM := defaultCrtConfigMapPtr() + modifyAnnotationCM.Annotations["service.beta.openshift.io/inject-cabundle"] = "no" + otherConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other", + Namespace: ns, + ResourceVersion: "1", + }, + } + updateOtherConfigMap := &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "other", + Namespace: ns, + ResourceVersion: "1", + Annotations: map[string]string{"test": "true"}, + }, + } + + existNS := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: ns}, + Status: v1.NamespaceStatus{ + Phase: v1.NamespaceActive, + }, + } + newNs := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "new"}, + Status: v1.NamespaceStatus{ + Phase: v1.NamespaceActive, + }, + } + terminatingNS := &v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: ns}, + Status: v1.NamespaceStatus{ + Phase: v1.NamespaceTerminating, + }, + } + + type action struct { + verb string + name string + } + testcases := map[string]struct { + ExistingConfigMaps []*v1.ConfigMap + AddedNamespace *v1.Namespace + UpdatedNamespace *v1.Namespace + DeletedConfigMap *v1.ConfigMap + UpdatedConfigMap *v1.ConfigMap + ExpectActions []action + }{ + "create new namespace": { + AddedNamespace: newNs, + ExpectActions: []action{{verb: "create", name: ServiceCACertConfigMapName}}, + }, + "delete other configmap": { + ExistingConfigMaps: []*v1.ConfigMap{otherConfigMap, caConfigMap}, + DeletedConfigMap: otherConfigMap, + }, + "delete ca configmap": { + ExistingConfigMaps: []*v1.ConfigMap{otherConfigMap, caConfigMap}, + DeletedConfigMap: caConfigMap, + ExpectActions: []action{{verb: "create", name: ServiceCACertConfigMapName}}, + }, + "update ca configmap with adding annotation": { + ExistingConfigMaps: []*v1.ConfigMap{caConfigMap}, + UpdatedConfigMap: addAnnotationCM, + ExpectActions: []action{{verb: "update", name: ServiceCACertConfigMapName}}, + }, + "update ca configmap with modifying annotation": { + ExistingConfigMaps: []*v1.ConfigMap{caConfigMap}, + UpdatedConfigMap: modifyAnnotationCM, + ExpectActions: []action{{verb: "update", name: ServiceCACertConfigMapName}}, + }, + "update with other configmap": { + ExistingConfigMaps: []*v1.ConfigMap{caConfigMap, otherConfigMap}, + UpdatedConfigMap: updateOtherConfigMap, + }, + "update namespace with terminating state": { + UpdatedNamespace: terminatingNS, + }, + } + + for k, tc := range testcases { + t.Run(k, func(t *testing.T) { + client := fake.NewSimpleClientset(caConfigMap, existNS) + informers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), controller.NoResyncPeriodFunc()) + cmInformer := informers.Core().V1().ConfigMaps() + nsInformer := informers.Core().V1().Namespaces() + controller, err := NewPublisher(cmInformer, nsInformer, client) + if err != nil { + t.Fatalf("error creating controller: %v", err) + } + + cmStore := cmInformer.Informer().GetStore() + + controller.syncHandler = controller.syncNamespace + + for _, s := range tc.ExistingConfigMaps { + cmStore.Add(s) + } + + if tc.AddedNamespace != nil { + controller.namespaceAdded(tc.AddedNamespace) + } + if tc.UpdatedNamespace != nil { + controller.namespaceUpdated(nil, tc.UpdatedNamespace) + } + + if tc.DeletedConfigMap != nil { + cmStore.Delete(tc.DeletedConfigMap) + controller.configMapDeleted(tc.DeletedConfigMap) + } + + if tc.UpdatedConfigMap != nil { + cmStore.Add(tc.UpdatedConfigMap) + controller.configMapUpdated(nil, tc.UpdatedConfigMap) + } + + for controller.queue.Len() != 0 { + controller.processNextWorkItem() + } + + actions := client.Actions() + if reflect.DeepEqual(actions, tc.ExpectActions) { + t.Errorf("Unexpected actions:\n%s", diff.ObjectGoPrintDiff(actions, tc.ExpectActions)) + } + }) + } +} + +func defaultCrtConfigMapPtr() *v1.ConfigMap { + tmp := v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: ServiceCACertConfigMapName, + Annotations: map[string]string{ + "service.beta.openshift.io/inject-cabundle": "true", + }, + }, + } + tmp.Namespace = metav1.NamespaceDefault + return &tmp +} diff --git a/plugin/pkg/admission/serviceaccount/admission.go b/plugin/pkg/admission/serviceaccount/admission.go index 3f4338128e53c..b42c536b685ba 100644 --- a/plugin/pkg/admission/serviceaccount/admission.go +++ b/plugin/pkg/admission/serviceaccount/admission.go @@ -519,6 +519,19 @@ func TokenVolumeSource() *api.ProjectedVolumeSource { }, }, }, + { + ConfigMap: &api.ConfigMapProjection{ + LocalObjectReference: api.LocalObjectReference{ + Name: "openshift-service-ca.crt", + }, + Items: []api.KeyToPath{ + { + Key: "service-ca.crt", + Path: "service-ca.crt", + }, + }, + }, + }, }, } } diff --git a/plugin/pkg/admission/serviceaccount/admission_test.go b/plugin/pkg/admission/serviceaccount/admission_test.go index 01b08da455f47..42b330309a558 100644 --- a/plugin/pkg/admission/serviceaccount/admission_test.go +++ b/plugin/pkg/admission/serviceaccount/admission_test.go @@ -199,6 +199,7 @@ func TestAssignsDefaultServiceAccountAndBoundTokenWithNoSecretTokens(t *testing. {ServiceAccountToken: &api.ServiceAccountTokenProjection{ExpirationSeconds: 3607, Path: "token"}}, {ConfigMap: &api.ConfigMapProjection{LocalObjectReference: api.LocalObjectReference{Name: "kube-root-ca.crt"}, Items: []api.KeyToPath{{Key: "ca.crt", Path: "ca.crt"}}}}, {DownwardAPI: &api.DownwardAPIProjection{Items: []api.DownwardAPIVolumeFile{{Path: "namespace", FieldRef: &api.ObjectFieldSelector{APIVersion: "v1", FieldPath: "metadata.namespace"}}}}}, + {ConfigMap: &api.ConfigMapProjection{LocalObjectReference: api.LocalObjectReference{Name: "openshift-service-ca.crt"}, Items: []api.KeyToPath{{Key: "service-ca.crt", Path: "service-ca.crt"}}}}, }, DefaultMode: utilpointer.Int32(0644), }, diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go index 5da84f7d7f843..dd066b48dba7b 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/controller_policy.go @@ -467,6 +467,13 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding) }) } + addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "service-ca-cert-publisher"}, + Rules: []rbacv1.PolicyRule{ + rbacv1helpers.NewRule("create", "update").Groups(legacyGroup).Resources("configmaps").RuleOrDie(), + eventsRule(), + }, + }) addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "validatingadmissionpolicy-status-controller"}, Rules: []rbacv1.PolicyRule{ diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml index 5b7cf3d4644b4..5e3521686e6da 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-role-bindings.yaml @@ -476,6 +476,23 @@ items: - kind: ServiceAccount name: service-account-controller namespace: kube-system +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:service-ca-cert-publisher + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:controller:service-ca-cert-publisher + subjects: + - kind: ServiceAccount + name: service-ca-cert-publisher + namespace: kube-system - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: diff --git a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml index 0a4f236377525..279e91be8d6c2 100644 --- a/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml +++ b/plugin/pkg/auth/authorizer/rbac/bootstrappolicy/testdata/controller-roles.yaml @@ -1339,6 +1339,32 @@ items: - create - patch - update +- apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + annotations: + rbac.authorization.kubernetes.io/autoupdate: "true" + creationTimestamp: null + labels: + kubernetes.io/bootstrapping: rbac-defaults + name: system:controller:service-ca-cert-publisher + rules: + - apiGroups: + - "" + resources: + - configmaps + verbs: + - create + - update + - apiGroups: + - "" + - events.k8s.io + resources: + - events + verbs: + - create + - patch + - update - apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: