wire up a means to dynamically reload ca bundles for kube-apiserver

This commit is contained in:
David Eads 2019-10-07 14:06:42 -04:00
parent b0c272e1fb
commit 6beb96261e
24 changed files with 438 additions and 105 deletions

View File

@ -118,10 +118,10 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/request/x509:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/healthz:go_default_library", "//staging/src/k8s.io/apiserver/pkg/server/healthz:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",

View File

@ -24,9 +24,9 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory" "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/request/x509"
"k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory" "k8s.io/apiserver/pkg/authorization/authorizerfactory"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1" authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1beta1" authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
@ -64,15 +64,19 @@ func BuildAuth(nodeName types.NodeName, client clientset.Interface, config kubel
// BuildAuthn creates an authenticator compatible with the kubelet's needs // BuildAuthn creates an authenticator compatible with the kubelet's needs
func BuildAuthn(client authenticationclient.TokenReviewInterface, authn kubeletconfig.KubeletAuthentication) (authenticator.Request, error) { func BuildAuthn(client authenticationclient.TokenReviewInterface, authn kubeletconfig.KubeletAuthentication) (authenticator.Request, error) {
clientCertVerifier, err := x509.NewStaticVerifierFromFile(authn.X509.ClientCAFile) var clientCertificateCAContentProvider authenticatorfactory.CAContentProvider
var err error
if len(authn.X509.ClientCAFile) > 0 {
clientCertificateCAContentProvider, err = dynamiccertificates.NewDynamicCAContentFromFile("client-ca-bundle", authn.X509.ClientCAFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
}
authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{ authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{
Anonymous: authn.Anonymous.Enabled, Anonymous: authn.Anonymous.Enabled,
CacheTTL: authn.Webhook.CacheTTL.Duration, CacheTTL: authn.Webhook.CacheTTL.Duration,
ClientVerifyOptionFn: clientCertVerifier, ClientCertificateCAContentProvider: clientCertificateCAContentProvider,
} }
if authn.Webhook.Enabled { if authn.Webhook.Enabled {

View File

@ -24,6 +24,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/authentication/token/cache:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/token/cache:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/token/union:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/token/union:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile:go_default_library", "//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile:go_default_library",
"//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth:go_default_library", "//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth:go_default_library",

View File

@ -33,6 +33,7 @@ import (
tokencache "k8s.io/apiserver/pkg/authentication/token/cache" tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
"k8s.io/apiserver/pkg/authentication/token/tokenfile" "k8s.io/apiserver/pkg/authentication/token/tokenfile"
tokenunion "k8s.io/apiserver/pkg/authentication/token/union" tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile" "k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth" "k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
@ -77,10 +78,10 @@ type Config struct {
// TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig // TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
BootstrapTokenAuthenticator authenticator.Token BootstrapTokenAuthenticator authenticator.Token
// ClientVerifyOptionFn are the options for verifying incoming connections using mTLS and directly assigning to users. // ClientCAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
// Generally this is the CA bundle file used to authenticate client certificates // Generally this is the CA bundle file used to authenticate client certificates
// If this value is nil, then mutual TLS is disabled. // If this value is nil, then mutual TLS is disabled.
ClientVerifyOptionFn x509.VerifyOptionFunc ClientCAContentProvider dynamiccertificates.CAContentProvider
} }
// New returns an authenticator.Request or an error that supports the standard // New returns an authenticator.Request or an error that supports the standard
@ -94,7 +95,7 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
// Add the front proxy authenticator if requested // Add the front proxy authenticator if requested
if config.RequestHeaderConfig != nil { if config.RequestHeaderConfig != nil {
requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure( requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
config.RequestHeaderConfig.VerifyOptionFn, config.RequestHeaderConfig.CAContentProvider.VerifyOptions,
config.RequestHeaderConfig.AllowedClientNames, config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders, config.RequestHeaderConfig.UsernameHeaders,
config.RequestHeaderConfig.GroupHeaders, config.RequestHeaderConfig.GroupHeaders,
@ -120,8 +121,8 @@ func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, er
} }
// X509 methods // X509 methods
if config.ClientVerifyOptionFn != nil { if config.ClientCAContentProvider != nil {
certAuth := x509.NewDynamic(config.ClientVerifyOptionFn, x509.CommonNameUserConversion) certAuth := x509.NewDynamic(config.ClientCAContentProvider.VerifyOptions, x509.CommonNameUserConversion)
authenticators = append(authenticators, certAuth) authenticators = append(authenticators, certAuth)
} }

View File

@ -324,7 +324,7 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
if s.ClientCert != nil { if s.ClientCert != nil {
var err error var err error
ret.ClientVerifyOptionFn, err = s.ClientCert.GetClientVerifyOptionFn() ret.ClientCAContentProvider, err = s.ClientCert.GetClientCAContentProvider()
if err != nil { if err != nil {
return kubeauthenticator.Config{}, err return kubeauthenticator.Config{}, err
} }
@ -390,17 +390,26 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(c *genericapiserver.Config) error
return nil return nil
} }
var err error
if o.ClientCert != nil { if o.ClientCert != nil {
if err = c.Authentication.ApplyClientCert(o.ClientCert.ClientCA, c.SecureServing); err != nil { clientCertificateCAContentProvider, err := o.ClientCert.GetClientCAContentProvider()
if err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
if err = c.Authentication.ApplyClientCert(clientCertificateCAContentProvider, c.SecureServing); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err) return fmt.Errorf("unable to load client CA file: %v", err)
} }
} }
if o.RequestHeader != nil { if o.RequestHeader != nil {
if err = c.Authentication.ApplyClientCert(o.RequestHeader.ClientCAFile, c.SecureServing); err != nil { requestHeaderConfig, err := o.RequestHeader.ToAuthenticationRequestHeaderConfig()
if err != nil {
return fmt.Errorf("unable to create request header authentication config: %v", err)
}
if requestHeaderConfig != nil {
if err = c.Authentication.ApplyClientCert(requestHeaderConfig.CAContentProvider, c.SecureServing); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err) return fmt.Errorf("unable to load client CA file: %v", err)
} }
} }
}
c.Authentication.SupportsBasicAuth = o.PasswordFile != nil && len(o.PasswordFile.BasicAuthFile) > 0 c.Authentication.SupportsBasicAuth = o.PasswordFile != nil && len(o.PasswordFile.BasicAuthFile) > 0

View File

@ -146,7 +146,7 @@ func TestToAuthenticationConfig(t *testing.T) {
Anonymous: false, Anonymous: false,
BasicAuthFile: "/testBasicAuthFile", BasicAuthFile: "/testBasicAuthFile",
BootstrapToken: false, BootstrapToken: false,
ClientVerifyOptionFn: nil, // this is nil because you can't compare functions ClientCAContentProvider: nil, // this is nil because you can't compare functions
TokenAuthFile: "/testTokenFile", TokenAuthFile: "/testTokenFile",
OIDCIssuerURL: "testIssuerURL", OIDCIssuerURL: "testIssuerURL",
OIDCClientID: "testClientID", OIDCClientID: "testClientID",
@ -165,7 +165,7 @@ func TestToAuthenticationConfig(t *testing.T) {
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"}, UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"}, GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"}, ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
VerifyOptionFn: nil, // this is nil because you can't compare functions CAContentProvider: nil, // this is nil because you can't compare functions
AllowedClientNames: headerrequest.StaticStringSlice{"kube-aggregator"}, AllowedClientNames: headerrequest.StaticStringSlice{"kube-aggregator"},
}, },
} }
@ -176,14 +176,14 @@ func TestToAuthenticationConfig(t *testing.T) {
} }
// nil these out because you cannot compare pointers. Ensure they are non-nil first // nil these out because you cannot compare pointers. Ensure they are non-nil first
if resultConfig.ClientVerifyOptionFn == nil { if resultConfig.ClientCAContentProvider == nil {
t.Error("missing client verify") t.Error("missing client verify")
} }
if resultConfig.RequestHeaderConfig.VerifyOptionFn == nil { if resultConfig.RequestHeaderConfig.CAContentProvider == nil {
t.Error("missing requestheader verify") t.Error("missing requestheader verify")
} }
resultConfig.ClientVerifyOptionFn = nil resultConfig.ClientCAContentProvider = nil
resultConfig.RequestHeaderConfig.VerifyOptionFn = nil resultConfig.RequestHeaderConfig.CAContentProvider = nil
if !reflect.DeepEqual(resultConfig, expectConfig) { if !reflect.DeepEqual(resultConfig, expectConfig) {
t.Error(cmp.Diff(resultConfig, expectConfig)) t.Error(cmp.Diff(resultConfig, expectConfig))

View File

@ -30,7 +30,6 @@ import (
unionauth "k8s.io/apiserver/pkg/authentication/request/union" unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authentication/request/websocket" "k8s.io/apiserver/pkg/authentication/request/websocket"
"k8s.io/apiserver/pkg/authentication/request/x509" "k8s.io/apiserver/pkg/authentication/request/x509"
x509request "k8s.io/apiserver/pkg/authentication/request/x509"
"k8s.io/apiserver/pkg/authentication/token/cache" "k8s.io/apiserver/pkg/authentication/token/cache"
webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1" authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
@ -47,9 +46,10 @@ type DelegatingAuthenticatorConfig struct {
// CacheTTL is the length of time that a token authentication answer will be cached. // CacheTTL is the length of time that a token authentication answer will be cached.
CacheTTL time.Duration CacheTTL time.Duration
// ClientVerifyOptionFn are the options for verifying incoming connections using mTLS and directly assigning to users. // CAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
// Generally this is the CA bundle file used to authenticate client certificates // Generally this is the CA bundle file used to authenticate client certificates
ClientVerifyOptionFn x509request.VerifyOptionFunc // If this is nil, then mTLS will not be used.
ClientCertificateCAContentProvider CAContentProvider
APIAudiences authenticator.Audiences APIAudiences authenticator.Audiences
@ -64,7 +64,7 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
// Add the front proxy authenticator if requested // Add the front proxy authenticator if requested
if c.RequestHeaderConfig != nil { if c.RequestHeaderConfig != nil {
requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure( requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
c.RequestHeaderConfig.VerifyOptionFn, c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
c.RequestHeaderConfig.AllowedClientNames, c.RequestHeaderConfig.AllowedClientNames,
c.RequestHeaderConfig.UsernameHeaders, c.RequestHeaderConfig.UsernameHeaders,
c.RequestHeaderConfig.GroupHeaders, c.RequestHeaderConfig.GroupHeaders,
@ -74,8 +74,8 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
} }
// x509 client cert auth // x509 client cert auth
if c.ClientVerifyOptionFn != nil { if c.ClientCertificateCAContentProvider != nil {
authenticators = append(authenticators, x509.NewDynamic(c.ClientVerifyOptionFn, x509.CommonNameUserConversion)) authenticators = append(authenticators, x509.NewDynamic(c.ClientCertificateCAContentProvider.VerifyOptions, x509.CommonNameUserConversion))
} }
if c.TokenAccessReviewClient != nil { if c.TokenAccessReviewClient != nil {

View File

@ -17,8 +17,9 @@ limitations under the License.
package authenticatorfactory package authenticatorfactory
import ( import (
"crypto/x509"
"k8s.io/apiserver/pkg/authentication/request/headerrequest" "k8s.io/apiserver/pkg/authentication/request/headerrequest"
x509request "k8s.io/apiserver/pkg/authentication/request/x509"
) )
type RequestHeaderConfig struct { type RequestHeaderConfig struct {
@ -29,9 +30,19 @@ type RequestHeaderConfig struct {
// ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in // ExtraHeaderPrefixes are the head prefixes to check (case-insentively) for filling in
// the user.Info.Extra. All values of all matching headers will be added. // the user.Info.Extra. All values of all matching headers will be added.
ExtraHeaderPrefixes headerrequest.StringSliceProvider ExtraHeaderPrefixes headerrequest.StringSliceProvider
// VerifyOptionFn are the options for verifying incoming connections using mTLS. Generally this points to CA bundle file which is used verify the identity of the front proxy. // CAContentProvider the options for verifying incoming connections using mTLS. Generally this points to CA bundle file which is used verify the identity of the front proxy.
// It may produce different options at will. // It may produce different options at will.
VerifyOptionFn x509request.VerifyOptionFunc CAContentProvider CAContentProvider
// AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any. // AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any.
AllowedClientNames headerrequest.StringSliceProvider AllowedClientNames headerrequest.StringSliceProvider
} }
// CAContentProvider provides ca bundle byte content
type CAContentProvider interface {
// Name is just an identifier
Name() string
// CurrentCABundleContent provides ca bundle byte content
CurrentCABundleContent() []byte
// VerifyOptions provides VerifyOptions for authenticators
VerifyOptions() x509.VerifyOptions
}

View File

@ -345,21 +345,19 @@ func DefaultOpenAPIConfig(getDefinitions openapicommon.GetOpenAPIDefinitions, de
} }
} }
func (c *AuthenticationInfo) ApplyClientCert(clientCAFile string, servingInfo *SecureServingInfo) error { func (c *AuthenticationInfo) ApplyClientCert(clientCA dynamiccertificates.CAContentProvider, servingInfo *SecureServingInfo) error {
if servingInfo != nil { if servingInfo == nil {
if len(clientCAFile) > 0 { return nil
clientCAProvider, err := dynamiccertificates.NewStaticCAContentFromFile(clientCAFile) }
if err != nil { if clientCA == nil {
return fmt.Errorf("unable to load client CA file: %v", err) return nil
} }
if servingInfo.ClientCA == nil { if servingInfo.ClientCA == nil {
servingInfo.ClientCA = clientCAProvider servingInfo.ClientCA = clientCA
} else { return nil
servingInfo.ClientCA = dynamiccertificates.NewUnionCAContentProvider(servingInfo.ClientCA, clientCAProvider)
}
}
} }
servingInfo.ClientCA = dynamiccertificates.NewUnionCAContentProvider(servingInfo.ClientCA, clientCA)
return nil return nil
} }

View File

@ -17,6 +17,7 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",

View File

@ -18,6 +18,7 @@ package dynamiccertificates
import ( import (
"bytes" "bytes"
"crypto/x509"
) )
// CAContentProvider provides ca bundle byte content // CAContentProvider provides ca bundle byte content
@ -27,6 +28,8 @@ type CAContentProvider interface {
// CurrentCABundleContent provides ca bundle byte content. Errors can be contained to the controllers initializing // CurrentCABundleContent provides ca bundle byte content. Errors can be contained to the controllers initializing
// the value. By the time you get here, you should always be returning a value that won't fail. // the value. By the time you get here, you should always be returning a value that won't fail.
CurrentCABundleContent() []byte CurrentCABundleContent() []byte
// VerifyOptions provides VerifyOptions for authenticators
VerifyOptions() x509.VerifyOptions
} }
// dynamicCertificateContent holds the content that overrides the baseTLSConfig // dynamicCertificateContent holds the content that overrides the baseTLSConfig

View File

@ -17,10 +17,10 @@ limitations under the License.
package dynamiccertificates package dynamiccertificates
import ( import (
"bytes"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"reflect"
"sync/atomic" "sync/atomic"
"time" "time"
@ -32,10 +32,30 @@ import (
"k8s.io/klog" "k8s.io/klog"
) )
type CAListener interface { // FileRefreshDuration is exposed so that integration tests can crank up the reload speed.
var FileRefreshDuration = 1 * time.Minute
// Listener is an interface to use to notify interested parties of a change.
type Listener interface {
// Enqueue should be called when an input may have changed
Enqueue() Enqueue()
} }
// Notifier is a way to add listeners
type Notifier interface {
// AddListener is adds a listener to be notified of potential input changes
AddListener(listener Listener)
}
// ControllerRunner is a generic interface for starting a controller
type ControllerRunner interface {
// RunOnce runs the sync loop a single time. This useful for synchronous priming
RunOnce() error
// Run should be called a go .Run
Run(workers int, stopCh <-chan struct{})
}
// DynamicFileCAContent provies a CAContentProvider that can dynamically react to new file content // DynamicFileCAContent provies a CAContentProvider that can dynamically react to new file content
// It also fulfills the authenticator interface to provide verifyoptions // It also fulfills the authenticator interface to provide verifyoptions
type DynamicFileCAContent struct { type DynamicFileCAContent struct {
@ -47,18 +67,22 @@ type DynamicFileCAContent struct {
// caBundle is a caBundleAndVerifier that contains the last read, non-zero length content of the file // caBundle is a caBundleAndVerifier that contains the last read, non-zero length content of the file
caBundle atomic.Value caBundle atomic.Value
listeners []CAListener listeners []Listener
// queue only ever has one item, but it has nice error handling backoff/retry semantics // queue only ever has one item, but it has nice error handling backoff/retry semantics
queue workqueue.RateLimitingInterface queue workqueue.RateLimitingInterface
} }
var _ Notifier = &DynamicFileCAContent{}
var _ CAContentProvider = &DynamicFileCAContent{}
var _ ControllerRunner = &DynamicFileCAContent{}
type caBundleAndVerifier struct { type caBundleAndVerifier struct {
caBundle []byte caBundle []byte
verifyOptions x509.VerifyOptions verifyOptions x509.VerifyOptions
} }
// NewStaticCAContentFromFile returns a CAContentProvider based on a filename // NewDynamicCAContentFromFile returns a CAContentProvider based on a filename that automatically reloads content
func NewDynamicCAContentFromFile(purpose, filename string) (*DynamicFileCAContent, error) { func NewDynamicCAContentFromFile(purpose, filename string) (*DynamicFileCAContent, error) {
if len(filename) == 0 { if len(filename) == 0 {
return nil, fmt.Errorf("missing filename for ca bundle") return nil, fmt.Errorf("missing filename for ca bundle")
@ -78,7 +102,7 @@ func NewDynamicCAContentFromFile(purpose, filename string) (*DynamicFileCAConten
} }
// AddListener adds a listener to be notified when the CA content changes. // AddListener adds a listener to be notified when the CA content changes.
func (c *DynamicFileCAContent) AddListener(listener CAListener) { func (c *DynamicFileCAContent) AddListener(listener Listener) {
c.listeners = append(c.listeners, listener) c.listeners = append(c.listeners, listener)
} }
@ -93,8 +117,7 @@ func (c *DynamicFileCAContent) loadCABundle() error {
} }
// check to see if we have a change. If the values are the same, do nothing. // check to see if we have a change. If the values are the same, do nothing.
existing, ok := c.caBundle.Load().(*caBundleAndVerifier) if !c.hasCAChanged(caBundle) {
if ok && existing != nil && reflect.DeepEqual(existing.caBundle, caBundle) {
return nil return nil
} }
@ -111,6 +134,30 @@ func (c *DynamicFileCAContent) loadCABundle() error {
return nil return nil
} }
// hasCAChanged returns true if the caBundle is different than the current.
func (c *DynamicFileCAContent) hasCAChanged(caBundle []byte) bool {
uncastExisting := c.caBundle.Load()
if uncastExisting == nil {
return true
}
// check to see if we have a change. If the values are the same, do nothing.
existing, ok := uncastExisting.(*caBundleAndVerifier)
if !ok {
return true
}
if !bytes.Equal(existing.caBundle, caBundle) {
return true
}
return false
}
// RunOnce runs a single sync loop
func (c *DynamicFileCAContent) RunOnce() error {
return c.loadCABundle()
}
// Run starts the kube-apiserver and blocks until stopCh is closed. // Run starts the kube-apiserver and blocks until stopCh is closed.
func (c *DynamicFileCAContent) Run(workers int, stopCh <-chan struct{}) { func (c *DynamicFileCAContent) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash() defer utilruntime.HandleCrash()
@ -123,7 +170,7 @@ func (c *DynamicFileCAContent) Run(workers int, stopCh <-chan struct{}) {
go wait.Until(c.runWorker, time.Second, stopCh) go wait.Until(c.runWorker, time.Second, stopCh)
// start timer that rechecks every minute, just in case. this also serves to prime the controller quickly. // start timer that rechecks every minute, just in case. this also serves to prime the controller quickly.
_ = wait.PollImmediateUntil(1*time.Minute, func() (bool, error) { _ = wait.PollImmediateUntil(FileRefreshDuration, func() (bool, error) {
c.queue.Add(workItemKey) c.queue.Add(workItemKey)
return false, nil return false, nil
}, stopCh) }, stopCh)
@ -164,11 +211,12 @@ func (c *DynamicFileCAContent) Name() string {
// CurrentCABundleContent provides ca bundle byte content // CurrentCABundleContent provides ca bundle byte content
func (c *DynamicFileCAContent) CurrentCABundleContent() (cabundle []byte) { func (c *DynamicFileCAContent) CurrentCABundleContent() (cabundle []byte) {
return c.caBundle.Load().(caBundleAndVerifier).caBundle return c.caBundle.Load().(*caBundleAndVerifier).caBundle
} }
// VerifyOptions provides verifyoptions compatible with authenticators
func (c *DynamicFileCAContent) VerifyOptions() x509.VerifyOptions { func (c *DynamicFileCAContent) VerifyOptions() x509.VerifyOptions {
return c.caBundle.Load().(caBundleAndVerifier).verifyOptions return c.caBundle.Load().(*caBundleAndVerifier).verifyOptions
} }
// newVerifyOptions creates a new verification func from a file. It reads the content and then fails. // newVerifyOptions creates a new verification func from a file. It reads the content and then fails.

View File

@ -18,15 +18,18 @@ package dynamiccertificates
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
) )
type staticCAContent struct { type staticCAContent struct {
name string name string
caBundle []byte caBundle *caBundleAndVerifier
} }
var _ CAContentProvider = &staticCAContent{}
// NewStaticCAContentFromFile returns a CAContentProvider based on a filename // NewStaticCAContentFromFile returns a CAContentProvider based on a filename
func NewStaticCAContentFromFile(filename string) (CAContentProvider, error) { func NewStaticCAContentFromFile(filename string) (CAContentProvider, error) {
if len(filename) == 0 { if len(filename) == 0 {
@ -37,15 +40,20 @@ func NewStaticCAContentFromFile(filename string) (CAContentProvider, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewStaticCAContent(filename, caBundle), nil return NewStaticCAContent(filename, caBundle)
} }
// NewStaticCAContent returns a CAContentProvider that always returns the same value // NewStaticCAContent returns a CAContentProvider that always returns the same value
func NewStaticCAContent(name string, caBundle []byte) CAContentProvider { func NewStaticCAContent(name string, caBundle []byte) (CAContentProvider, error) {
caBundleAndVerifier, err := newCABundleAndVerifier(name, caBundle)
if err != nil {
return nil, err
}
return &staticCAContent{ return &staticCAContent{
name: name, name: name,
caBundle: caBundle, caBundle: caBundleAndVerifier,
} }, nil
} }
// Name is just an identifier // Name is just an identifier
@ -55,7 +63,11 @@ func (c *staticCAContent) Name() string {
// CurrentCABundleContent provides ca bundle byte content // CurrentCABundleContent provides ca bundle byte content
func (c *staticCAContent) CurrentCABundleContent() (cabundle []byte) { func (c *staticCAContent) CurrentCABundleContent() (cabundle []byte) {
return c.caBundle return c.caBundle.caBundle
}
func (c *staticCAContent) VerifyOptions() x509.VerifyOptions {
return c.caBundle.verifyOptions
} }
type staticCertKeyContent struct { type staticCertKeyContent struct {

View File

@ -60,6 +60,8 @@ type DynamicServingCertificateController struct {
eventRecorder events.EventRecorder eventRecorder events.EventRecorder
} }
var _ Listener = &DynamicServingCertificateController{}
// NewDynamicServingCertificateController returns a controller that can be used to keep a TLSConfig up to date. // NewDynamicServingCertificateController returns a controller that can be used to keep a TLSConfig up to date.
func NewDynamicServingCertificateController( func NewDynamicServingCertificateController(
baseTLSConfig tls.Config, baseTLSConfig tls.Config,

View File

@ -89,7 +89,7 @@ func TestNewStaticCertKeyContent(t *testing.T) {
}{ }{
{ {
name: "filled", name: "filled",
clientCA: NewStaticCAContent("test-ca", []byte("content-1")), clientCA: &staticCAContent{name: "test-ca", caBundle: &caBundleAndVerifier{caBundle: []byte("content-1")}},
servingCert: testCertProvider, servingCert: testCertProvider,
sniCerts: []SNICertKeyContentProvider{testCertProvider}, sniCerts: []SNICertKeyContentProvider{testCertProvider},
expected: &dynamicCertificateContent{ expected: &dynamicCertificateContent{
@ -101,7 +101,7 @@ func TestNewStaticCertKeyContent(t *testing.T) {
}, },
{ {
name: "missingCA", name: "missingCA",
clientCA: NewStaticCAContent("test-ca", []byte("")), clientCA: &staticCAContent{name: "test-ca", caBundle: &caBundleAndVerifier{caBundle: []byte("")}},
expected: nil, expected: nil,
expectedErr: `not loading an empty client ca bundle from "test-ca"`, expectedErr: `not loading an empty client ca bundle from "test-ca"`,
}, },

View File

@ -18,11 +18,18 @@ package dynamiccertificates
import ( import (
"bytes" "bytes"
"crypto/x509"
"strings" "strings"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
) )
type unionCAContent []CAContentProvider type unionCAContent []CAContentProvider
var _ Notifier = &unionCAContent{}
var _ CAContentProvider = &unionCAContent{}
var _ ControllerRunner = &unionCAContent{}
// NewUnionCAContentProvider returns a CAContentProvider that is a union of other CAContentProviders // NewUnionCAContentProvider returns a CAContentProvider that is a union of other CAContentProviders
func NewUnionCAContentProvider(caContentProviders ...CAContentProvider) CAContentProvider { func NewUnionCAContentProvider(caContentProviders ...CAContentProvider) CAContentProvider {
return unionCAContent(caContentProviders) return unionCAContent(caContentProviders)
@ -46,3 +53,48 @@ func (c unionCAContent) CurrentCABundleContent() []byte {
return bytes.Join(caBundles, []byte("\n")) return bytes.Join(caBundles, []byte("\n"))
} }
// CurrentCABundleContent provides ca bundle byte content
func (c unionCAContent) VerifyOptions() x509.VerifyOptions {
// TODO make more efficient. This isn't actually used in any of our mainline paths. It's called to build the TLSConfig
// TODO on file changes, but the actual authentication runs against the individual items, not the union.
ret, err := newCABundleAndVerifier(c.Name(), c.CurrentCABundleContent())
if err != nil {
// because we're made up of already vetted values, this indicates some kind of coding error
panic(err)
}
return ret.verifyOptions
}
// AddListener adds a listener to be notified when the CA content changes.
func (c unionCAContent) AddListener(listener Listener) {
for _, curr := range c {
if notifier, ok := curr.(Notifier); ok {
notifier.AddListener(listener)
}
}
}
// AddListener adds a listener to be notified when the CA content changes.
func (c unionCAContent) RunOnce() error {
errors := []error{}
for _, curr := range c {
if controller, ok := curr.(ControllerRunner); ok {
if err := controller.RunOnce(); err != nil {
errors = append(errors, err)
}
}
}
return utilerrors.NewAggregate(errors)
}
// Run runs the controller
func (c unionCAContent) Run(workers int, stopCh <-chan struct{}) {
for _, curr := range c {
if controller, ok := curr.(ControllerRunner); ok {
go controller.Run(workers, stopCh)
}
}
}

View File

@ -50,7 +50,6 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/audit/policy:go_default_library", "//staging/src/k8s.io/apiserver/pkg/audit/policy:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authentication/request/headerrequest:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/request/x509:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/path:go_default_library", "//staging/src/k8s.io/apiserver/pkg/authorization/path:go_default_library",

View File

@ -23,6 +23,8 @@ import (
"strings" "strings"
"time" "time"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"github.com/spf13/pflag" "github.com/spf13/pflag"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -30,12 +32,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory" "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/request/headerrequest" "k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/authentication/request/x509"
"k8s.io/apiserver/pkg/server" "k8s.io/apiserver/pkg/server"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/cert"
"k8s.io/klog" "k8s.io/klog"
openapicommon "k8s.io/kube-openapi/pkg/common" openapicommon "k8s.io/kube-openapi/pkg/common"
) )
@ -112,7 +112,7 @@ func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig
return nil, nil return nil, nil
} }
verifyFn, err := x509.NewStaticVerifierFromFile(s.ClientCAFile) caBundleProvider, err := dynamiccertificates.NewDynamicCAContentFromFile("request-header", s.ClientCAFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,7 +121,7 @@ func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig
UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders), UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders),
GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders), GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders),
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes), ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes),
VerifyOptionFn: verifyFn, CAContentProvider: caBundleProvider,
AllowedClientNames: headerrequest.StaticStringSlice(s.AllowedNames), AllowedClientNames: headerrequest.StaticStringSlice(s.AllowedNames),
}, nil }, nil
} }
@ -132,23 +132,23 @@ type ClientCertAuthenticationOptions struct {
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates // ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string ClientCA string
// ClientVerifyOptionFn are the options for verifying incoming connections using mTLS and directly assigning to users. // CAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
// Generally this is the CA bundle file used to authenticate client certificates // Generally this is the CA bundle file used to authenticate client certificates
// If non-nil, this takes priority over the ClientCA file. // If non-nil, this takes priority over the ClientCA file.
ClientVerifyOptionFn x509.VerifyOptionFunc CAContentProvider dynamiccertificates.CAContentProvider
} }
// GetClientVerifyOptionFn provides verify options for your authenticator while respecting the preferred order of verifiers. // GetClientVerifyOptionFn provides verify options for your authenticator while respecting the preferred order of verifiers.
func (s *ClientCertAuthenticationOptions) GetClientVerifyOptionFn() (x509.VerifyOptionFunc, error) { func (s *ClientCertAuthenticationOptions) GetClientCAContentProvider() (dynamiccertificates.CAContentProvider, error) {
if s.ClientVerifyOptionFn != nil { if s.CAContentProvider != nil {
return s.ClientVerifyOptionFn, nil return s.CAContentProvider, nil
} }
if len(s.ClientCA) == 0 { if len(s.ClientCA) == 0 {
return nil, nil return nil, nil
} }
return x509.NewStaticVerifierFromFile(s.ClientCA) return dynamiccertificates.NewDynamicCAContentFromFile("client-ca-bundle", s.ClientCA)
} }
func (s *ClientCertAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { func (s *ClientCertAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
@ -230,9 +230,9 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
"Note that this can result in authentication that treats all requests as anonymous.") "Note that this can result in authentication that treats all requests as anonymous.")
} }
func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error { func (s *DelegatingAuthenticationOptions) ApplyTo(authenticationInfo *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error {
if s == nil { if s == nil {
c.Authenticator = nil authenticationInfo.Authenticator = nil
return nil return nil
} }
@ -266,32 +266,36 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo,
} }
// configure AuthenticationInfo config // configure AuthenticationInfo config
cfg.ClientVerifyOptionFn, err = s.ClientCert.GetClientVerifyOptionFn() cfg.ClientCertificateCAContentProvider, err = s.ClientCert.GetClientCAContentProvider()
if err != nil { if err != nil {
return fmt.Errorf("unable to load client CA file: %v", err) return fmt.Errorf("unable to load client CA file: %v", err)
} }
if err = c.ApplyClientCert(s.ClientCert.ClientCA, servingInfo); err != nil { if cfg.ClientCertificateCAContentProvider != nil {
if err = authenticationInfo.ApplyClientCert(cfg.ClientCertificateCAContentProvider, servingInfo); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err) return fmt.Errorf("unable to load client CA file: %v", err)
} }
}
cfg.RequestHeaderConfig, err = s.RequestHeader.ToAuthenticationRequestHeaderConfig() cfg.RequestHeaderConfig, err = s.RequestHeader.ToAuthenticationRequestHeaderConfig()
if err != nil { if err != nil {
return fmt.Errorf("unable to create request header authentication config: %v", err) return fmt.Errorf("unable to create request header authentication config: %v", err)
} }
if err = c.ApplyClientCert(s.RequestHeader.ClientCAFile, servingInfo); err != nil { if cfg.RequestHeaderConfig != nil {
if err = authenticationInfo.ApplyClientCert(cfg.RequestHeaderConfig.CAContentProvider, servingInfo); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err) return fmt.Errorf("unable to load client CA file: %v", err)
} }
}
// create authenticator // create authenticator
authenticator, securityDefinitions, err := cfg.New() authenticator, securityDefinitions, err := cfg.New()
if err != nil { if err != nil {
return err return err
} }
c.Authenticator = authenticator authenticationInfo.Authenticator = authenticator
if openAPIConfig != nil { if openAPIConfig != nil {
openAPIConfig.SecurityDefinitions = securityDefinitions openAPIConfig.SecurityDefinitions = securityDefinitions
} }
c.SupportsBasicAuth = false authenticationInfo.SupportsBasicAuth = false
return nil return nil
} }
@ -372,28 +376,14 @@ func inClusterClientCA(authConfigMap *v1.ConfigMap) (*ClientCertAuthenticationOp
// not having a client-ca is fine, return nil // not having a client-ca is fine, return nil
return nil, nil return nil, nil
} }
clientCAProvider, err := dynamiccertificates.NewStaticCAContent("client-ca-file", []byte(clientCA))
clientCAs, err := cert.NewPoolFromBytes([]byte(clientCA))
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to load client CA from configmap: %v", err)
}
verifyOpts := x509.DefaultVerifyOptions()
verifyOpts.Roots = clientCAs
// we still need to write out the client-ca-file for now because it is used to plumb the options through the apiserver's
// configuration to hint clients.
// TODO deads2k this should eventually be made dynamic along with the authenticator. I'm just wiring them one at at time.
f, err := ioutil.TempFile("", "client-ca-file")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(f.Name(), []byte(clientCA), 0600); err != nil {
return nil, err return nil, err
} }
return &ClientCertAuthenticationOptions{ return &ClientCertAuthenticationOptions{
ClientCA: f.Name(), ClientCA: "",
ClientVerifyOptionFn: x509.StaticVerifierFn(verifyOpts), CAContentProvider: clientCAProvider,
}, nil }, nil
} }

