Merge pull request #35452 from deads2k/auth-02-front-proxy

Automatic merge from submit-queue

allow authentication through a front-proxy

This allows a front proxy to set a request header and have that be a valid `user.Info` in the authentication chain.  To secure this power, a client certificate may be used to confirm the identity of the front proxy

@kubernetes/sig-auth fyi
@erictune per-request
@liggitt you wrote the openshift one, ptal.
pull/6/head
Kubernetes Submit Queue 2016-10-29 07:52:09 -07:00 committed by GitHub
commit 4ec036c8af
15 changed files with 585 additions and 59 deletions

View File

@ -232,6 +232,7 @@ func Run(s *options.APIServer) error {
KeystoneURL: s.KeystoneURL,
WebhookTokenAuthnConfigFile: s.WebhookTokenAuthnConfigFile,
WebhookTokenAuthnCacheTTL: s.WebhookTokenAuthnCacheTTL,
RequestHeaderConfig: s.AuthenticationRequestHeaderConfig(),
})
if err != nil {

View File

@ -117,17 +117,18 @@ func Run(s *options.ServerRunOptions) error {
}
apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{
Anonymous: s.AnonymousAuth,
AnyToken: s.EnableAnyToken,
BasicAuthFile: s.BasicAuthFile,
ClientCAFile: s.ClientCAFile,
TokenAuthFile: s.TokenAuthFile,
OIDCIssuerURL: s.OIDCIssuerURL,
OIDCClientID: s.OIDCClientID,
OIDCCAFile: s.OIDCCAFile,
OIDCUsernameClaim: s.OIDCUsernameClaim,
OIDCGroupsClaim: s.OIDCGroupsClaim,
KeystoneURL: s.KeystoneURL,
Anonymous: s.AnonymousAuth,
AnyToken: s.EnableAnyToken,
BasicAuthFile: s.BasicAuthFile,
ClientCAFile: s.ClientCAFile,
TokenAuthFile: s.TokenAuthFile,
OIDCIssuerURL: s.OIDCIssuerURL,
OIDCClientID: s.OIDCClientID,
OIDCCAFile: s.OIDCCAFile,
OIDCUsernameClaim: s.OIDCUsernameClaim,
OIDCGroupsClaim: s.OIDCGroupsClaim,
KeystoneURL: s.KeystoneURL,
RequestHeaderConfig: s.AuthenticationRequestHeaderConfig(),
})
if err != nil {
glog.Fatalf("Invalid Authentication Config: %v", err)

View File

@ -464,6 +464,9 @@ replication-controller-lookup-cache-size
repo-root
report-dir
report-prefix
requestheader-allowed-names
requestheader-client-ca-file
requestheader-username-headers
require-kubeconfig
required-contexts
resolv-conf

View File

@ -25,6 +25,7 @@ go_library(
"//plugin/pkg/auth/authenticator/password/passwordfile:go_default_library",
"//plugin/pkg/auth/authenticator/request/anonymous:go_default_library",
"//plugin/pkg/auth/authenticator/request/basicauth:go_default_library",
"//plugin/pkg/auth/authenticator/request/headerrequest:go_default_library",
"//plugin/pkg/auth/authenticator/request/union:go_default_library",
"//plugin/pkg/auth/authenticator/request/x509:go_default_library",
"//plugin/pkg/auth/authenticator/token/anytoken:go_default_library",

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken"
@ -39,6 +40,15 @@ import (
"k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook"
)
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
// ClientCA points to CA bundle file which is used verify the identity of the front proxy
ClientCA string
// AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any.
AllowedClientNames []string
}
type AuthenticatorConfig struct {
Anonymous bool
AnyToken bool
@ -56,6 +66,8 @@ type AuthenticatorConfig struct {
KeystoneURL string
WebhookTokenAuthnConfigFile string
WebhookTokenAuthnCacheTTL time.Duration
RequestHeaderConfig *RequestHeaderConfig
}
// New returns an authenticator.Request or an error that supports the standard
@ -66,7 +78,20 @@ func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefin
hasBasicAuth := false
hasTokenAuth := false
// BasicAuth methods, local first, then remote
// front-proxy, BasicAuth methods, local first, then remote
// Add the front proxy authenticator if requested
if config.RequestHeaderConfig != nil {
requestHeaderAuthenticator, err := headerrequest.NewSecure(
config.RequestHeaderConfig.ClientCA,
config.RequestHeaderConfig.AllowedClientNames,
config.RequestHeaderConfig.UsernameHeaders,
)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, requestHeaderAuthenticator)
}
if len(config.BasicAuthFile) > 0 {
basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
if err != nil {

View File

@ -13,6 +13,7 @@ load(
go_library(
name = "go_default_library",
srcs = [
"authenticator.go",
"doc.go",
"etcd_options.go",
"server_run_options.go",
@ -23,6 +24,7 @@ go_library(
"//pkg/api:go_default_library",
"//pkg/api/unversioned:go_default_library",
"//pkg/apimachinery/registered:go_default_library",
"//pkg/apiserver/authenticator:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/storage/storagebackend:go_default_library",

View File

@ -0,0 +1,35 @@
/*
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 options
import (
"k8s.io/kubernetes/pkg/apiserver/authenticator"
)
// AuthenticationRequestHeaderConfig returns an authenticator config object for these options
// if necessary. nil otherwise.
func (s *ServerRunOptions) AuthenticationRequestHeaderConfig() *authenticator.RequestHeaderConfig {
if len(s.RequestHeaderUsernameHeaders) == 0 {
return nil
}
return &authenticator.RequestHeaderConfig{
UsernameHeaders: s.RequestHeaderUsernameHeaders,
ClientCA: s.RequestHeaderClientCAFile,
AllowedClientNames: s.RequestHeaderAllowedNames,
}
}

View File

@ -67,46 +67,49 @@ type ServerRunOptions struct {
AuthorizationWebhookCacheUnauthorizedTTL time.Duration
AuthorizationRBACSuperUser string
AnonymousAuth bool
BasicAuthFile string
BindAddress net.IP
CertDirectory string
ClientCAFile string
CloudConfigFile string
CloudProvider string
CorsAllowedOriginList []string
DefaultStorageMediaType string
DeleteCollectionWorkers int
AuditLogPath string
AuditLogMaxAge int
AuditLogMaxBackups int
AuditLogMaxSize int
EnableGarbageCollection bool
EnableProfiling bool
EnableSwaggerUI bool
EnableWatchCache bool
EtcdServersOverrides []string
StorageConfig storagebackend.Config
ExternalHost string
InsecureBindAddress net.IP
InsecurePort int
KeystoneURL string
KubernetesServiceNodePort int
LongRunningRequestRE string
MasterCount int
MasterServiceNamespace string
MaxRequestsInFlight int
MinRequestTimeout int
OIDCCAFile string
OIDCClientID string
OIDCIssuerURL string
OIDCUsernameClaim string
OIDCGroupsClaim string
RuntimeConfig config.ConfigurationMap
SecurePort int
ServiceClusterIPRange net.IPNet // TODO: make this a list
ServiceNodePortRange utilnet.PortRange
StorageVersions string
AnonymousAuth bool
BasicAuthFile string
BindAddress net.IP
CertDirectory string
ClientCAFile string
CloudConfigFile string
CloudProvider string
CorsAllowedOriginList []string
DefaultStorageMediaType string
DeleteCollectionWorkers int
AuditLogPath string
AuditLogMaxAge int
AuditLogMaxBackups int
AuditLogMaxSize int
EnableGarbageCollection bool
EnableProfiling bool
EnableSwaggerUI bool
EnableWatchCache bool
EtcdServersOverrides []string
StorageConfig storagebackend.Config
ExternalHost string
InsecureBindAddress net.IP
InsecurePort int
KeystoneURL string
KubernetesServiceNodePort int
LongRunningRequestRE string
MasterCount int
MasterServiceNamespace string
MaxRequestsInFlight int
MinRequestTimeout int
OIDCCAFile string
OIDCClientID string
OIDCIssuerURL string
OIDCUsernameClaim string
OIDCGroupsClaim string
RequestHeaderUsernameHeaders []string
RequestHeaderClientCAFile string
RequestHeaderAllowedNames []string
RuntimeConfig config.ConfigurationMap
SecurePort int
ServiceClusterIPRange net.IPNet // TODO: make this a list
ServiceNodePortRange utilnet.PortRange
StorageVersions string
// The default values for StorageVersions. StorageVersions overrides
// these; you can change this if you want to change the defaults (e.g.,
// for testing). This is not actually exposed as a flag.
@ -423,6 +426,18 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
"The claim value is expected to be a string or array of strings. This flag is experimental, "+
"please see the authentication documentation for further details.")
fs.StringSliceVar(&s.RequestHeaderUsernameHeaders, "requestheader-username-headers", s.RequestHeaderUsernameHeaders, ""+
"List of request headers to inspect for usernames. X-Remote-User is common.")
fs.StringVar(&s.RequestHeaderClientCAFile, "requestheader-client-ca-file", s.RequestHeaderClientCAFile, ""+
"Root certificate bundle to use to verify client certificates on incoming requests "+
"before trusting usernames in headers specified by --requestheader-username-headers")
fs.StringSliceVar(&s.RequestHeaderAllowedNames, "requestheader-allowed-names", s.RequestHeaderAllowedNames, ""+
"List of client certificate common names to allow to provide usernames in headers "+
"specified by --requestheader-username-headers. If empty, any client certificate validated "+
"by the authorities in --requestheader-client-ca-file is allowed.")
fs.Var(&s.RuntimeConfig, "runtime-config", ""+
"A set of key=value pairs that describe runtime configuration that may be passed "+
"to apiserver. apis/<groupVersion> key can be used to turn on/off specific api versions. "+

View File

@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["requestheader.go"],
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/cert:go_default_library",
"//pkg/util/sets:go_default_library",
"//plugin/pkg/auth/authenticator/request/x509:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["requestheader_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View File

@ -0,0 +1,96 @@
/*
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"
"strings"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
utilcert "k8s.io/kubernetes/pkg/util/cert"
"k8s.io/kubernetes/pkg/util/sets"
x509request "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
)
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
}
func New(nameHeaders []string) (authenticator.Request, error) {
headers := []string{}
for _, headerName := range nameHeaders {
trimmedHeader := strings.TrimSpace(headerName)
if len(trimmedHeader) == 0 {
return nil, fmt.Errorf("empty header %q", headerName)
}
headers = append(headers, trimmedHeader)
}
return &requestHeaderAuthRequestHandler{nameHeaders: headers}, nil
}
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string) (authenticator.Request, error) {
headerAuthenticator, err := New(nameHeaders)
if err != nil {
return nil, err
}
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)
}
return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil
}
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
name := headerValue(req.Header, a.nameHeaders)
if len(name) == 0 {
return nil, false, nil
}
return &user.DefaultInfo{Name: name}, 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 ""
}

View File

@ -0,0 +1,89 @@
/*
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 (
"net/http"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/auth/user"
)
func TestRequestHeader(t *testing.T) {
testcases := map[string]struct {
nameHeaders []string
requestHeaders http.Header
expectedUser user.Info
expectedOk bool
}{
"empty": {},
"no match": {
nameHeaders: []string{"X-Remote-User"},
},
"match": {
nameHeaders: []string{"X-Remote-User"},
requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
expectedUser: &user.DefaultInfo{Name: "Bob"},
expectedOk: true,
},
"exact match": {
nameHeaders: []string{"X-Remote-User"},
requestHeaders: http.Header{
"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
"X-Remote-User-With-Suffix": {"Bob"},
},
},
"first match": {
nameHeaders: []string{
"X-Remote-User",
"A-Second-X-Remote-User",
"Another-X-Remote-User",
},
requestHeaders: http.Header{
"X-Remote-User": {"", "First header, second value"},
"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
"Another-X-Remote-User": {"Third header, first value"}},
expectedUser: &user.DefaultInfo{Name: "Second header, first value"},
expectedOk: true,
},
"case-insensitive": {
nameHeaders: []string{"x-REMOTE-user"}, // configured headers can be case-insensitive
requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package
expectedUser: &user.DefaultInfo{Name: "Bob"},
expectedOk: true,
},
}
for k, testcase := range testcases {
auth, err := New(testcase.nameHeaders)
if err != nil {
t.Fatal(err)
}
req := &http.Request{Header: testcase.requestHeaders}
user, ok, _ := auth.AuthenticateRequest(req)
if testcase.expectedOk != ok {
t.Errorf("%v: expected %v, got %v", k, testcase.expectedOk, ok)
}
if e, a := testcase.expectedUser, user; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected %#v, got %#v", k, e, a)
}
}
}

View File

@ -18,8 +18,11 @@ go_library(
],
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/errors:go_default_library",
"//pkg/util/sets:go_default_library",
"//vendor:github.com/golang/glog",
],
)
@ -29,5 +32,9 @@ go_test(
data = glob(["testdata/*"]),
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/sets:go_default_library",
],
)

View File

@ -18,11 +18,17 @@ package x509
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"net/http"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
)
// UserConversion defines an interface for extracting user info from a client certificate chain
@ -85,6 +91,58 @@ func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool,
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 {
opts x509.VerifyOptions
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
}
// 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}
}
// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// Use intermediates, if provided
optsCopy := a.opts
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) == 0 {
return nil
}
// Enforce CN restrictions
if a.allowedCommonNames.Has(subject.CommonName) {
return nil
}
glog.Warningf("x509: subject with cn=%s is not in the allowed list: %v", subject.CommonName, a.allowedCommonNames.List())
return fmt.Errorf("x509: subject with cn=%s is not allowed", 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 {

View File

@ -28,7 +28,9 @@ import (
"testing"
"time"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/util/sets"
)
const (
@ -373,12 +375,12 @@ mFlG6tStAWz3TmydciZNdiEbeqHw5uaIYWj1zC5AdvFXBFue0ojIrJ5JtbTWccH9
`
/*
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 36500 \
-sha256 -key ca.key -extensions v3_ca \
-out ca.crt \
-subj "/C=US/ST=My State/L=My City/O=My Org/O=My Org 1/O=My Org 2/CN=ROOT CA WITH GROUPS"
openssl x509 -in ca.crt -text
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 36500 \
-sha256 -key ca.key -extensions v3_ca \
-out ca.crt \
-subj "/C=US/ST=My State/L=My City/O=My Org/O=My Org 1/O=My Org 2/CN=ROOT CA WITH GROUPS"
openssl x509 -in ca.crt -text
*/
// A certificate with multiple organizations.
@ -723,6 +725,164 @@ func TestX509(t *testing.T) {
}
}
func TestX509Verifier(t *testing.T) {
multilevelOpts := DefaultVerifyOptions()
multilevelOpts.Roots = x509.NewCertPool()
multilevelOpts.Roots.AddCert(getCertsFromFile(t, "root")[0])
testCases := map[string]struct {
Insecure bool
Certs []*x509.Certificate
Opts x509.VerifyOptions
AllowedCNs sets.String
ExpectOK bool
ExpectErr bool
}{
"non-tls": {
Insecure: true,
ExpectOK: false,
ExpectErr: false,
},
"tls, no certs": {
ExpectOK: false,
ExpectErr: false,
},
"self signed": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, selfSignedCert),
ExpectErr: true,
},
"server cert disallowed": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, serverCert),
ExpectErr: true,
},
"server cert allowing non-client cert usages": {
Opts: x509.VerifyOptions{Roots: getRootCertPool(t)},
Certs: getCerts(t, serverCert),
ExpectOK: true,
ExpectErr: false,
},
"valid client cert": {
Opts: getDefaultVerifyOptions(t),
Certs: getCerts(t, clientCNCert),
ExpectOK: true,
ExpectErr: false,
},
"valid client cert with wrong CN": {
Opts: getDefaultVerifyOptions(t),
AllowedCNs: sets.NewString("foo", "bar"),
Certs: getCerts(t, clientCNCert),
ExpectOK: false,
ExpectErr: true,
},
"valid client cert with right CN": {
Opts: getDefaultVerifyOptions(t),
AllowedCNs: sets.NewString("client_cn"),
Certs: getCerts(t, clientCNCert),
ExpectOK: true,
ExpectErr: false,
},
"future cert": {
Opts: x509.VerifyOptions{
CurrentTime: time.Now().Add(-100 * time.Hour * 24 * 365),
Roots: getRootCertPool(t),
},
Certs: getCerts(t, clientCNCert),
ExpectOK: false,
ExpectErr: true,
},
"expired cert": {
Opts: x509.VerifyOptions{
CurrentTime: time.Now().Add(100 * time.Hour * 24 * 365),
Roots: getRootCertPool(t),
},
Certs: getCerts(t, clientCNCert),
ExpectOK: false,
ExpectErr: true,
},
"multi-level, valid": {
Opts: multilevelOpts,
Certs: getCertsFromFile(t, "client-valid", "intermediate"),
ExpectOK: true,
ExpectErr: false,
},
"multi-level, expired": {
Opts: multilevelOpts,
Certs: getCertsFromFile(t, "client-expired", "intermediate"),
ExpectOK: false,
ExpectErr: true,
},
}
for k, testCase := range testCases {
req, _ := http.NewRequest("GET", "/", nil)
if !testCase.Insecure {
req.TLS = &tls.ConnectionState{PeerCertificates: testCase.Certs}
}
authCall := false
auth := authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
authCall = true
return &user.DefaultInfo{Name: "innerauth"}, true, nil
})
a := NewVerifier(testCase.Opts, auth, testCase.AllowedCNs)
user, ok, err := a.AuthenticateRequest(req)
if testCase.ExpectErr && err == nil {
t.Errorf("%s: Expected error, got none", k)
continue
}
if !testCase.ExpectErr && err != nil {
t.Errorf("%s: Got unexpected error: %v", k, err)
continue
}
if testCase.ExpectOK != ok {
t.Errorf("%s: Expected ok=%v, got %v", k, testCase.ExpectOK, ok)
continue
}
if testCase.ExpectOK {
if !authCall {
t.Errorf("%s: Expected inner auth called, wasn't", k)
continue
}
if "innerauth" != user.GetName() {
t.Errorf("%s: Expected user.name=%v, got %v", k, "innerauth", user.GetName())
continue
}
} else {
if authCall {
t.Errorf("%s: Expected inner auth not to be called, was", k)
continue
}
}
}
}
func getDefaultVerifyOptions(t *testing.T) x509.VerifyOptions {
options := DefaultVerifyOptions()
options.Roots = getRootCertPool(t)

View File

@ -844,6 +844,7 @@ k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow,liggitt,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile,liggitt,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous,justinsb,1
k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth,liggitt,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest,deads2k,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union,liggitt,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509,liggitt,0
k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken,krousey,1

1 name owner auto-assigned
844 k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile liggitt 0
845 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous justinsb 1
846 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth liggitt 0
847 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest deads2k 0
848 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union liggitt 0
849 k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509 liggitt 0
850 k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken krousey 1