2019-08-30 18:33:25 +00:00
/ *
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 (
2020-03-26 21:07:15 +00:00
"context"
2019-08-30 18:33:25 +00:00
"fmt"
"reflect"
"time"
2019-09-27 21:51:53 +00:00
v1 "k8s.io/api/core/v1"
2020-03-26 21:07:15 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2019-08-30 18:33:25 +00:00
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"
2019-12-12 01:27:03 +00:00
"k8s.io/component-base/metrics/prometheus/ratelimiter"
2020-08-10 17:43:49 +00:00
"k8s.io/klog/v2"
2019-08-30 18:33:25 +00:00
)
// RootCACertConfigMapName is name of the configmap which stores certificates
// to access api-server
2021-07-02 08:43:15 +00:00
const (
RootCACertConfigMapName = "kube-root-ca.crt"
DescriptionAnnotation = "kubernetes.io/description"
Description = "Contains a CA bundle that can be used to verify the kube-apiserver when using internal endpoints such as the internal service IP or kubernetes.default.svc. " +
"No other usage is guaranteed across distributions of Kubernetes clusters."
)
2019-08-30 18:33:25 +00:00
2021-03-18 22:40:29 +00:00
func init ( ) {
registerMetrics ( )
}
2019-08-30 18:33:25 +00:00
// 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 {
2019-12-12 01:27:03 +00:00
if err := ratelimiter . RegisterMetricAndTrackRateLimiterUsage ( "root_ca_cert_publisher" , cl . CoreV1 ( ) . RESTClient ( ) . GetRateLimiter ( ) ) ; err != nil {
2019-08-30 18:33:25 +00:00
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" )
2019-09-27 21:51:53 +00:00
if ! cache . WaitForNamedCacheSync ( "crt configmap" , stopCh , c . cmListerSynced ) {
2019-08-30 18:33:25 +00:00
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
}
2021-03-18 22:40:29 +00:00
func ( c * Publisher ) syncNamespace ( ns string ) ( err error ) {
2019-08-30 18:33:25 +00:00
startTime := time . Now ( )
defer func ( ) {
2021-03-18 22:40:29 +00:00
recordMetrics ( startTime , ns , err )
2019-08-30 18:33:25 +00:00
klog . V ( 4 ) . Infof ( "Finished syncing namespace %q (%v)" , ns , time . Since ( startTime ) )
} ( )
cm , err := c . cmLister . ConfigMaps ( ns ) . Get ( RootCACertConfigMapName )
switch {
2020-03-26 21:07:15 +00:00
case apierrors . IsNotFound ( err ) :
2021-03-18 22:40:29 +00:00
_ , err = c . client . CoreV1 ( ) . ConfigMaps ( ns ) . Create ( context . TODO ( ) , & v1 . ConfigMap {
2019-08-30 18:33:25 +00:00
ObjectMeta : metav1 . ObjectMeta {
2021-07-02 08:43:15 +00:00
Name : RootCACertConfigMapName ,
Annotations : map [ string ] string { DescriptionAnnotation : Description } ,
2019-08-30 18:33:25 +00:00
} ,
Data : map [ string ] string {
"ca.crt" : string ( c . rootCA ) ,
} ,
2020-03-26 21:07:15 +00:00
} , metav1 . CreateOptions { } )
2020-12-01 01:06:26 +00:00
// 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
}
2019-08-30 18:33:25 +00:00
return err
case err != nil :
return err
}
data := map [ string ] string {
"ca.crt" : string ( c . rootCA ) ,
}
2021-07-02 08:43:15 +00:00
// ensure the data and the one annotation describing usage of this configmap match.
if reflect . DeepEqual ( cm . Data , data ) && len ( cm . Annotations [ DescriptionAnnotation ] ) > 0 {
2019-08-30 18:33:25 +00:00
return nil
}
2020-12-01 01:06:26 +00:00
// copy so we don't modify the cache's instance of the configmap
cm = cm . DeepCopy ( )
2019-08-30 18:33:25 +00:00
cm . Data = data
2021-07-02 08:43:15 +00:00
if cm . Annotations == nil {
cm . Annotations = map [ string ] string { }
}
cm . Annotations [ DescriptionAnnotation ] = Description
2019-08-30 18:33:25 +00:00
2020-03-26 21:07:15 +00:00
_ , err = c . client . CoreV1 ( ) . ConfigMaps ( ns ) . Update ( context . TODO ( ) , cm , metav1 . UpdateOptions { } )
2019-08-30 18:33:25 +00:00
return err
}
func convertToCM ( obj interface { } ) ( * v1 . ConfigMap , error ) {
cm , ok := obj . ( * v1 . ConfigMap )
if ! ok {
tombstone , ok := obj . ( cache . DeletedFinalStateUnknown )
if ! ok {
2019-09-27 21:51:53 +00:00
return nil , fmt . Errorf ( "couldn't get object from tombstone %#v" , obj )
2019-08-30 18:33:25 +00:00
}
cm , ok = tombstone . Obj . ( * v1 . ConfigMap )
if ! ok {
2019-09-27 21:51:53 +00:00
return nil , fmt . Errorf ( "tombstone contained object that is not a ConfigMap %#v" , obj )
2019-08-30 18:33:25 +00:00
}
}
return cm , nil
}