View File

@ -57,7 +57,7 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"}, UsernameHeaders: headerrequest.StaticStringSlice{"x-remote-user"},
GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"}, GroupHeaders: headerrequest.StaticStringSlice{"x-remote-group"},
ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"}, ExtraHeaderPrefixes: headerrequest.StaticStringSlice{"x-remote-extra-"},
VerifyOptionFn: nil, // this is nil because you can't compare functions CAContentProvider: nil, // this is nil because you can't compare functions
AllowedClientNames: headerrequest.StaticStringSlice{"kube-aggregator"}, AllowedClientNames: headerrequest.StaticStringSlice{"kube-aggregator"},
}, },
}, },
@ -70,10 +70,10 @@ func TestToAuthenticationRequestHeaderConfig(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if resultConfig != nil { if resultConfig != nil {
if resultConfig.VerifyOptionFn == nil { if resultConfig.CAContentProvider == nil {
t.Error("missing requestheader verify") t.Error("missing requestheader verify")
} }
resultConfig.VerifyOptionFn = nil resultConfig.CAContentProvider = nil
} }
if !reflect.DeepEqual(resultConfig, testcase.expectConfig) { if !reflect.DeepEqual(resultConfig, testcase.expectConfig) {

View File

@ -72,6 +72,20 @@ func (s *SecureServingInfo) tlsConfig(stopCh <-chan struct{}) (*tls.Config, erro
s.SNICerts, s.SNICerts,
nil, // TODO see how to plumb an event recorder down in here. For now this results in simply klog messages. nil, // TODO see how to plumb an event recorder down in here. For now this results in simply klog messages.
) )
// register if possible
if notifier, ok := s.ClientCA.(dynamiccertificates.Notifier); ok {
notifier.AddListener(dynamicCertificateController)
}
// start controllers if possible
if controller, ok := s.ClientCA.(dynamiccertificates.ControllerRunner); ok {
// runonce to be sure that we have a value.
if err := controller.RunOnce(); err != nil {
return nil, err
}
go controller.Run(1, stopCh)
}
// runonce to be sure that we have a value. // runonce to be sure that we have a value.
if err := dynamicCertificateController.RunOnce(); err != nil { if err := dynamicCertificateController.RunOnce(); err != nil {
return nil, err return nil, err

View File

@ -89,6 +89,7 @@ filegroup(
":package-srcs", ":package-srcs",
"//test/integration/apiserver/admissionwebhook:all-srcs", "//test/integration/apiserver/admissionwebhook:all-srcs",
"//test/integration/apiserver/apply:all-srcs", "//test/integration/apiserver/apply:all-srcs",
"//test/integration/apiserver/certreload:all-srcs",
"//test/integration/apiserver/podlogs:all-srcs", "//test/integration/apiserver/podlogs:all-srcs",
], ],
tags = ["automanaged"], tags = ["automanaged"],

View File

@ -0,0 +1,29 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = [
"certreload_test.go",
"main_test.go",
],
tags = ["integration"],
deps = [
"//cmd/kube-apiserver/app/options:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/server/dynamiccertificates:go_default_library",
"//test/integration/framework: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"],
)

View File

@ -0,0 +1,131 @@
/*
Copyright 2019 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 podlogs
import (
"crypto/tls"
"io/ioutil"
"net/url"
"strings"
"testing"
"time"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/kubernetes/cmd/kube-apiserver/app/options"
"k8s.io/kubernetes/test/integration/framework"
)
func TestClientCA(t *testing.T) {
stopCh := make(chan struct{})
defer close(stopCh)
// I have no idea what this cert is, but it doesn't matter, we just want something that always fails validation
differentClientCA := []byte(`-----BEGIN CERTIFICATE-----
MIIDQDCCAiigAwIBAgIJANWw74P5KJk2MA0GCSqGSIb3DQEBCwUAMDQxMjAwBgNV
BAMMKWdlbmVyaWNfd2ViaG9va19hZG1pc3Npb25fcGx1Z2luX3Rlc3RzX2NhMCAX
DTE3MTExNjAwMDUzOVoYDzIyOTEwOTAxMDAwNTM5WjAjMSEwHwYDVQQDExh3ZWJo
b29rLXRlc3QuZGVmYXVsdC5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDXd/nQ89a5H8ifEsigmMd01Ib6NVR3bkJjtkvYnTbdfYEBj7UzqOQtHoLa
dIVmefny5uIHvj93WD8WDVPB3jX2JHrXkDTXd/6o6jIXHcsUfFTVLp6/bZ+Anqe0
r/7hAPkzA2A7APyTWM3ZbEeo1afXogXhOJ1u/wz0DflgcB21gNho4kKTONXO3NHD
XLpspFqSkxfEfKVDJaYAoMnYZJtFNsa2OvsmLnhYF8bjeT3i07lfwrhUZvP+7Gsp
7UgUwc06WuNHjfx1s5e6ySzH0QioMD1rjYneqOvk0pKrMIhuAEWXqq7jlXcDtx1E
j+wnYbVqqVYheHZ8BCJoVAAQGs9/AgMBAAGjZDBiMAkGA1UdEwQCMAAwCwYDVR0P
BAQDAgXgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATApBgNVHREEIjAg
hwR/AAABghh3ZWJob29rLXRlc3QuZGVmYXVsdC5zdmMwDQYJKoZIhvcNAQELBQAD
ggEBAD/GKSPNyQuAOw/jsYZesb+RMedbkzs18sSwlxAJQMUrrXwlVdHrA8q5WhE6
ABLqU1b8lQ8AWun07R8k5tqTmNvCARrAPRUqls/ryER+3Y9YEcxEaTc3jKNZFLbc
T6YtcnkdhxsiO136wtiuatpYL91RgCmuSpR8+7jEHhuFU01iaASu7ypFrUzrKHTF
bKwiLRQi1cMzVcLErq5CDEKiKhUkoDucyARFszrGt9vNIl/YCcBOkcNvM3c05Hn3
M++C29JwS3Hwbubg6WO3wjFjoEhpCwU6qRYUz3MRp4tHO4kxKXx+oQnUiFnR7vW0
YkNtGc1RUDHwecCTFpJtPb7Yu/E=
-----END CERTIFICATE-----
`)
differentFrontProxyCA := []byte(`-----BEGIN CERTIFICATE-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE-----
`)
clientCAFilename := ""
frontProxyCAFilename := ""
_, kubeconfig := framework.StartTestServer(t, stopCh, framework.TestServerSetup{
ModifyServerRunOptions: func(opts *options.ServerRunOptions) {
opts.GenericServerRunOptions.MaxRequestBodyBytes = 1024 * 1024
clientCAFilename = opts.Authentication.ClientCert.ClientCA
frontProxyCAFilename = opts.Authentication.RequestHeader.ClientCAFile
dynamiccertificates.FileRefreshDuration = 1 * time.Second
},
})
apiserverURL, err := url.Parse(kubeconfig.Host)
if err != nil {
t.Fatal(err)
}
// when we run this the second time, we know which one we are expecting
acceptableCAs := []string{}
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
GetClientCertificate: func(hello *tls.CertificateRequestInfo) (*tls.Certificate, error) {
acceptableCAs = []string{}
for _, curr := range hello.AcceptableCAs {
acceptableCAs = append(acceptableCAs, string(curr))
}
return &tls.Certificate{}, nil
},
}
conn, err := tls.Dial("tcp", apiserverURL.Host, tlsConfig)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
if err := ioutil.WriteFile(clientCAFilename, differentClientCA, 0644); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(frontProxyCAFilename, differentFrontProxyCA, 0644); err != nil {
t.Fatal(err)
}
time.Sleep(4 * time.Second)
conn2, err := tls.Dial("tcp", apiserverURL.Host, tlsConfig)
if err != nil {
t.Fatal(err)
}
defer conn2.Close()
expectedCAs := []string{"webhook-test.default.svc", "My Client"}
if len(expectedCAs) != len(acceptableCAs) {
t.Fatal(strings.Join(acceptableCAs, ":"))
}
for i := range expectedCAs {
if !strings.Contains(acceptableCAs[i], expectedCAs[i]) {
t.Errorf("expected %q, got %q", expectedCAs[i], acceptableCAs[i])
}
}
}

View File

@ -0,0 +1,27 @@
/*
Copyright 2017 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 podlogs
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}