vendor: Update vendor logic

This commit is contained in:
Clayton Coleman 2020-04-08 14:34:43 -04:00
parent c6ac5cbc87
commit 4ca64b85f0
No known key found for this signature in database
GPG key ID: 3D16906B4F1C5CB3
1540 changed files with 265304 additions and 91616 deletions

View file

@ -18,7 +18,6 @@ package authenticatorfactory
import (
"errors"
"fmt"
"time"
"github.com/go-openapi/spec"
@ -33,8 +32,7 @@ import (
"k8s.io/apiserver/pkg/authentication/request/x509"
"k8s.io/apiserver/pkg/authentication/token/cache"
webhooktoken "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
"k8s.io/client-go/util/cert"
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1"
)
// DelegatingAuthenticatorConfig is the minimal configuration needed to create an authenticator
@ -48,8 +46,10 @@ type DelegatingAuthenticatorConfig struct {
// CacheTTL is the length of time that a token authentication answer will be cached.
CacheTTL time.Duration
// ClientCAFile is the CA bundle file used to authenticate client certificates
ClientCAFile string
// 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
// If this is nil, then mTLS will not be used.
ClientCertificateCAContentProvider CAContentProvider
APIAudiences authenticator.Audiences
@ -63,28 +63,19 @@ func (c DelegatingAuthenticatorConfig) New() (authenticator.Request, *spec.Secur
// front-proxy first, then remote
// Add the front proxy authenticator if requested
if c.RequestHeaderConfig != nil {
requestHeaderAuthenticator, err := headerrequest.NewSecure(
c.RequestHeaderConfig.ClientCA,
requestHeaderAuthenticator := headerrequest.NewDynamicVerifyOptionsSecure(
c.RequestHeaderConfig.CAContentProvider.VerifyOptions,
c.RequestHeaderConfig.AllowedClientNames,
c.RequestHeaderConfig.UsernameHeaders,
c.RequestHeaderConfig.GroupHeaders,
c.RequestHeaderConfig.ExtraHeaderPrefixes,
)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, requestHeaderAuthenticator)
}
// x509 client cert auth
if len(c.ClientCAFile) > 0 {
clientCAs, err := cert.NewPool(c.ClientCAFile)
if err != nil {
return nil, nil, fmt.Errorf("unable to load client CA file %s: %v", c.ClientCAFile, err)
}
verifyOpts := x509.DefaultVerifyOptions()
verifyOpts.Roots = clientCAs
authenticators = append(authenticators, x509.New(verifyOpts, x509.CommonNameUserConversion))
if c.ClientCertificateCAContentProvider != nil {
authenticators = append(authenticators, x509.NewDynamic(c.ClientCertificateCAContentProvider.VerifyOptions, x509.CommonNameUserConversion))
}
if c.TokenAccessReviewClient != nil {

View file

@ -16,16 +16,33 @@ limitations under the License.
package authenticatorfactory
import (
"crypto/x509"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
)
type RequestHeaderConfig struct {
// UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
UsernameHeaders []string
UsernameHeaders headerrequest.StringSliceProvider
// GroupHeaders are the headers to check (case-insensitively) for a group names. All values will be used.
GroupHeaders []string
GroupHeaders headerrequest.StringSliceProvider
// 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.
ExtraHeaderPrefixes []string
// ClientCA points to CA bundle file which is used verify the identity of the front proxy
ClientCA string
ExtraHeaderPrefixes headerrequest.StringSliceProvider
// 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.
CAContentProvider CAContentProvider
// AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any.
AllowedClientNames []string
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, bool)
}

View file

@ -24,26 +24,47 @@ import (
"net/url"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/authenticator"
x509request "k8s.io/apiserver/pkg/authentication/request/x509"
"k8s.io/apiserver/pkg/authentication/user"
utilcert "k8s.io/client-go/util/cert"
)
// StringSliceProvider is a way to get a string slice value. It is heavily used for authentication headers among other places.
type StringSliceProvider interface {
// Value returns the current string slice. Callers should never mutate the returned value.
Value() []string
}
// StringSliceProviderFunc is a function that matches the StringSliceProvider interface
type StringSliceProviderFunc func() []string
// Value returns the current string slice. Callers should never mutate the returned value.
func (d StringSliceProviderFunc) Value() []string {
return d()
}
// StaticStringSlice a StringSliceProvider that returns a fixed value
type StaticStringSlice []string
// Value returns the current string slice. Callers should never mutate the returned value.
func (s StaticStringSlice) Value() []string {
return s
}
type requestHeaderAuthRequestHandler struct {
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
nameHeaders []string
nameHeaders StringSliceProvider
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
groupHeaders []string
groupHeaders StringSliceProvider
// extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
// the user.Info.Extra. All values of all matching headers will be added.
extraHeaderPrefixes []string
extraHeaderPrefixes StringSliceProvider
}
func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
if err != nil {
return nil, err
@ -57,11 +78,19 @@ func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []stri
return nil, err
}
return NewDynamic(
StaticStringSlice(trimmedNameHeaders),
StaticStringSlice(trimmedGroupHeaders),
StaticStringSlice(trimmedExtraHeaderPrefixes),
), nil
}
func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
return &requestHeaderAuthRequestHandler{
nameHeaders: trimmedNameHeaders,
groupHeaders: trimmedGroupHeaders,
extraHeaderPrefixes: trimmedExtraHeaderPrefixes,
}, nil
nameHeaders: nameHeaders,
groupHeaders: groupHeaders,
extraHeaderPrefixes: extraHeaderPrefixes,
}
}
func trimHeaders(headerNames ...string) ([]string, error) {
@ -78,11 +107,6 @@ func trimHeaders(headerNames ...string) ([]string, error) {
}
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
if err != nil {
return nil, err
}
if len(clientCA) == 0 {
return nil, fmt.Errorf("missing clientCA file")
}
@ -102,26 +126,51 @@ func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string,
opts.Roots.AddCert(cert)
}
return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
if err != nil {
return nil, err
}
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
if err != nil {
return nil, err
}
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
if err != nil {
return nil, err
}
return NewDynamicVerifyOptionsSecure(
x509request.StaticVerifierFn(opts),
StaticStringSlice(proxyClientNames),
StaticStringSlice(trimmedNameHeaders),
StaticStringSlice(trimmedGroupHeaders),
StaticStringSlice(trimmedExtraHeaderPrefixes),
), nil
}
func NewDynamicVerifyOptionsSecure(verifyOptionFn x509request.VerifyOptionFunc, proxyClientNames, nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
headerAuthenticator := NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes)
return x509request.NewDynamicCAVerifier(verifyOptionFn, headerAuthenticator, proxyClientNames)
}
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
name := headerValue(req.Header, a.nameHeaders)
name := headerValue(req.Header, a.nameHeaders.Value())
if len(name) == 0 {
return nil, false, nil
}
groups := allHeaderValues(req.Header, a.groupHeaders)
extra := newExtra(req.Header, a.extraHeaderPrefixes)
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
// clear headers used for authentication
for _, headerName := range a.nameHeaders {
for _, headerName := range a.nameHeaders.Value() {
req.Header.Del(headerName)
}
for _, headerName := range a.groupHeaders {
for _, headerName := range a.groupHeaders.Value() {
req.Header.Del(headerName)
}
for k := range extra {
for _, prefix := range a.extraHeaderPrefixes {
for _, prefix := range a.extraHeaderPrefixes.Value() {
req.Header.Del(prefix + k)
}
}

View file

@ -0,0 +1,71 @@
/*
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 x509
import (
"crypto/x509"
"fmt"
"k8s.io/client-go/util/cert"
)
// StaticVerifierFn is a VerifyOptionFunc that always returns the same value. This allows verify options that cannot change.
func StaticVerifierFn(opts x509.VerifyOptions) VerifyOptionFunc {
return func() (x509.VerifyOptions, bool) {
return opts, true
}
}
// NewStaticVerifierFromFile creates a new verification func from a file. It reads the content and then fails.
// It will return a nil function if you pass an empty CA file.
func NewStaticVerifierFromFile(clientCA string) (VerifyOptionFunc, error) {
if len(clientCA) == 0 {
return nil, nil
}
// Wrap with an x509 verifier
var err error
opts := DefaultVerifyOptions()
opts.Roots, err = cert.NewPool(clientCA)
if err != nil {
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
}
return StaticVerifierFn(opts), nil
}
// StringSliceProvider is a way to get a string slice value. It is heavily used for authentication headers among other places.
type StringSliceProvider interface {
// Value returns the current string slice. Callers should never mutate the returned value.
Value() []string
}
// StringSliceProviderFunc is a function that matches the StringSliceProvider interface
type StringSliceProviderFunc func() []string
// Value returns the current string slice. Callers should never mutate the returned value.
func (d StringSliceProviderFunc) Value() []string {
return d()
}
// StaticStringSlice a StringSliceProvider that returns a fixed value
type StaticStringSlice []string
// Value returns the current string slice. Callers should never mutate the returned value.
func (s StaticStringSlice) Value() []string {
return s
}

View file

@ -23,16 +23,24 @@ import (
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/component-base/metrics"
"k8s.io/component-base/metrics/legacyregistry"
)
var clientCertificateExpirationHistogram = prometheus.NewHistogram(
prometheus.HistogramOpts{
/*
* By default, the following metric is defined as falling under
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/20190404-kubernetes-control-plane-metrics-stability.md#stability-classes)
*
* Promoting the stability level of the metric is a responsibility of the component owner, since it
* involves explicitly acknowledging support for the metric across multiple releases, in accordance with
* the metric stability policy.
*/
var clientCertificateExpirationHistogram = metrics.NewHistogram(
&metrics.HistogramOpts{
Namespace: "apiserver",
Subsystem: "client",
Name: "certificate_expiration_seconds",
@ -53,11 +61,12 @@ var clientCertificateExpirationHistogram = prometheus.NewHistogram(
(6 * 30 * 24 * time.Hour).Seconds(),
(12 * 30 * 24 * time.Hour).Seconds(),
},
StabilityLevel: metrics.ALPHA,
},
)
func init() {
prometheus.MustRegister(clientCertificateExpirationHistogram)
legacyregistry.MustRegister(clientCertificateExpirationHistogram)
}
// UserConversion defines an interface for extracting user info from a client certificate chain
@ -73,16 +82,28 @@ func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Resp
return f(chain)
}
// VerifyOptionFunc is function which provides a shallow copy of the VerifyOptions to the authenticator. This allows
// for cases where the options (particularly the CAs) can change. If the bool is false, then the returned VerifyOptions
// are ignored and the authenticator will express "no opinion". This allows a clear signal for cases where a CertPool
// is eventually expected, but not currently present.
type VerifyOptionFunc func() (x509.VerifyOptions, bool)
// Authenticator implements request.Authenticator by extracting user info from verified client certificates
type Authenticator struct {
opts x509.VerifyOptions
user UserConversion
verifyOptionsFn VerifyOptionFunc
user UserConversion
}
// New returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
return &Authenticator{opts, user}
return NewDynamic(StaticVerifierFn(opts), user)
}
// NewDynamic returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptionFunc (which may be dynamic), and converts valid certificate chains into user.Info using the provided UserConversion
func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
return &Authenticator{verifyOptionsFn, user}
}
// AuthenticateRequest authenticates the request using presented client certificates
@ -92,7 +113,11 @@ func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.R
}
// Use intermediates, if provided
optsCopy := a.opts
optsCopy, ok := a.verifyOptionsFn()
// if there are intentionally no verify options, then we cannot authenticate this request
if !ok {
return nil, false, nil
}
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
@ -124,17 +149,23 @@ func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.R
// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
type Verifier struct {
opts x509.VerifyOptions
auth authenticator.Request
verifyOptionsFn VerifyOptionFunc
auth authenticator.Request
// allowedCommonNames contains the common names which a verified certificate is allowed to have.
// If empty, all verified certificates are allowed.
allowedCommonNames sets.String
allowedCommonNames StringSliceProvider
}
// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
return &Verifier{opts, auth, allowedCommonNames}
return NewDynamicCAVerifier(StaticVerifierFn(opts), auth, StaticStringSlice(allowedCommonNames.List()))
}
// NewDynamicCAVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
// TODO make the allowedCommonNames dynamic
func NewDynamicCAVerifier(verifyOptionsFn VerifyOptionFunc, auth authenticator.Request, allowedCommonNames StringSliceProvider) authenticator.Request {
return &Verifier{verifyOptionsFn, auth, allowedCommonNames}
}
// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
@ -144,7 +175,11 @@ func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Respon
}
// Use intermediates, if provided
optsCopy := a.opts
optsCopy, ok := a.verifyOptionsFn()
// if there are intentionally no verify options, then we cannot authenticate this request
if !ok {
return nil, false, nil
}
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
@ -163,12 +198,14 @@ func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Respon
func (a *Verifier) verifySubject(subject pkix.Name) error {
// No CN restrictions
if len(a.allowedCommonNames) == 0 {
if len(a.allowedCommonNames.Value()) == 0 {
return nil
}
// Enforce CN restrictions
if a.allowedCommonNames.Has(subject.CommonName) {
return nil
for _, allowedCommonName := range a.allowedCommonNames.Value() {
if allowedCommonName == subject.CommonName {
return nil
}
}
return fmt.Errorf("x509: subject with cn=%s is not in the allowed list", subject.CommonName)
}

View file

@ -19,20 +19,20 @@ package cache
import (
"time"
lrucache "k8s.io/apimachinery/pkg/util/cache"
utilcache "k8s.io/apimachinery/pkg/util/cache"
"k8s.io/apimachinery/pkg/util/clock"
)
type simpleCache struct {
lru *lrucache.LRUExpireCache
cache *utilcache.Expiring
}
func newSimpleCache(size int, clock clock.Clock) cache {
return &simpleCache{lru: lrucache.NewLRUExpireCacheWithClock(size, clock)}
func newSimpleCache(clock clock.Clock) cache {
return &simpleCache{cache: utilcache.NewExpiringWithClock(clock)}
}
func (c *simpleCache) get(key string) (*cacheRecord, bool) {
record, ok := c.lru.Get(key)
record, ok := c.cache.Get(key)
if !ok {
return nil, false
}
@ -41,9 +41,9 @@ func (c *simpleCache) get(key string) (*cacheRecord, bool) {
}
func (c *simpleCache) set(key string, value *cacheRecord, ttl time.Duration) {
c.lru.Add(key, value, ttl)
c.cache.Set(key, value, ttl)
}
func (c *simpleCache) remove(key string) {
c.lru.Remove(key)
c.cache.Delete(key)
}

View file

@ -18,8 +18,15 @@ package cache
import (
"context"
"fmt"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"hash"
"io"
"sync"
"time"
"unsafe"
utilclock "k8s.io/apimachinery/pkg/util/clock"
"k8s.io/apiserver/pkg/authentication/authenticator"
@ -40,6 +47,11 @@ type cachedTokenAuthenticator struct {
failureTTL time.Duration
cache cache
// hashPool is a per authenticator pool of hash.Hash (to avoid allocations from building the Hash)
// HMAC with SHA-256 and a random key is used to prevent precomputation and length extension attacks
// It also mitigates hash map DOS attacks via collisions (the inputs are supplied by untrusted users)
hashPool *sync.Pool
}
type cache interface {
@ -57,12 +69,30 @@ func New(authenticator authenticator.Token, cacheErrs bool, successTTL, failureT
}
func newWithClock(authenticator authenticator.Token, cacheErrs bool, successTTL, failureTTL time.Duration, clock utilclock.Clock) authenticator.Token {
randomCacheKey := make([]byte, 32)
if _, err := rand.Read(randomCacheKey); err != nil {
panic(err) // rand should never fail
}
return &cachedTokenAuthenticator{
authenticator: authenticator,
cacheErrs: cacheErrs,
successTTL: successTTL,
failureTTL: failureTTL,
cache: newStripedCache(32, fnvHashFunc, func() cache { return newSimpleCache(128, clock) }),
// Cache performance degrades noticeably when the number of
// tokens in operation exceeds the size of the cache. It is
// cheap to make the cache big in the second dimension below,
// the memory is only consumed when that many tokens are being
// used. Currently we advertise support 5k nodes and 10k
// namespaces; a 32k entry cache is therefore a 2x safety
// margin.
cache: newStripedCache(32, fnvHashFunc, func() cache { return newSimpleCache(clock) }),
hashPool: &sync.Pool{
New: func() interface{} {
return hmac.New(sha256.New, randomCacheKey)
},
},
}
}
@ -70,7 +100,7 @@ func newWithClock(authenticator authenticator.Token, cacheErrs bool, successTTL,
func (a *cachedTokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
auds, _ := authenticator.AudiencesFrom(ctx)
key := keyFunc(auds, token)
key := keyFunc(a.hashPool, auds, token)
if record, ok := a.cache.get(key); ok {
return record.resp, record.ok, record.err
}
@ -90,6 +120,55 @@ func (a *cachedTokenAuthenticator) AuthenticateToken(ctx context.Context, token
return resp, ok, err
}
func keyFunc(auds []string, token string) string {
return fmt.Sprintf("%#v|%v", auds, token)
// keyFunc generates a string key by hashing the inputs.
// This lowers the memory requirement of the cache and keeps tokens out of memory.
func keyFunc(hashPool *sync.Pool, auds []string, token string) string {
h := hashPool.Get().(hash.Hash)
h.Reset()
// try to force stack allocation
var a [4]byte
b := a[:]
writeLengthPrefixedString(h, b, token)
// encode the length of audiences to avoid ambiguities
writeLength(h, b, len(auds))
for _, aud := range auds {
writeLengthPrefixedString(h, b, aud)
}
key := toString(h.Sum(nil)) // skip base64 encoding to save an allocation
hashPool.Put(h)
return key
}
// writeLengthPrefixedString writes s with a length prefix to prevent ambiguities, i.e. "xy" + "z" == "x" + "yz"
// the length of b is assumed to be 4 (b is mutated by this function to store the length of s)
func writeLengthPrefixedString(w io.Writer, b []byte, s string) {
writeLength(w, b, len(s))
if _, err := w.Write(toBytes(s)); err != nil {
panic(err) // Write() on hash never fails
}
}
// writeLength encodes length into b and then writes it via the given writer
// the length of b is assumed to be 4
func writeLength(w io.Writer, b []byte, length int) {
binary.BigEndian.PutUint32(b, uint32(length))
if _, err := w.Write(b); err != nil {
panic(err) // Write() on hash never fails
}
}
// toBytes performs unholy acts to avoid allocations
func toBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}
// toString performs unholy acts to avoid allocations
func toString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}