mirror of https://github.com/k3s-io/k3s
Remove BoundServiceAccountTokenVolume
parent
a92b46730e
commit
5a36c793a4
|
@ -68,14 +68,6 @@ func validateTokenRequest(options *ServerRunOptions) []error {
|
||||||
errs = append(errs, errors.New("the TokenRequest feature is not enabled but --service-account-signing-key-file, --service-account-issuer and/or --api-audiences flags were passed"))
|
errs = append(errs, errors.New("the TokenRequest feature is not enabled but --service-account-signing-key-file, --service-account-issuer and/or --api-audiences flags were passed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) && !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) {
|
|
||||||
errs = append(errs, errors.New("the BoundServiceAccountTokenVolume feature depends on the TokenRequest feature, but the TokenRequest features is not enabled"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !enableAttempted && utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) {
|
|
||||||
errs = append(errs, errors.New("--service-account-signing-key-file and --service-account-issuer are required flags"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if enableAttempted && !enableSucceeded {
|
if enableAttempted && !enableSucceeded {
|
||||||
errs = append(errs, errors.New("--service-account-signing-key-file, --service-account-issuer, and --api-audiences should be specified together"))
|
errs = append(errs, errors.New("--service-account-signing-key-file, --service-account-issuer, and --api-audiences should be specified together"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,10 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
||||||
kubeoptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
|
kubeoptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options"
|
||||||
"k8s.io/kubernetes/pkg/controller/certificates/approver"
|
"k8s.io/kubernetes/pkg/controller/certificates/approver"
|
||||||
"k8s.io/kubernetes/pkg/controller/certificates/cleaner"
|
"k8s.io/kubernetes/pkg/controller/certificates/cleaner"
|
||||||
"k8s.io/kubernetes/pkg/controller/certificates/rootcacertpublisher"
|
|
||||||
"k8s.io/kubernetes/pkg/controller/certificates/signer"
|
"k8s.io/kubernetes/pkg/controller/certificates/signer"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startCSRSigningController(ctx ControllerContext) (http.Handler, bool, error) {
|
func startCSRSigningController(ctx ControllerContext) (http.Handler, bool, error) {
|
||||||
|
@ -125,31 +122,5 @@ func startCSRCleanerController(ctx ControllerContext) (http.Handler, bool, error
|
||||||
}
|
}
|
||||||
|
|
||||||
func startRootCACertPublisher(ctx ControllerContext) (http.Handler, bool, error) {
|
func startRootCACertPublisher(ctx ControllerContext) (http.Handler, bool, error) {
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) {
|
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rootCA []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if ctx.ComponentConfig.SAController.RootCAFile != "" {
|
|
||||||
if rootCA, err = readCA(ctx.ComponentConfig.SAController.RootCAFile); err != nil {
|
|
||||||
return nil, true, fmt.Errorf("error parsing root-ca-file at %s: %v", ctx.ComponentConfig.SAController.RootCAFile, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rootCA = ctx.ClientBuilder.ConfigOrDie("root-ca-cert-publisher").CAData
|
|
||||||
}
|
|
||||||
|
|
||||||
sac, err := rootcacertpublisher.NewPublisher(
|
|
||||||
ctx.InformerFactory.Core().V1().ConfigMaps(),
|
|
||||||
ctx.InformerFactory.Core().V1().Namespaces(),
|
|
||||||
ctx.ClientBuilder.ClientOrDie("root-ca-cert-publisher"),
|
|
||||||
rootCA,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, true, fmt.Errorf("error creating root CA certificate publisher: %v", err)
|
|
||||||
}
|
|
||||||
go sac.Run(1, ctx.Stop)
|
|
||||||
return nil, true, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["publisher.go"],
|
|
||||||
importpath = "k8s.io/kubernetes/pkg/controller/certificates/rootcacertpublisher",
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/controller:go_default_library",
|
|
||||||
"//pkg/util/metrics:go_default_library",
|
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
|
|
||||||
"//vendor/k8s.io/klog:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:public"],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["publisher_test.go"],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/controller:go_default_library",
|
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
|
||||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -1,222 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package rootcacertpublisher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
|
||||||
apierrs "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"
|
|
||||||
"k8s.io/kubernetes/pkg/controller"
|
|
||||||
"k8s.io/kubernetes/pkg/util/metrics"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootCACertConfigMapName is name of the configmap which stores certificates
|
|
||||||
// to access api-server
|
|
||||||
const RootCACertConfigMapName = "kube-root-ca.crt"
|
|
||||||
|
|
||||||
// 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, rootCA []byte) (*Publisher, error) {
|
|
||||||
e := &Publisher{
|
|
||||||
client: cl,
|
|
||||||
rootCA: rootCA,
|
|
||||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "root_ca_cert_publisher"),
|
|
||||||
}
|
|
||||||
if cl.CoreV1().RESTClient().GetRateLimiter() != nil {
|
|
||||||
if err := metrics.RegisterMetricAndTrackRateLimiterUsage("root_ca_cert_publisher", cl.CoreV1().RESTClient().GetRateLimiter()); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
rootCA []byte
|
|
||||||
|
|
||||||
// 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 root CA certificate configmap publisher")
|
|
||||||
defer klog.Infof("Shutting down root CA certificate configmap publisher")
|
|
||||||
|
|
||||||
if !controller.WaitForCacheSync("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 != RootCACertConfigMapName {
|
|
||||||
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 != RootCACertConfigMapName {
|
|
||||||
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) error {
|
|
||||||
startTime := time.Now()
|
|
||||||
defer func() {
|
|
||||||
klog.V(4).Infof("Finished syncing namespace %q (%v)", ns, time.Since(startTime))
|
|
||||||
}()
|
|
||||||
|
|
||||||
cm, err := c.cmLister.ConfigMaps(ns).Get(RootCACertConfigMapName)
|
|
||||||
switch {
|
|
||||||
case apierrs.IsNotFound(err):
|
|
||||||
_, err := c.client.CoreV1().ConfigMaps(ns).Create(&v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: RootCACertConfigMapName,
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"ca.crt": string(c.rootCA),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
case err != nil:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]string{
|
|
||||||
"ca.crt": string(c.rootCA),
|
|
||||||
}
|
|
||||||
|
|
||||||
if reflect.DeepEqual(cm.Data, data) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cm.Data = data
|
|
||||||
|
|
||||||
_, err = c.client.CoreV1().ConfigMaps(ns).Update(cm)
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2018 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package rootcacertpublisher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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
|
|
||||||
fakeRootCA := []byte("fake-root-ca")
|
|
||||||
|
|
||||||
caConfigMap := defaultCrtConfigMapPtr(fakeRootCA)
|
|
||||||
addFieldCM := defaultCrtConfigMapPtr(fakeRootCA)
|
|
||||||
addFieldCM.Data["test"] = "test"
|
|
||||||
modifyFieldCM := defaultCrtConfigMapPtr([]byte("abc"))
|
|
||||||
otherConfigMap := &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "other",
|
|
||||||
Namespace: ns,
|
|
||||||
ResourceVersion: "1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
updateOtherConfigMap := &v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "other",
|
|
||||||
Namespace: ns,
|
|
||||||
ResourceVersion: "1",
|
|
||||||
},
|
|
||||||
Data: map[string]string{"aa": "bb"},
|
|
||||||
}
|
|
||||||
|
|
||||||
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 namesapce": {
|
|
||||||
AddedNamespace: newNs,
|
|
||||||
ExpectActions: []action{{verb: "create", name: RootCACertConfigMapName}},
|
|
||||||
},
|
|
||||||
"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: RootCACertConfigMapName}},
|
|
||||||
},
|
|
||||||
"update ca configmap with adding field": {
|
|
||||||
ExistingConfigMaps: []*v1.ConfigMap{caConfigMap},
|
|
||||||
UpdatedConfigMap: addFieldCM,
|
|
||||||
ExpectActions: []action{{verb: "update", name: RootCACertConfigMapName}},
|
|
||||||
},
|
|
||||||
"update ca configmap with modifying field": {
|
|
||||||
ExistingConfigMaps: []*v1.ConfigMap{caConfigMap},
|
|
||||||
UpdatedConfigMap: modifyFieldCM,
|
|
||||||
ExpectActions: []action{{verb: "update", name: RootCACertConfigMapName}},
|
|
||||||
},
|
|
||||||
"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, fakeRootCA)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error creating ServiceAccounts 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(rootCA []byte) *v1.ConfigMap {
|
|
||||||
tmp := v1.ConfigMap{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: RootCACertConfigMapName,
|
|
||||||
},
|
|
||||||
Data: map[string]string{
|
|
||||||
"ca.crt": string(rootCA),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tmp.Namespace = metav1.NamespaceDefault
|
|
||||||
return &tmp
|
|
||||||
}
|
|
|
@ -215,14 +215,6 @@ const (
|
||||||
// Enable ServiceAccountTokenVolumeProjection support in ProjectedVolumes.
|
// Enable ServiceAccountTokenVolumeProjection support in ProjectedVolumes.
|
||||||
TokenRequestProjection utilfeature.Feature = "TokenRequestProjection"
|
TokenRequestProjection utilfeature.Feature = "TokenRequestProjection"
|
||||||
|
|
||||||
// owner: @mikedanese
|
|
||||||
// alpha: v1.13
|
|
||||||
//
|
|
||||||
// Migrate ServiceAccount volumes to use a projected volume consisting of a
|
|
||||||
// ServiceAccountTokenVolumeProjection. This feature adds new required flags
|
|
||||||
// to the API server.
|
|
||||||
BoundServiceAccountTokenVolume utilfeature.Feature = "BoundServiceAccountTokenVolume"
|
|
||||||
|
|
||||||
// owner: @Random-Liu
|
// owner: @Random-Liu
|
||||||
// beta: v1.11
|
// beta: v1.11
|
||||||
//
|
//
|
||||||
|
@ -378,7 +370,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||||
ScheduleDaemonSetPods: {Default: true, PreRelease: utilfeature.Beta},
|
ScheduleDaemonSetPods: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
TokenRequest: {Default: true, PreRelease: utilfeature.Beta},
|
TokenRequest: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
TokenRequestProjection: {Default: true, PreRelease: utilfeature.Beta},
|
TokenRequestProjection: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
BoundServiceAccountTokenVolume: {Default: false, PreRelease: utilfeature.Alpha},
|
|
||||||
CRIContainerLogRotation: {Default: true, PreRelease: utilfeature.Beta},
|
CRIContainerLogRotation: {Default: true, PreRelease: utilfeature.Beta},
|
||||||
cloudfeatures.GCERegionalPersistentDisk: {Default: true, PreRelease: utilfeature.GA},
|
cloudfeatures.GCERegionalPersistentDisk: {Default: true, PreRelease: utilfeature.GA},
|
||||||
CSIMigration: {Default: false, PreRelease: utilfeature.Alpha},
|
CSIMigration: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
|
|
|
@ -17,7 +17,6 @@ limitations under the License.
|
||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -30,9 +29,7 @@ import (
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
||||||
cliflag "k8s.io/component-base/cli/flag"
|
cliflag "k8s.io/component-base/cli/flag"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
|
||||||
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
||||||
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||||
)
|
)
|
||||||
|
@ -173,18 +170,6 @@ func (s *BuiltInAuthenticationOptions) Validate() []error {
|
||||||
allErrors = append(allErrors, fmt.Errorf("service-account-issuer contained a ':' but was not a valid URL: %v", err))
|
allErrors = append(allErrors, fmt.Errorf("service-account-issuer contained a ':' but was not a valid URL: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.ServiceAccounts != nil && utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) {
|
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) || !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequestProjection) {
|
|
||||||
allErrors = append(allErrors, errors.New("If the BoundServiceAccountTokenVolume feature is enabled,"+
|
|
||||||
" the TokenRequest and TokenRequestProjection features must also be enabled"))
|
|
||||||
}
|
|
||||||
if len(s.ServiceAccounts.Issuer) == 0 {
|
|
||||||
allErrors = append(allErrors, errors.New("service-account-issuer is a required flag when BoundServiceAccountTokenVolume is enabled"))
|
|
||||||
}
|
|
||||||
if len(s.ServiceAccounts.KeyFiles) == 0 {
|
|
||||||
allErrors = append(allErrors, errors.New("service-account-key-file is a required flag when BoundServiceAccountTokenVolume is enabled"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allErrors
|
return allErrors
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
@ -39,7 +38,6 @@ import (
|
||||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
kubefeatures "k8s.io/kubernetes/pkg/features"
|
|
||||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -431,30 +429,23 @@ func (s *serviceAccount) mountServiceAccountToken(serviceAccount *corev1.Service
|
||||||
|
|
||||||
// Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
|
// Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
|
||||||
tokenVolumeName := ""
|
tokenVolumeName := ""
|
||||||
hasTokenVolume := false
|
|
||||||
allVolumeNames := sets.NewString()
|
allVolumeNames := sets.NewString()
|
||||||
for _, volume := range pod.Spec.Volumes {
|
for _, volume := range pod.Spec.Volumes {
|
||||||
allVolumeNames.Insert(volume.Name)
|
allVolumeNames.Insert(volume.Name)
|
||||||
if (!s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken) ||
|
if volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken {
|
||||||
(s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && strings.HasPrefix(volume.Name, ServiceAccountVolumeName+"-")) {
|
|
||||||
tokenVolumeName = volume.Name
|
tokenVolumeName = volume.Name
|
||||||
hasTokenVolume = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine a volume name for the ServiceAccountTokenSecret in case we need it
|
// Determine a volume name for the ServiceAccountTokenSecret in case we need it
|
||||||
if len(tokenVolumeName) == 0 {
|
if len(tokenVolumeName) == 0 {
|
||||||
if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
|
|
||||||
tokenVolumeName = s.generateName(ServiceAccountVolumeName + "-")
|
|
||||||
} else {
|
|
||||||
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed
|
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed
|
||||||
tokenVolumeName = serviceAccountToken
|
tokenVolumeName = serviceAccountToken
|
||||||
if allVolumeNames.Has(tokenVolumeName) {
|
if allVolumeNames.Has(tokenVolumeName) {
|
||||||
tokenVolumeName = s.generateName(fmt.Sprintf("%s-", serviceAccountToken))
|
tokenVolumeName = s.generateName(fmt.Sprintf("%s-", serviceAccountToken))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Create the prototypical VolumeMount
|
// Create the prototypical VolumeMount
|
||||||
volumeMount := api.VolumeMount{
|
volumeMount := api.VolumeMount{
|
||||||
|
@ -495,56 +486,13 @@ func (s *serviceAccount) mountServiceAccountToken(serviceAccount *corev1.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the volume if a container needs it
|
// Add the volume if a container needs it
|
||||||
if !hasTokenVolume && needsTokenVolume {
|
if needsTokenVolume {
|
||||||
pod.Spec.Volumes = append(pod.Spec.Volumes, s.createVolume(tokenVolumeName, serviceAccountToken))
|
pod.Spec.Volumes = append(pod.Spec.Volumes, s.createVolume(tokenVolumeName, serviceAccountToken))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *serviceAccount) createVolume(tokenVolumeName, secretName string) api.Volume {
|
func (s *serviceAccount) createVolume(tokenVolumeName, secretName string) api.Volume {
|
||||||
if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
|
|
||||||
return api.Volume{
|
|
||||||
Name: tokenVolumeName,
|
|
||||||
VolumeSource: api.VolumeSource{
|
|
||||||
Projected: &api.ProjectedVolumeSource{
|
|
||||||
Sources: []api.VolumeProjection{
|
|
||||||
{
|
|
||||||
ServiceAccountToken: &api.ServiceAccountTokenProjection{
|
|
||||||
Path: "token",
|
|
||||||
ExpirationSeconds: 60 * 60,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return api.Volume{
|
return api.Volume{
|
||||||
Name: tokenVolumeName,
|
Name: tokenVolumeName,
|
||||||
VolumeSource: api.VolumeSource{
|
VolumeSource: api.VolumeSource{
|
||||||
|
|
|
@ -347,16 +347,6 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) {
|
|
||||||
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "root-ca-cert-publisher"},
|
|
||||||
Rules: []rbacv1.PolicyRule{
|
|
||||||
rbacv1helpers.NewRule("create", "update").Groups(legacyGroup).Resources("configmaps").RuleOrDie(),
|
|
||||||
eventsRule(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return controllerRoles, controllerRoleBindings
|
return controllerRoles, controllerRoleBindings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue