mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
rebase: bump k8s.io/kubernetes from 1.26.2 to 1.27.2
Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
mergify[bot]
parent
0e79135419
commit
07b05616a0
43
vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go
generated
vendored
Normal file
43
vendor/k8s.io/apiserver/pkg/authentication/request/anonymous/anonymous.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2016 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 anonymous
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
const (
|
||||
anonymousUser = user.Anonymous
|
||||
|
||||
unauthenticatedGroup = user.AllUnauthenticated
|
||||
)
|
||||
|
||||
func NewAuthenticator() authenticator.Request {
|
||||
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
auds, _ := authenticator.AudiencesFrom(req.Context())
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: anonymousUser,
|
||||
Groups: []string{unauthenticatedGroup},
|
||||
},
|
||||
Audiences: auds,
|
||||
}, true, nil
|
||||
})
|
||||
}
|
76
vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go
generated
vendored
Normal file
76
vendor/k8s.io/apiserver/pkg/authentication/request/bearertoken/bearertoken.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2014 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 bearertoken
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidTokenWithSpaceWarning = "the provided Authorization header contains extra space before the bearer token, and is ignored"
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
auth authenticator.Token
|
||||
}
|
||||
|
||||
func New(auth authenticator.Token) *Authenticator {
|
||||
return &Authenticator{auth}
|
||||
}
|
||||
|
||||
var invalidToken = errors.New("invalid bearer token")
|
||||
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
auth := strings.TrimSpace(req.Header.Get("Authorization"))
|
||||
if auth == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
parts := strings.SplitN(auth, " ", 3)
|
||||
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
token := parts[1]
|
||||
|
||||
// Empty bearer tokens aren't valid
|
||||
if len(token) == 0 {
|
||||
// The space before the token case
|
||||
if len(parts) == 3 {
|
||||
warning.AddWarning(req.Context(), "", invalidTokenWithSpaceWarning)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
|
||||
// if we authenticated successfully, go ahead and remove the bearer token so that no one
|
||||
// is ever tempted to use it inside of the API server
|
||||
if ok {
|
||||
req.Header.Del("Authorization")
|
||||
}
|
||||
|
||||
// If the token authenticator didn't error, provide a default error
|
||||
if !ok && err == nil {
|
||||
err = invalidToken
|
||||
}
|
||||
|
||||
return resp, ok, err
|
||||
}
|
239
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go
generated
vendored
Normal file
239
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader.go
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
/*
|
||||
Copyright 2016 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 headerrequest
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"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 StringSliceProvider
|
||||
|
||||
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
|
||||
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 StringSliceProvider
|
||||
}
|
||||
|
||||
func New(nameHeaders, groupHeaders, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
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 NewDynamic(
|
||||
StaticStringSlice(trimmedNameHeaders),
|
||||
StaticStringSlice(trimmedGroupHeaders),
|
||||
StaticStringSlice(trimmedExtraHeaderPrefixes),
|
||||
), nil
|
||||
}
|
||||
|
||||
func NewDynamic(nameHeaders, groupHeaders, extraHeaderPrefixes StringSliceProvider) authenticator.Request {
|
||||
return &requestHeaderAuthRequestHandler{
|
||||
nameHeaders: nameHeaders,
|
||||
groupHeaders: groupHeaders,
|
||||
extraHeaderPrefixes: extraHeaderPrefixes,
|
||||
}
|
||||
}
|
||||
|
||||
func trimHeaders(headerNames ...string) ([]string, error) {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
trimmedHeader := strings.TrimSpace(headerName)
|
||||
if len(trimmedHeader) == 0 {
|
||||
return nil, fmt.Errorf("empty header %q", headerName)
|
||||
}
|
||||
ret = append(ret, trimmedHeader)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
if len(clientCA) == 0 {
|
||||
return nil, fmt.Errorf("missing clientCA file")
|
||||
}
|
||||
|
||||
// Wrap with an x509 verifier
|
||||
caData, err := ioutil.ReadFile(clientCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %v", clientCA, err)
|
||||
}
|
||||
opts := x509request.DefaultVerifyOptions()
|
||||
opts.Roots = x509.NewCertPool()
|
||||
certs, err := utilcert.ParseCertsPEM(caData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
|
||||
}
|
||||
for _, cert := range certs {
|
||||
opts.Roots.AddCert(cert)
|
||||
}
|
||||
|
||||
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.Value())
|
||||
if len(name) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
groups := allHeaderValues(req.Header, a.groupHeaders.Value())
|
||||
extra := newExtra(req.Header, a.extraHeaderPrefixes.Value())
|
||||
|
||||
// clear headers used for authentication
|
||||
for _, headerName := range a.nameHeaders.Value() {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for _, headerName := range a.groupHeaders.Value() {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for k := range extra {
|
||||
for _, prefix := range a.extraHeaderPrefixes.Value() {
|
||||
req.Header.Del(prefix + k)
|
||||
}
|
||||
}
|
||||
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: name,
|
||||
Groups: groups,
|
||||
Extra: extra,
|
||||
},
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func headerValue(h http.Header, headerNames []string) string {
|
||||
for _, headerName := range headerNames {
|
||||
headerValue := h.Get(headerName)
|
||||
if len(headerValue) > 0 {
|
||||
return headerValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func allHeaderValues(h http.Header, headerNames []string) []string {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
headerKey := http.CanonicalHeaderKey(headerName)
|
||||
values, ok := h[headerKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, headerValue := range values {
|
||||
if len(headerValue) > 0 {
|
||||
ret = append(ret, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func unescapeExtraKey(encodedKey string) string {
|
||||
key, err := url.PathUnescape(encodedKey) // Decode %-encoded bytes.
|
||||
if err != nil {
|
||||
return encodedKey // Always record extra strings, even if malformed/unencoded.
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
|
||||
ret := map[string][]string{}
|
||||
|
||||
// we have to iterate over prefixes first in order to have proper ordering inside the value slices
|
||||
for _, prefix := range headerPrefixes {
|
||||
for headerName, vv := range h {
|
||||
if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
|
||||
continue
|
||||
}
|
||||
|
||||
extraKey := unescapeExtraKey(strings.ToLower(headerName[len(prefix):]))
|
||||
ret[extraKey] = append(ret[extraKey], vv...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
337
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go
generated
vendored
Normal file
337
vendor/k8s.io/apiserver/pkg/authentication/request/headerrequest/requestheader_controller.go
generated
vendored
Normal file
@ -0,0 +1,337 @@
|
||||
/*
|
||||
Copyright 2020 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 headerrequest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
authenticationRoleName = "extension-apiserver-authentication-reader"
|
||||
)
|
||||
|
||||
// RequestHeaderAuthRequestProvider a provider that knows how to dynamically fill parts of RequestHeaderConfig struct
|
||||
type RequestHeaderAuthRequestProvider interface {
|
||||
UsernameHeaders() []string
|
||||
GroupHeaders() []string
|
||||
ExtraHeaderPrefixes() []string
|
||||
AllowedClientNames() []string
|
||||
}
|
||||
|
||||
var _ RequestHeaderAuthRequestProvider = &RequestHeaderAuthRequestController{}
|
||||
|
||||
type requestHeaderBundle struct {
|
||||
UsernameHeaders []string
|
||||
GroupHeaders []string
|
||||
ExtraHeaderPrefixes []string
|
||||
AllowedClientNames []string
|
||||
}
|
||||
|
||||
// RequestHeaderAuthRequestController a controller that exposes a set of methods for dynamically filling parts of RequestHeaderConfig struct.
|
||||
// The methods are sourced from the config map which is being monitored by this controller.
|
||||
// The controller is primed from the server at the construction time for components that don't want to dynamically react to changes
|
||||
// in the config map.
|
||||
type RequestHeaderAuthRequestController struct {
|
||||
name string
|
||||
|
||||
configmapName string
|
||||
configmapNamespace string
|
||||
|
||||
client kubernetes.Interface
|
||||
configmapLister corev1listers.ConfigMapNamespaceLister
|
||||
configmapInformer cache.SharedIndexInformer
|
||||
configmapInformerSynced cache.InformerSynced
|
||||
|
||||
queue workqueue.RateLimitingInterface
|
||||
|
||||
// exportedRequestHeaderBundle is a requestHeaderBundle that contains the last read, non-zero length content of the configmap
|
||||
exportedRequestHeaderBundle atomic.Value
|
||||
|
||||
usernameHeadersKey string
|
||||
groupHeadersKey string
|
||||
extraHeaderPrefixesKey string
|
||||
allowedClientNamesKey string
|
||||
}
|
||||
|
||||
// NewRequestHeaderAuthRequestController creates a new controller that implements RequestHeaderAuthRequestController
|
||||
func NewRequestHeaderAuthRequestController(
|
||||
cmName string,
|
||||
cmNamespace string,
|
||||
client kubernetes.Interface,
|
||||
usernameHeadersKey, groupHeadersKey, extraHeaderPrefixesKey, allowedClientNamesKey string) *RequestHeaderAuthRequestController {
|
||||
c := &RequestHeaderAuthRequestController{
|
||||
name: "RequestHeaderAuthRequestController",
|
||||
|
||||
client: client,
|
||||
|
||||
configmapName: cmName,
|
||||
configmapNamespace: cmNamespace,
|
||||
|
||||
usernameHeadersKey: usernameHeadersKey,
|
||||
groupHeadersKey: groupHeadersKey,
|
||||
extraHeaderPrefixesKey: extraHeaderPrefixesKey,
|
||||
allowedClientNamesKey: allowedClientNamesKey,
|
||||
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "RequestHeaderAuthRequestController"),
|
||||
}
|
||||
|
||||
// we construct our own informer because we need such a small subset of the information available. Just one namespace.
|
||||
c.configmapInformer = coreinformers.NewFilteredConfigMapInformer(client, c.configmapNamespace, 12*time.Hour, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) {
|
||||
listOptions.FieldSelector = fields.OneTermEqualSelector("metadata.name", c.configmapName).String()
|
||||
})
|
||||
|
||||
c.configmapInformer.AddEventHandler(cache.FilteringResourceEventHandler{
|
||||
FilterFunc: func(obj interface{}) bool {
|
||||
if cast, ok := obj.(*corev1.ConfigMap); ok {
|
||||
return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
|
||||
}
|
||||
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
|
||||
if cast, ok := tombstone.Obj.(*corev1.ConfigMap); ok {
|
||||
return cast.Name == c.configmapName && cast.Namespace == c.configmapNamespace
|
||||
}
|
||||
}
|
||||
return true // always return true just in case. The checks are fairly cheap
|
||||
},
|
||||
Handler: cache.ResourceEventHandlerFuncs{
|
||||
// we have a filter, so any time we're called, we may as well queue. We only ever check one configmap
|
||||
// so we don't have to be choosy about our key.
|
||||
AddFunc: func(obj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
c.queue.Add(c.keyFn())
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
c.configmapLister = corev1listers.NewConfigMapLister(c.configmapInformer.GetIndexer()).ConfigMaps(c.configmapNamespace)
|
||||
c.configmapInformerSynced = c.configmapInformer.HasSynced
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) UsernameHeaders() []string {
|
||||
return c.loadRequestHeaderFor(c.usernameHeadersKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) GroupHeaders() []string {
|
||||
return c.loadRequestHeaderFor(c.groupHeadersKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) ExtraHeaderPrefixes() []string {
|
||||
return c.loadRequestHeaderFor(c.extraHeaderPrefixesKey)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) AllowedClientNames() []string {
|
||||
return c.loadRequestHeaderFor(c.allowedClientNamesKey)
|
||||
}
|
||||
|
||||
// Run starts RequestHeaderAuthRequestController controller and blocks until stopCh is closed.
|
||||
func (c *RequestHeaderAuthRequestController) Run(ctx context.Context, workers int) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
klog.Infof("Starting %s", c.name)
|
||||
defer klog.Infof("Shutting down %s", c.name)
|
||||
|
||||
go c.configmapInformer.Run(ctx.Done())
|
||||
|
||||
// wait for caches to fill before starting your work
|
||||
if !cache.WaitForNamedCacheSync(c.name, ctx.Done(), c.configmapInformerSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
// doesn't matter what workers say, only start one.
|
||||
go wait.Until(c.runWorker, time.Second, ctx.Done())
|
||||
|
||||
<-ctx.Done()
|
||||
}
|
||||
|
||||
// // RunOnce runs a single sync loop
|
||||
func (c *RequestHeaderAuthRequestController) RunOnce(ctx context.Context) error {
|
||||
configMap, err := c.client.CoreV1().ConfigMaps(c.configmapNamespace).Get(ctx, c.configmapName, metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// ignore, authConfigMap is nil now
|
||||
return nil
|
||||
case errors.IsForbidden(err):
|
||||
klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
|
||||
"'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
|
||||
c.configmapName, c.configmapNamespace, c.configmapNamespace, authenticationRoleName)
|
||||
return err
|
||||
case err != nil:
|
||||
return err
|
||||
}
|
||||
return c.syncConfigMap(configMap)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) runWorker() {
|
||||
for c.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) processNextWorkItem() bool {
|
||||
dsKey, quit := c.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer c.queue.Done(dsKey)
|
||||
|
||||
err := c.sync()
|
||||
if err == nil {
|
||||
c.queue.Forget(dsKey)
|
||||
return true
|
||||
}
|
||||
|
||||
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", dsKey, err))
|
||||
c.queue.AddRateLimited(dsKey)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// sync reads the config and propagates the changes to exportedRequestHeaderBundle
|
||||
// which is exposed by the set of methods that are used to fill RequestHeaderConfig struct
|
||||
func (c *RequestHeaderAuthRequestController) sync() error {
|
||||
configMap, err := c.configmapLister.Get(c.configmapName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.syncConfigMap(configMap)
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) syncConfigMap(configMap *corev1.ConfigMap) error {
|
||||
hasChanged, newRequestHeaderBundle, err := c.hasRequestHeaderBundleChanged(configMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasChanged {
|
||||
c.exportedRequestHeaderBundle.Store(newRequestHeaderBundle)
|
||||
klog.V(2).Infof("Loaded a new request header values for %v", c.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) hasRequestHeaderBundleChanged(cm *corev1.ConfigMap) (bool, *requestHeaderBundle, error) {
|
||||
currentHeadersBundle, err := c.getRequestHeaderBundleFromConfigMap(cm)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
||||
rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
|
||||
if rawHeaderBundle == nil {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
|
||||
// check to see if we have a change. If the values are the same, do nothing.
|
||||
loadedHeadersBundle, ok := rawHeaderBundle.(*requestHeaderBundle)
|
||||
if !ok {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
|
||||
if !equality.Semantic.DeepEqual(loadedHeadersBundle, currentHeadersBundle) {
|
||||
return true, currentHeadersBundle, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) getRequestHeaderBundleFromConfigMap(cm *corev1.ConfigMap) (*requestHeaderBundle, error) {
|
||||
usernameHeaderCurrentValue, err := deserializeStrings(cm.Data[c.usernameHeadersKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupHeadersCurrentValue, err := deserializeStrings(cm.Data[c.groupHeadersKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
extraHeaderPrefixesCurrentValue, err := deserializeStrings(cm.Data[c.extraHeaderPrefixesKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
allowedClientNamesCurrentValue, err := deserializeStrings(cm.Data[c.allowedClientNamesKey])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &requestHeaderBundle{
|
||||
UsernameHeaders: usernameHeaderCurrentValue,
|
||||
GroupHeaders: groupHeadersCurrentValue,
|
||||
ExtraHeaderPrefixes: extraHeaderPrefixesCurrentValue,
|
||||
AllowedClientNames: allowedClientNamesCurrentValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) loadRequestHeaderFor(key string) []string {
|
||||
rawHeaderBundle := c.exportedRequestHeaderBundle.Load()
|
||||
if rawHeaderBundle == nil {
|
||||
return nil // this can happen if we've been unable load data from the apiserver for some reason
|
||||
}
|
||||
headerBundle := rawHeaderBundle.(*requestHeaderBundle)
|
||||
|
||||
switch key {
|
||||
case c.usernameHeadersKey:
|
||||
return headerBundle.UsernameHeaders
|
||||
case c.groupHeadersKey:
|
||||
return headerBundle.GroupHeaders
|
||||
case c.extraHeaderPrefixesKey:
|
||||
return headerBundle.ExtraHeaderPrefixes
|
||||
case c.allowedClientNamesKey:
|
||||
return headerBundle.AllowedClientNames
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RequestHeaderAuthRequestController) keyFn() string {
|
||||
// this format matches DeletionHandlingMetaNamespaceKeyFunc for our single key
|
||||
return c.configmapNamespace + "/" + c.configmapName
|
||||
}
|
||||
|
||||
func deserializeStrings(in string) ([]string, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var ret []string
|
||||
if err := json.Unmarshal([]byte(in), &ret); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
71
vendor/k8s.io/apiserver/pkg/authentication/request/union/union.go
generated
vendored
Normal file
71
vendor/k8s.io/apiserver/pkg/authentication/request/union/union.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2014 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 union
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
)
|
||||
|
||||
// unionAuthRequestHandler authenticates requests using a chain of authenticator.Requests
|
||||
type unionAuthRequestHandler struct {
|
||||
// Handlers is a chain of request authenticators to delegate to
|
||||
Handlers []authenticator.Request
|
||||
// FailOnError determines whether an error returns short-circuits the chain
|
||||
FailOnError bool
|
||||
}
|
||||
|
||||
// New returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
|
||||
// The entire chain is tried until one succeeds. If all fail, an aggregate error is returned.
|
||||
func New(authRequestHandlers ...authenticator.Request) authenticator.Request {
|
||||
if len(authRequestHandlers) == 1 {
|
||||
return authRequestHandlers[0]
|
||||
}
|
||||
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: false}
|
||||
}
|
||||
|
||||
// NewFailOnError returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
|
||||
// The first error short-circuits the chain.
|
||||
func NewFailOnError(authRequestHandlers ...authenticator.Request) authenticator.Request {
|
||||
if len(authRequestHandlers) == 1 {
|
||||
return authRequestHandlers[0]
|
||||
}
|
||||
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: true}
|
||||
}
|
||||
|
||||
// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
|
||||
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
var errlist []error
|
||||
for _, currAuthRequestHandler := range authHandler.Handlers {
|
||||
resp, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
|
||||
if err != nil {
|
||||
if authHandler.FailOnError {
|
||||
return resp, ok, err
|
||||
}
|
||||
errlist = append(errlist, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
return resp, ok, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false, utilerrors.NewAggregate(errlist)
|
||||
}
|
108
vendor/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go
generated
vendored
Normal file
108
vendor/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
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 websocket
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
)
|
||||
|
||||
const bearerProtocolPrefix = "base64url.bearer.authorization.k8s.io."
|
||||
|
||||
var protocolHeader = textproto.CanonicalMIMEHeaderKey("Sec-WebSocket-Protocol")
|
||||
|
||||
var errInvalidToken = errors.New("invalid bearer token")
|
||||
|
||||
// ProtocolAuthenticator allows a websocket connection to provide a bearer token as a subprotocol
|
||||
// in the format "base64url.bearer.authorization.<base64url-without-padding(bearer-token)>"
|
||||
type ProtocolAuthenticator struct {
|
||||
// auth is the token authenticator to use to validate the token
|
||||
auth authenticator.Token
|
||||
}
|
||||
|
||||
func NewProtocolAuthenticator(auth authenticator.Token) *ProtocolAuthenticator {
|
||||
return &ProtocolAuthenticator{auth}
|
||||
}
|
||||
|
||||
func (a *ProtocolAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
// Only accept websocket connections
|
||||
if !wsstream.IsWebSocketRequest(req) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
token := ""
|
||||
sawTokenProtocol := false
|
||||
filteredProtocols := []string{}
|
||||
for _, protocolHeader := range req.Header[protocolHeader] {
|
||||
for _, protocol := range strings.Split(protocolHeader, ",") {
|
||||
protocol = strings.TrimSpace(protocol)
|
||||
|
||||
if !strings.HasPrefix(protocol, bearerProtocolPrefix) {
|
||||
filteredProtocols = append(filteredProtocols, protocol)
|
||||
continue
|
||||
}
|
||||
|
||||
if sawTokenProtocol {
|
||||
return nil, false, errors.New("multiple base64.bearer.authorization tokens specified")
|
||||
}
|
||||
sawTokenProtocol = true
|
||||
|
||||
encodedToken := strings.TrimPrefix(protocol, bearerProtocolPrefix)
|
||||
decodedToken, err := base64.RawURLEncoding.DecodeString(encodedToken)
|
||||
if err != nil {
|
||||
return nil, false, errors.New("invalid base64.bearer.authorization token encoding")
|
||||
}
|
||||
if !utf8.Valid(decodedToken) {
|
||||
return nil, false, errors.New("invalid base64.bearer.authorization token")
|
||||
}
|
||||
token = string(decodedToken)
|
||||
}
|
||||
}
|
||||
|
||||
// Must pass at least one other subprotocol so that we can remove the one containing the bearer token,
|
||||
// and there is at least one to echo back to the client
|
||||
if len(token) > 0 && len(filteredProtocols) == 0 {
|
||||
return nil, false, errors.New("missing additional subprotocol")
|
||||
}
|
||||
|
||||
if len(token) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
|
||||
|
||||
// on success, remove the protocol with the token
|
||||
if ok {
|
||||
// https://tools.ietf.org/html/rfc6455#section-11.3.4 indicates the Sec-WebSocket-Protocol header may appear multiple times
|
||||
// in a request, and is logically the same as a single Sec-WebSocket-Protocol header field that contains all values
|
||||
req.Header.Set(protocolHeader, strings.Join(filteredProtocols, ","))
|
||||
}
|
||||
|
||||
// If the token authenticator didn't error, provide a default error
|
||||
if !ok && err == nil {
|
||||
err = errInvalidToken
|
||||
}
|
||||
|
||||
return resp, ok, err
|
||||
}
|
8
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
Normal file
8
vendor/k8s.io/apiserver/pkg/authentication/request/x509/OWNERS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- sig-auth-certificates-approvers
|
||||
reviewers:
|
||||
- sig-auth-certificates-reviewers
|
||||
labels:
|
||||
- sig/auth
|
19
vendor/k8s.io/apiserver/pkg/authentication/request/x509/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/pkg/authentication/request/x509/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2014 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 provides a request authenticator that validates and
|
||||
// extracts user information from client certificates
|
||||
package x509 // import "k8s.io/apiserver/pkg/authentication/request/x509"
|
71
vendor/k8s.io/apiserver/pkg/authentication/request/x509/verify_options.go
generated
vendored
Normal file
71
vendor/k8s.io/apiserver/pkg/authentication/request/x509/verify_options.go
generated
vendored
Normal 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
|
||||
}
|
258
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
Normal file
258
vendor/k8s.io/apiserver/pkg/authentication/request/x509/x509.go
generated
vendored
Normal file
@ -0,0 +1,258 @@
|
||||
/*
|
||||
Copyright 2014 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"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
/*
|
||||
* By default, the following metric is defined as falling under
|
||||
* ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/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",
|
||||
Help: "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
|
||||
Buckets: []float64{
|
||||
0,
|
||||
1800, // 30 minutes
|
||||
3600, // 1 hour
|
||||
7200, // 2 hours
|
||||
21600, // 6 hours
|
||||
43200, // 12 hours
|
||||
86400, // 1 day
|
||||
172800, // 2 days
|
||||
345600, // 4 days
|
||||
604800, // 1 week
|
||||
2592000, // 1 month
|
||||
7776000, // 3 months
|
||||
15552000, // 6 months
|
||||
31104000, // 1 year
|
||||
},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
)
|
||||
|
||||
func init() {
|
||||
legacyregistry.MustRegister(clientCertificateExpirationHistogram)
|
||||
}
|
||||
|
||||
// UserConversion defines an interface for extracting user info from a client certificate chain
|
||||
type UserConversion interface {
|
||||
User(chain []*x509.Certificate) (*authenticator.Response, bool, error)
|
||||
}
|
||||
|
||||
// UserConversionFunc is a function that implements the UserConversion interface.
|
||||
type UserConversionFunc func(chain []*x509.Certificate) (*authenticator.Response, bool, error)
|
||||
|
||||
// User implements x509.UserConversion
|
||||
func (f UserConversionFunc) User(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
|
||||
return f(chain)
|
||||
}
|
||||
|
||||
func columnSeparatedHex(d []byte) string {
|
||||
h := strings.ToUpper(hex.EncodeToString(d))
|
||||
var sb strings.Builder
|
||||
for i, r := range h {
|
||||
sb.WriteRune(r)
|
||||
if i%2 == 1 && i != len(h)-1 {
|
||||
sb.WriteRune(':')
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func certificateIdentifier(c *x509.Certificate) string {
|
||||
return fmt.Sprintf(
|
||||
"SN=%d, SKID=%s, AKID=%s",
|
||||
c.SerialNumber,
|
||||
columnSeparatedHex(c.SubjectKeyId),
|
||||
columnSeparatedHex(c.AuthorityKeyId),
|
||||
)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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 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
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Use intermediates, if provided
|
||||
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:] {
|
||||
optsCopy.Intermediates.AddCert(intermediate)
|
||||
}
|
||||
}
|
||||
|
||||
remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
|
||||
clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
|
||||
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf(
|
||||
"verifying certificate %s failed: %w",
|
||||
certificateIdentifier(req.TLS.PeerCertificates[0]),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
var errlist []error
|
||||
for _, chain := range chains {
|
||||
user, ok, err := a.user.User(chain)
|
||||
if err != nil {
|
||||
errlist = append(errlist, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if ok {
|
||||
return user, ok, err
|
||||
}
|
||||
}
|
||||
return nil, false, utilerrors.NewAggregate(errlist)
|
||||
}
|
||||
|
||||
// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
|
||||
type Verifier struct {
|
||||
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 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 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
|
||||
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
|
||||
func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Use intermediates, if provided
|
||||
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:] {
|
||||
optsCopy.Intermediates.AddCert(intermediate)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return a.auth.AuthenticateRequest(req)
|
||||
}
|
||||
|
||||
func (a *Verifier) verifySubject(subject pkix.Name) error {
|
||||
// No CN restrictions
|
||||
if len(a.allowedCommonNames.Value()) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Enforce CN restrictions
|
||||
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)
|
||||
}
|
||||
|
||||
// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
|
||||
// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
|
||||
func DefaultVerifyOptions() x509.VerifyOptions {
|
||||
return x509.VerifyOptions{
|
||||
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
}
|
||||
|
||||
// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
|
||||
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (*authenticator.Response, bool, error) {
|
||||
if len(chain[0].Subject.CommonName) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: chain[0].Subject.CommonName,
|
||||
Groups: chain[0].Subject.Organization,
|
||||
},
|
||||
}, true, nil
|
||||
})
|
Reference in New Issue
Block a user