vendor: Update vendoring for the exec client and server implementations
Signed-off-by: Jacek J. Łakis <jacek.lakis@intel.com> Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
This commit is contained in:
parent
d25b88583f
commit
bf51655a7b
2124 changed files with 809703 additions and 5 deletions
18
vendor/k8s.io/apiserver/plugin/pkg/authenticator/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/apiserver/plugin/pkg/authenticator/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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 authenticator contains implementations for pkg/auth/authenticator interfaces
|
||||
package authenticator // import "k8s.io/apiserver/plugin/pkg/authenticator"
|
38
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/allow/allow.go
generated
vendored
Normal file
38
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/allow/allow.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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 allow
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type allowAuthenticator struct{}
|
||||
|
||||
// NewAllow returns a password authenticator that allows any non-empty username
|
||||
func NewAllow() authenticator.Password {
|
||||
return allowAuthenticator{}
|
||||
}
|
||||
|
||||
// AuthenticatePassword implements authenticator.Password to allow any non-empty username,
|
||||
// using the specified username as the name and UID
|
||||
func (allowAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) {
|
||||
if username == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &user.DefaultInfo{Name: username, UID: username}, true, nil
|
||||
}
|
47
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/allow/allow_test.go
generated
vendored
Normal file
47
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/allow/allow_test.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
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 allow
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAllowEmpty(t *testing.T) {
|
||||
allow := NewAllow()
|
||||
user, ok, err := allow.AuthenticatePassword("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("Unexpected success")
|
||||
}
|
||||
if user != nil {
|
||||
t.Fatalf("Unexpected user: %v", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowPresent(t *testing.T) {
|
||||
allow := NewAllow()
|
||||
user, ok, err := allow.AuthenticatePassword("myuser", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected failure")
|
||||
}
|
||||
if user.GetName() != "myuser" || user.GetUID() != "myuser" {
|
||||
t.Fatalf("Unexpected user name or uid: %v", user)
|
||||
}
|
||||
}
|
18
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
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 password contains authenticator.Password implementations
|
||||
package password // import "k8s.io/apiserver/plugin/pkg/authenticator/password"
|
20
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/keystone/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/keystone/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
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 keystone provides authentication via keystone.
|
||||
// For details about keystone and how to use the plugin, refer to
|
||||
// https://github.com/kubernetes/kubernetes.github.io/blob/master/docs/admin/authentication.md
|
||||
package keystone // import "k8s.io/apiserver/plugin/pkg/authenticator/password/keystone"
|
94
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/keystone/keystone.go
generated
vendored
Normal file
94
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/keystone/keystone.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
Copyright 2015 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 keystone
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack"
|
||||
|
||||
netutil "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
// KeystoneAuthenticator contacts openstack keystone to validate user's credentials passed in the request.
|
||||
// The keystone endpoint is passed during apiserver startup
|
||||
type KeystoneAuthenticator struct {
|
||||
authURL string
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
// AuthenticatePassword checks the username, password via keystone call
|
||||
func (keystoneAuthenticator *KeystoneAuthenticator) AuthenticatePassword(username string, password string) (user.Info, bool, error) {
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: keystoneAuthenticator.authURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
_, err := keystoneAuthenticator.AuthenticatedClient(opts)
|
||||
if err != nil {
|
||||
glog.Info("Failed: Starting openstack authenticate client:" + err.Error())
|
||||
return nil, false, errors.New("Failed to authenticate")
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{Name: username}, true, nil
|
||||
}
|
||||
|
||||
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a
|
||||
// token, and returns a Client instance that's ready to operate.
|
||||
func (keystoneAuthenticator *KeystoneAuthenticator) AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
||||
client, err := openstack.NewClient(options.IdentityEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keystoneAuthenticator.transport != nil {
|
||||
client.HTTPClient.Transport = keystoneAuthenticator.transport
|
||||
}
|
||||
|
||||
err = openstack.Authenticate(client, options)
|
||||
return client, err
|
||||
}
|
||||
|
||||
// NewKeystoneAuthenticator returns a password authenticator that validates credentials using openstack keystone
|
||||
func NewKeystoneAuthenticator(authURL string, caFile string) (*KeystoneAuthenticator, error) {
|
||||
if !strings.HasPrefix(authURL, "https") {
|
||||
return nil, errors.New("Auth URL should be secure and start with https")
|
||||
}
|
||||
if authURL == "" {
|
||||
return nil, errors.New("Auth URL is empty")
|
||||
}
|
||||
if caFile != "" {
|
||||
roots, err := certutil.NewPool(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &tls.Config{}
|
||||
config.RootCAs = roots
|
||||
transport := netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config})
|
||||
return &KeystoneAuthenticator{authURL, transport}, nil
|
||||
}
|
||||
|
||||
return &KeystoneAuthenticator{authURL: authURL}, nil
|
||||
}
|
90
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile/passwordfile.go
generated
vendored
Normal file
90
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile/passwordfile.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2015 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 passwordfile
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type PasswordAuthenticator struct {
|
||||
users map[string]*userPasswordInfo
|
||||
}
|
||||
|
||||
type userPasswordInfo struct {
|
||||
info *user.DefaultInfo
|
||||
password string
|
||||
}
|
||||
|
||||
// NewCSV returns a PasswordAuthenticator, populated from a CSV file.
|
||||
// The CSV file must contain records in the format "password,username,useruid"
|
||||
func NewCSV(path string) (*PasswordAuthenticator, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
recordNum := 0
|
||||
users := make(map[string]*userPasswordInfo)
|
||||
reader := csv.NewReader(file)
|
||||
reader.FieldsPerRecord = -1
|
||||
for {
|
||||
record, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(record) < 3 {
|
||||
return nil, fmt.Errorf("password file '%s' must have at least 3 columns (password, user name, user uid), found %d", path, len(record))
|
||||
}
|
||||
obj := &userPasswordInfo{
|
||||
info: &user.DefaultInfo{Name: record[1], UID: record[2]},
|
||||
password: record[0],
|
||||
}
|
||||
if len(record) >= 4 {
|
||||
obj.info.Groups = strings.Split(record[3], ",")
|
||||
}
|
||||
recordNum++
|
||||
if _, exist := users[obj.info.Name]; exist {
|
||||
glog.Warningf("duplicate username '%s' has been found in password file '%s', record number '%d'", obj.info.Name, path, recordNum)
|
||||
}
|
||||
users[obj.info.Name] = obj
|
||||
}
|
||||
|
||||
return &PasswordAuthenticator{users}, nil
|
||||
}
|
||||
|
||||
func (a *PasswordAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) {
|
||||
user, ok := a.users[username]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if user.password != password {
|
||||
return nil, false, nil
|
||||
}
|
||||
return user.info, true, nil
|
||||
}
|
160
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile/passwordfile_test.go
generated
vendored
Normal file
160
vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile/passwordfile_test.go
generated
vendored
Normal file
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
Copyright 2015 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 passwordfile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
func TestPasswordFile(t *testing.T) {
|
||||
auth, err := newWithContents(t, `
|
||||
password1,user1,uid1
|
||||
password2,user2,uid2
|
||||
password3,user3,uid3,"group1,group2"
|
||||
password4,user4,uid4,"group2"
|
||||
password5,user5,uid5,group5
|
||||
password6,user6,uid6,group5,otherdata
|
||||
password7,user7,uid7,"group1,group2",otherdata
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read passwordfile: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Username string
|
||||
Password string
|
||||
User *user.DefaultInfo
|
||||
Ok bool
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
Username: "user1",
|
||||
Password: "password1",
|
||||
User: &user.DefaultInfo{Name: "user1", UID: "uid1"},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user2",
|
||||
Password: "password2",
|
||||
User: &user.DefaultInfo{Name: "user2", UID: "uid2"},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user1",
|
||||
Password: "password2",
|
||||
},
|
||||
{
|
||||
Username: "user2",
|
||||
Password: "password1",
|
||||
},
|
||||
{
|
||||
Username: "user3",
|
||||
Password: "password3",
|
||||
User: &user.DefaultInfo{Name: "user3", UID: "uid3", Groups: []string{"group1", "group2"}},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user4",
|
||||
Password: "password4",
|
||||
User: &user.DefaultInfo{Name: "user4", UID: "uid4", Groups: []string{"group2"}},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user5",
|
||||
Password: "password5",
|
||||
User: &user.DefaultInfo{Name: "user5", UID: "uid5", Groups: []string{"group5"}},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user6",
|
||||
Password: "password6",
|
||||
User: &user.DefaultInfo{Name: "user6", UID: "uid6", Groups: []string{"group5"}},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user7",
|
||||
Password: "password7",
|
||||
User: &user.DefaultInfo{Name: "user7", UID: "uid7", Groups: []string{"group1", "group2"}},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user7",
|
||||
Password: "passwordbad",
|
||||
},
|
||||
{
|
||||
Username: "userbad",
|
||||
Password: "password7",
|
||||
},
|
||||
{
|
||||
Username: "user8",
|
||||
Password: "password8",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
user, ok, err := auth.AuthenticatePassword(testCase.Username, testCase.Password)
|
||||
if err != nil {
|
||||
t.Errorf("%d: unexpected error: %v", i, err)
|
||||
}
|
||||
if testCase.User == nil {
|
||||
if user != nil {
|
||||
t.Errorf("%d: unexpected non-nil user %#v", i, user)
|
||||
}
|
||||
} else if !reflect.DeepEqual(testCase.User, user) {
|
||||
t.Errorf("%d: expected user %#v, got %#v", i, testCase.User, user)
|
||||
}
|
||||
if testCase.Ok != ok {
|
||||
t.Errorf("%d: expected auth %v, got %v", i, testCase.Ok, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadPasswordFile(t *testing.T) {
|
||||
if _, err := newWithContents(t, `
|
||||
password1,user1,uid1
|
||||
password2,user2,uid2
|
||||
password3,user3
|
||||
password4
|
||||
`); err == nil {
|
||||
t.Fatalf("unexpected non error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsufficientColumnsPasswordFile(t *testing.T) {
|
||||
if _, err := newWithContents(t, "password4\n"); err == nil {
|
||||
t.Fatalf("unexpected non error")
|
||||
}
|
||||
}
|
||||
|
||||
func newWithContents(t *testing.T, contents string) (auth *PasswordAuthenticator, err error) {
|
||||
f, err := ioutil.TempFile("", "passwordfile_test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating passwordfile: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
|
||||
t.Fatalf("unexpected error writing passwordfile: %v", err)
|
||||
}
|
||||
|
||||
return NewCSV(f.Name())
|
||||
}
|
43
vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth.go
generated
vendored
Normal file
43
vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
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 basicauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// Authenticator authenticates requests using basic auth
|
||||
type Authenticator struct {
|
||||
auth authenticator.Password
|
||||
}
|
||||
|
||||
// New returns a request authenticator that validates credentials using the provided password authenticator
|
||||
func New(auth authenticator.Password) *Authenticator {
|
||||
return &Authenticator{auth}
|
||||
}
|
||||
|
||||
// AuthenticateRequest authenticates the request using the "Authorization: Basic" header in the request
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||
username, password, found := req.BasicAuth()
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
return a.auth.AuthenticatePassword(username, password)
|
||||
}
|
123
vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth_test.go
generated
vendored
Normal file
123
vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth/basicauth_test.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
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 basicauth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type testPassword struct {
|
||||
Username string
|
||||
Password string
|
||||
Called bool
|
||||
|
||||
User user.Info
|
||||
OK bool
|
||||
Err error
|
||||
}
|
||||
|
||||
func (t *testPassword) AuthenticatePassword(user, password string) (user.Info, bool, error) {
|
||||
t.Called = true
|
||||
t.Username = user
|
||||
t.Password = password
|
||||
return t.User, t.OK, t.Err
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Header string
|
||||
Password testPassword
|
||||
|
||||
ExpectedCalled bool
|
||||
ExpectedUsername string
|
||||
ExpectedPassword string
|
||||
|
||||
ExpectedUser string
|
||||
ExpectedOK bool
|
||||
ExpectedErr bool
|
||||
}{
|
||||
"no auth": {},
|
||||
"empty password basic header": {
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "user_with_empty_password",
|
||||
ExpectedPassword: "",
|
||||
},
|
||||
"valid basic header": {
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypassword:withcolon",
|
||||
},
|
||||
"password auth returned user": {
|
||||
Password: testPassword{User: &user.DefaultInfo{Name: "returneduser"}, OK: true},
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypw",
|
||||
ExpectedUser: "returneduser",
|
||||
ExpectedOK: true,
|
||||
},
|
||||
"password auth returned error": {
|
||||
Password: testPassword{Err: errors.New("auth error")},
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypw",
|
||||
ExpectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, testCase := range testCases {
|
||||
password := testCase.Password
|
||||
auth := authenticator.Request(New(&password))
|
||||
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
if testCase.ExpectedUsername != "" || testCase.ExpectedPassword != "" {
|
||||
req.SetBasicAuth(testCase.ExpectedUsername, testCase.ExpectedPassword)
|
||||
}
|
||||
|
||||
user, ok, err := auth.AuthenticateRequest(req)
|
||||
|
||||
if testCase.ExpectedCalled != password.Called {
|
||||
t.Errorf("%s: Expected called=%v, got %v", k, testCase.ExpectedCalled, password.Called)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedUsername != password.Username {
|
||||
t.Errorf("%s: Expected called with username=%v, got %v", k, testCase.ExpectedUsername, password.Username)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedPassword != password.Password {
|
||||
t.Errorf("%s: Expected called with password=%v, got %v", k, testCase.ExpectedPassword, password.Password)
|
||||
continue
|
||||
}
|
||||
|
||||
if testCase.ExpectedErr != (err != nil) {
|
||||
t.Errorf("%s: Expected err=%v, got err=%v", k, testCase.ExpectedErr, err)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedOK != ok {
|
||||
t.Errorf("%s: Expected ok=%v, got ok=%v", k, testCase.ExpectedOK, ok)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedUser != "" && testCase.ExpectedUser != user.GetName() {
|
||||
t.Errorf("%s: Expected user.GetName()=%v, got %v", k, testCase.ExpectedUser, user.GetName())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
42
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/anytoken/anytoken.go
generated
vendored
Normal file
42
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/anytoken/anytoken.go
generated
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
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 anytoken
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type AnyTokenAuthenticator struct{}
|
||||
|
||||
func (AnyTokenAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) {
|
||||
lastSlash := strings.LastIndex(value, "/")
|
||||
if lastSlash == -1 {
|
||||
return &user.DefaultInfo{Name: value}, true, nil
|
||||
}
|
||||
|
||||
ret := &user.DefaultInfo{Name: value[:lastSlash]}
|
||||
|
||||
groupString := value[lastSlash+1:]
|
||||
if len(groupString) == 0 {
|
||||
return ret, true, nil
|
||||
}
|
||||
|
||||
ret.Groups = strings.Split(groupString, ",")
|
||||
return ret, true, nil
|
||||
}
|
71
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/anytoken/anytoken_test.go
generated
vendored
Normal file
71
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/anytoken/anytoken_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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 anytoken
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
func TestAnyTokenAuthenticator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
|
||||
expectedUser user.Info
|
||||
}{
|
||||
{
|
||||
name: "user only",
|
||||
token: "joe",
|
||||
expectedUser: &user.DefaultInfo{Name: "joe"},
|
||||
},
|
||||
{
|
||||
name: "user with slash",
|
||||
token: "scheme/joe/",
|
||||
expectedUser: &user.DefaultInfo{Name: "scheme/joe"},
|
||||
},
|
||||
{
|
||||
name: "user with groups",
|
||||
token: "joe/group1,group2",
|
||||
expectedUser: &user.DefaultInfo{Name: "joe", Groups: []string{"group1", "group2"}},
|
||||
},
|
||||
{
|
||||
name: "user with slash and groups",
|
||||
token: "scheme/joe/group1,group2",
|
||||
expectedUser: &user.DefaultInfo{Name: "scheme/joe", Groups: []string{"group1", "group2"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
actualUser, _, _ := AnyTokenAuthenticator{}.AuthenticateToken(tc.token)
|
||||
|
||||
if len(actualUser.GetExtra()) != 0 {
|
||||
t.Errorf("%q: got extra: %v", tc.name, actualUser.GetExtra())
|
||||
}
|
||||
if len(actualUser.GetUID()) != 0 {
|
||||
t.Errorf("%q: got extra: %v", tc.name, actualUser.GetUID())
|
||||
}
|
||||
if e, a := tc.expectedUser.GetName(), actualUser.GetName(); e != a {
|
||||
t.Errorf("%q: expected %v, got %v", tc.name, e, a)
|
||||
}
|
||||
if e, a := tc.expectedUser.GetGroups(), actualUser.GetGroups(); !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("%q: expected %v, got %v", tc.name, e, a)
|
||||
}
|
||||
}
|
||||
}
|
4
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/OWNERS
generated
vendored
Normal file
4
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/OWNERS
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
approvers:
|
||||
- ericchiang
|
||||
reviewers:
|
||||
- ericchiang
|
282
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go
generated
vendored
Normal file
282
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
Copyright 2015 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
oidc implements the authenticator.Token interface using the OpenID Connect protocol.
|
||||
|
||||
config := oidc.OIDCOptions{
|
||||
IssuerURL: "https://accounts.google.com",
|
||||
ClientID: os.Getenv("GOOGLE_CLIENT_ID"),
|
||||
UsernameClaim: "email",
|
||||
}
|
||||
tokenAuthenticator, err := oidc.New(config)
|
||||
*/
|
||||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
)
|
||||
|
||||
type OIDCOptions struct {
|
||||
// IssuerURL is the URL the provider signs ID Tokens as. This will be the "iss"
|
||||
// field of all tokens produced by the provider and is used for configuration
|
||||
// discovery.
|
||||
//
|
||||
// The URL is usually the provider's URL without a path, for example
|
||||
// "https://accounts.google.com" or "https://login.salesforce.com".
|
||||
//
|
||||
// The provider must implement configuration discovery.
|
||||
// See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig
|
||||
IssuerURL string
|
||||
|
||||
// ClientID the JWT must be issued for, the "sub" field. This plugin only trusts a single
|
||||
// client to ensure the plugin can be used with public providers.
|
||||
//
|
||||
// The plugin supports the "authorized party" OpenID Connect claim, which allows
|
||||
// specialized providers to issue tokens to a client for a different client.
|
||||
// See: https://openid.net/specs/openid-connect-core-1_0.html#IDToken
|
||||
ClientID string
|
||||
|
||||
// Path to a PEM encoded root certificate of the provider.
|
||||
CAFile string
|
||||
|
||||
// UsernameClaim is the JWT field to use as the user's username.
|
||||
UsernameClaim string
|
||||
|
||||
// GroupsClaim, if specified, causes the OIDCAuthenticator to try to populate the user's
|
||||
// groups with an ID Token field. If the GrouppClaim field is present in an ID Token the value
|
||||
// must be a string or list of strings.
|
||||
GroupsClaim string
|
||||
}
|
||||
|
||||
type OIDCAuthenticator struct {
|
||||
issuerURL string
|
||||
|
||||
trustedClientID string
|
||||
|
||||
usernameClaim string
|
||||
groupsClaim string
|
||||
|
||||
httpClient *http.Client
|
||||
|
||||
// Contains an *oidc.Client. Do not access directly. Use client() method.
|
||||
oidcClient atomic.Value
|
||||
|
||||
// Guards the close method and is used to lock during initialization and closing.
|
||||
mu sync.Mutex
|
||||
close func() // May be nil
|
||||
}
|
||||
|
||||
// New creates a token authenticator which validates OpenID Connect ID Tokens.
|
||||
func New(opts OIDCOptions) (*OIDCAuthenticator, error) {
|
||||
url, err := url.Parse(opts.IssuerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if url.Scheme != "https" {
|
||||
return nil, fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", opts.IssuerURL, url.Scheme)
|
||||
}
|
||||
|
||||
if opts.UsernameClaim == "" {
|
||||
return nil, errors.New("no username claim provided")
|
||||
}
|
||||
|
||||
var roots *x509.CertPool
|
||||
if opts.CAFile != "" {
|
||||
roots, err = certutil.NewPool(opts.CAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read the CA file: %v", err)
|
||||
}
|
||||
} else {
|
||||
glog.Info("OIDC: No x509 certificates provided, will use host's root CA set")
|
||||
}
|
||||
|
||||
// Copied from http.DefaultTransport.
|
||||
tr := net.SetTransportDefaults(&http.Transport{
|
||||
// According to golang's doc, if RootCAs is nil,
|
||||
// TLS uses the host's root CA set.
|
||||
TLSClientConfig: &tls.Config{RootCAs: roots},
|
||||
})
|
||||
|
||||
authenticator := &OIDCAuthenticator{
|
||||
issuerURL: opts.IssuerURL,
|
||||
trustedClientID: opts.ClientID,
|
||||
usernameClaim: opts.UsernameClaim,
|
||||
groupsClaim: opts.GroupsClaim,
|
||||
httpClient: &http.Client{Transport: tr},
|
||||
}
|
||||
|
||||
// Attempt to initialize the authenticator asynchronously.
|
||||
//
|
||||
// Ignore errors instead of returning it since the OpenID Connect provider might not be
|
||||
// available yet, for instance if it's running on the cluster and needs the API server
|
||||
// to come up first. Errors will be logged within the client() method.
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
authenticator.client()
|
||||
}()
|
||||
|
||||
return authenticator, nil
|
||||
}
|
||||
|
||||
// Close stops all goroutines used by the authenticator.
|
||||
func (a *OIDCAuthenticator) Close() {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
if a.close != nil {
|
||||
a.close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *OIDCAuthenticator) client() (*oidc.Client, error) {
|
||||
// Fast check to see if client has already been initialized.
|
||||
if client := a.oidcClient.Load(); client != nil {
|
||||
return client.(*oidc.Client), nil
|
||||
}
|
||||
|
||||
// Acquire lock, then recheck initialization.
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if client := a.oidcClient.Load(); client != nil {
|
||||
return client.(*oidc.Client), nil
|
||||
}
|
||||
|
||||
// Try to initialize client.
|
||||
providerConfig, err := oidc.FetchProviderConfig(a.httpClient, a.issuerURL)
|
||||
if err != nil {
|
||||
glog.Errorf("oidc authenticator: failed to fetch provider discovery data: %v", err)
|
||||
return nil, fmt.Errorf("fetch provider config: %v", err)
|
||||
}
|
||||
|
||||
clientConfig := oidc.ClientConfig{
|
||||
HTTPClient: a.httpClient,
|
||||
Credentials: oidc.ClientCredentials{ID: a.trustedClientID},
|
||||
ProviderConfig: providerConfig,
|
||||
}
|
||||
|
||||
client, err := oidc.NewClient(clientConfig)
|
||||
if err != nil {
|
||||
glog.Errorf("oidc authenticator: failed to create client: %v", err)
|
||||
return nil, fmt.Errorf("create client: %v", err)
|
||||
}
|
||||
|
||||
// SyncProviderConfig will start a goroutine to periodically synchronize the provider config.
|
||||
// The synchronization interval is set by the expiration length of the config, and has a minimum
|
||||
// and maximum threshold.
|
||||
stop := client.SyncProviderConfig(a.issuerURL)
|
||||
a.oidcClient.Store(client)
|
||||
a.close = func() {
|
||||
// This assumes the stop is an unbuffered channel.
|
||||
// So instead of closing the channel, we send am empty struct here.
|
||||
// This guarantees that when this function returns, there is no flying requests,
|
||||
// because a send to an unbuffered channel happens after the receive from the channel.
|
||||
stop <- struct{}{}
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// AuthenticateToken decodes and verifies an ID Token using the OIDC client, if the verification succeeds,
|
||||
// then it will extract the user info from the JWT claims.
|
||||
func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) {
|
||||
jwt, err := jose.ParseJWT(value)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
client, err := a.client()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if err := client.VerifyJWT(jwt); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
claims, err := jwt.Claims()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
claim, ok, err := claims.StringClaim(a.usernameClaim)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("cannot find %q in JWT claims", a.usernameClaim)
|
||||
}
|
||||
|
||||
var username string
|
||||
switch a.usernameClaim {
|
||||
case "email":
|
||||
verified, ok := claims["email_verified"]
|
||||
if !ok {
|
||||
return nil, false, errors.New("'email_verified' claim not present")
|
||||
}
|
||||
|
||||
emailVerified, ok := verified.(bool)
|
||||
if !ok {
|
||||
// OpenID Connect spec defines 'email_verified' as a boolean. For now, be a pain and error if
|
||||
// it's a different type. If there are enough misbehaving providers we can relax this latter.
|
||||
//
|
||||
// See: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
|
||||
return nil, false, fmt.Errorf("malformed claim 'email_verified', expected boolean got %T", verified)
|
||||
}
|
||||
|
||||
if !emailVerified {
|
||||
return nil, false, errors.New("email not verified")
|
||||
}
|
||||
username = claim
|
||||
default:
|
||||
// For all other cases, use issuerURL + claim as the user name.
|
||||
username = fmt.Sprintf("%s#%s", a.issuerURL, claim)
|
||||
}
|
||||
|
||||
// TODO(yifan): Add UID, also populate the issuer to upper layer.
|
||||
info := &user.DefaultInfo{Name: username}
|
||||
|
||||
if a.groupsClaim != "" {
|
||||
groups, found, err := claims.StringsClaim(a.groupsClaim)
|
||||
if err != nil {
|
||||
// Groups type is present but is not an array of strings, try to decode as a string.
|
||||
group, _, err := claims.StringClaim(a.groupsClaim)
|
||||
if err != nil {
|
||||
// Custom claim is present, but isn't an array of strings or a string.
|
||||
return nil, false, fmt.Errorf("custom group claim contains invalid type: %T", claims[a.groupsClaim])
|
||||
}
|
||||
info.Groups = []string{group}
|
||||
} else if found {
|
||||
info.Groups = groups
|
||||
}
|
||||
}
|
||||
return info, true, nil
|
||||
}
|
336
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go
generated
vendored
Normal file
336
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/oidc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
Copyright 2015 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 oidc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
oidctesting "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing"
|
||||
)
|
||||
|
||||
func generateToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}, iat, exp time.Time, emailVerified bool) string {
|
||||
claims := oidc.NewClaims(iss, sub, aud, iat, exp)
|
||||
claims.Add(usernameClaim, value)
|
||||
if groups != nil && groupsClaim != "" {
|
||||
claims.Add(groupsClaim, groups)
|
||||
}
|
||||
claims.Add("email_verified", emailVerified)
|
||||
|
||||
signer := op.PrivKey.Signer()
|
||||
jwt, err := jose.NewSignedJWT(claims, signer)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot generate token: %v", err)
|
||||
return ""
|
||||
}
|
||||
return jwt.Encode()
|
||||
}
|
||||
|
||||
func generateTokenWithUnverifiedEmail(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, email string) string {
|
||||
return generateToken(t, op, iss, sub, aud, "email", email, "", nil, time.Now(), time.Now().Add(time.Hour), false)
|
||||
}
|
||||
|
||||
func generateGoodToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string {
|
||||
return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour), true)
|
||||
}
|
||||
|
||||
func generateMalformedToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string {
|
||||
return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now(), time.Now().Add(time.Hour), true) + "randombits"
|
||||
}
|
||||
|
||||
func generateExpiredToken(t *testing.T, op *oidctesting.OIDCProvider, iss, sub, aud string, usernameClaim, value, groupsClaim string, groups interface{}) string {
|
||||
return generateToken(t, op, iss, sub, aud, usernameClaim, value, groupsClaim, groups, time.Now().Add(-2*time.Hour), time.Now().Add(-1*time.Hour), true)
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
// Verify the cert/key pair works.
|
||||
cert1 := path.Join(os.TempDir(), "oidc-cert-1")
|
||||
key1 := path.Join(os.TempDir(), "oidc-key-1")
|
||||
cert2 := path.Join(os.TempDir(), "oidc-cert-2")
|
||||
key2 := path.Join(os.TempDir(), "oidc-key-2")
|
||||
|
||||
defer os.Remove(cert1)
|
||||
defer os.Remove(key1)
|
||||
defer os.Remove(cert2)
|
||||
defer os.Remove(key2)
|
||||
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1)
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2)
|
||||
|
||||
tests := []struct {
|
||||
testCase string
|
||||
|
||||
serverCertFile string
|
||||
serverKeyFile string
|
||||
|
||||
trustedCertFile string
|
||||
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
testCase: "provider using untrusted custom cert",
|
||||
serverCertFile: cert1,
|
||||
serverKeyFile: key1,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
testCase: "provider using untrusted cert",
|
||||
serverCertFile: cert1,
|
||||
serverKeyFile: key1,
|
||||
trustedCertFile: cert2,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
testCase: "provider using trusted cert",
|
||||
serverCertFile: cert1,
|
||||
serverKeyFile: key1,
|
||||
trustedCertFile: cert1,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
func() {
|
||||
op := oidctesting.NewOIDCProvider(t, "")
|
||||
srv, err := op.ServeTLSWithKeyPair(tc.serverCertFile, tc.serverKeyFile)
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", tc.testCase, err)
|
||||
return
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
issuer := srv.URL
|
||||
clientID := "client-foo"
|
||||
|
||||
options := OIDCOptions{
|
||||
IssuerURL: srv.URL,
|
||||
ClientID: clientID,
|
||||
CAFile: tc.trustedCertFile,
|
||||
UsernameClaim: "email",
|
||||
GroupsClaim: "groups",
|
||||
}
|
||||
|
||||
authenticator, err := New(options)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to initialize authenticator: %v", tc.testCase, err)
|
||||
return
|
||||
}
|
||||
defer authenticator.Close()
|
||||
|
||||
email := "user-1@example.com"
|
||||
groups := []string{"group1", "group2"}
|
||||
sort.Strings(groups)
|
||||
|
||||
token := generateGoodToken(t, op, issuer, "user-1", clientID, "email", email, "groups", groups)
|
||||
|
||||
// Because this authenticator behaves differently for subsequent requests, run these
|
||||
// tests multiple times (but expect the same result).
|
||||
for i := 1; i < 4; i++ {
|
||||
|
||||
user, ok, err := authenticator.AuthenticateToken(token)
|
||||
if err != nil {
|
||||
if !tc.wantErr {
|
||||
t.Errorf("%s (req #%d): failed to authenticate token: %v", tc.testCase, i, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tc.wantErr {
|
||||
t.Errorf("%s (req #%d): expected error authenticating", tc.testCase, i)
|
||||
continue
|
||||
}
|
||||
if !ok {
|
||||
t.Errorf("%s (req #%d): did not get user or error", tc.testCase, i)
|
||||
continue
|
||||
}
|
||||
|
||||
if gotUsername := user.GetName(); email != gotUsername {
|
||||
t.Errorf("%s (req #%d): GetName() expected=%q got %q", tc.testCase, i, email, gotUsername)
|
||||
}
|
||||
gotGroups := user.GetGroups()
|
||||
sort.Strings(gotGroups)
|
||||
if !reflect.DeepEqual(gotGroups, groups) {
|
||||
t.Errorf("%s (req #%d): GetGroups() expected=%q got %q", tc.testCase, i, groups, gotGroups)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCAuthentication(t *testing.T) {
|
||||
cert := path.Join(os.TempDir(), "oidc-cert")
|
||||
key := path.Join(os.TempDir(), "oidc-key")
|
||||
|
||||
defer os.Remove(cert)
|
||||
defer os.Remove(key)
|
||||
|
||||
oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
|
||||
|
||||
// Ensure all tests pass when the issuer is not at a base URL.
|
||||
for _, path := range []string{"", "/path/with/trailing/slash/"} {
|
||||
|
||||
// Create a TLS server and a client.
|
||||
op := oidctesting.NewOIDCProvider(t, path)
|
||||
srv, err := op.ServeTLSWithKeyPair(cert, key)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot start server: %v", err)
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
tests := []struct {
|
||||
userClaim string
|
||||
groupsClaim string
|
||||
token string
|
||||
userInfo user.Info
|
||||
verified bool
|
||||
err string
|
||||
}{
|
||||
{
|
||||
"sub",
|
||||
"",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "", nil),
|
||||
&user.DefaultInfo{Name: "foo@example.com"},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
|
||||
&user.DefaultInfo{Name: "foo@example.com"},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Use user defined claim (email here).
|
||||
"email",
|
||||
"groups",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", []string{"group1", "group2"}),
|
||||
&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1", "group2"}},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Group claim is a string rather than an array. Map that string to a single group.
|
||||
"email",
|
||||
"groups",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", "group1"),
|
||||
&user.DefaultInfo{Name: "foo@example.com", Groups: []string{"group1"}},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
// Group claim is not a string or array of strings. Throw out this as invalid.
|
||||
"email",
|
||||
"groups",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "foo@example.com", "groups", 1),
|
||||
nil,
|
||||
false,
|
||||
"custom group claim contains invalid type: float64",
|
||||
},
|
||||
{
|
||||
// Email not verified
|
||||
"email",
|
||||
"",
|
||||
generateTokenWithUnverifiedEmail(t, op, srv.URL, "client-foo", "client-foo", "foo@example.com"),
|
||||
nil,
|
||||
false,
|
||||
"email not verified",
|
||||
},
|
||||
{
|
||||
"sub",
|
||||
"",
|
||||
generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: unable to verify JWT signature: no matching keys",
|
||||
},
|
||||
{
|
||||
// Invalid 'aud'.
|
||||
"sub",
|
||||
"",
|
||||
generateGoodToken(t, op, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
|
||||
},
|
||||
{
|
||||
// Invalid issuer.
|
||||
"sub",
|
||||
"",
|
||||
generateGoodToken(t, op, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: invalid claim value: 'iss'.",
|
||||
},
|
||||
{
|
||||
"sub",
|
||||
"",
|
||||
generateExpiredToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
|
||||
nil,
|
||||
false,
|
||||
"oidc: JWT claims invalid: token is expired",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
client, err := New(OIDCOptions{srv.URL, "client-foo", cert, tt.userClaim, tt.groupsClaim})
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
user, result, err := client.AuthenticateToken(tt.token)
|
||||
if tt.err != "" {
|
||||
if !strings.HasPrefix(err.Error(), tt.err) {
|
||||
t.Errorf("#%d: Expecting: %v..., but got: %v", i, tt.err, err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("#%d: Unexpected error: %v", i, err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tt.verified, result) {
|
||||
t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.verified, result)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.userInfo, user) {
|
||||
t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.userInfo, user)
|
||||
}
|
||||
client.Close()
|
||||
}
|
||||
}
|
||||
}
|
200
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing/provider.go
generated
vendored
Normal file
200
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/oidc/testing/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
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 testing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
"github.com/coreos/go-oidc/key"
|
||||
"github.com/coreos/go-oidc/oidc"
|
||||
)
|
||||
|
||||
// NewOIDCProvider provides a bare minimum OIDC IdP Server useful for testing.
|
||||
func NewOIDCProvider(t *testing.T, issuerPath string) *OIDCProvider {
|
||||
privKey, err := key.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create OIDC Provider: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
op := &OIDCProvider{
|
||||
Mux: http.NewServeMux(),
|
||||
PrivKey: privKey,
|
||||
issuerPath: issuerPath,
|
||||
}
|
||||
|
||||
op.Mux.HandleFunc(path.Join(issuerPath, "/.well-known/openid-configuration"), op.handleConfig)
|
||||
op.Mux.HandleFunc(path.Join(issuerPath, "/keys"), op.handleKeys)
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
type OIDCProvider struct {
|
||||
Mux *http.ServeMux
|
||||
PCFG oidc.ProviderConfig
|
||||
PrivKey *key.PrivateKey
|
||||
issuerPath string
|
||||
}
|
||||
|
||||
func (op *OIDCProvider) ServeTLSWithKeyPair(cert, key string) (*httptest.Server, error) {
|
||||
srv := httptest.NewUnstartedServer(op.Mux)
|
||||
|
||||
srv.TLS = &tls.Config{Certificates: make([]tls.Certificate, 1)}
|
||||
var err error
|
||||
srv.TLS.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot load cert/key pair: %v", err)
|
||||
}
|
||||
srv.StartTLS()
|
||||
|
||||
// The issuer's URL is extended by an optional path. This ensures that the plugin can
|
||||
// handle issuers that use a non-root path for discovery (see kubernetes/kubernetes#29749).
|
||||
srv.URL = srv.URL + op.issuerPath
|
||||
|
||||
u, err := url.Parse(srv.URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pathFor := func(p string) *url.URL {
|
||||
u2 := *u // Shallow copy.
|
||||
u2.Path = path.Join(u2.Path, p)
|
||||
return &u2
|
||||
}
|
||||
|
||||
op.PCFG = oidc.ProviderConfig{
|
||||
Issuer: u,
|
||||
AuthEndpoint: pathFor("/auth"),
|
||||
TokenEndpoint: pathFor("/token"),
|
||||
KeysEndpoint: pathFor("/keys"),
|
||||
ResponseTypesSupported: []string{"code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenSigningAlgValues: []string{"RS256"},
|
||||
}
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
func (op *OIDCProvider) handleConfig(w http.ResponseWriter, req *http.Request) {
|
||||
b, err := json.Marshal(&op.PCFG)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func (op *OIDCProvider) handleKeys(w http.ResponseWriter, req *http.Request) {
|
||||
keys := struct {
|
||||
Keys []jose.JWK `json:"keys"`
|
||||
}{
|
||||
Keys: []jose.JWK{op.PrivKey.JWK()},
|
||||
}
|
||||
|
||||
b, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", int(time.Hour.Seconds())))
|
||||
w.Header().Set("Expires", time.Now().Add(time.Hour).Format(time.RFC1123))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
// generateSelfSignedCert generates a self-signed cert/key pairs and writes to the certPath/keyPath.
|
||||
// This method is mostly identical to crypto.GenerateSelfSignedCert except for the 'IsCA' and 'KeyUsage'
|
||||
// in the certificate template. (Maybe we can merge these two methods).
|
||||
func GenerateSelfSignedCert(t *testing.T, host, certPath, keyPath string) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ip)
|
||||
} else {
|
||||
template.DNSNames = append(template.DNSNames, host)
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Generate cert
|
||||
certBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&certBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Generate key
|
||||
keyBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(&keyBuffer, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write cert
|
||||
if err := os.MkdirAll(filepath.Dir(certPath), os.FileMode(0755)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(certPath, certBuffer.Bytes(), os.FileMode(0644)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write key
|
||||
if err := os.MkdirAll(filepath.Dir(keyPath), os.FileMode(0755)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ioutil.WriteFile(keyPath, keyBuffer.Bytes(), os.FileMode(0600)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
36
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest/tokentest.go
generated
vendored
Normal file
36
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest/tokentest.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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 tokentest
|
||||
|
||||
import "k8s.io/apiserver/pkg/authentication/user"
|
||||
|
||||
type TokenAuthenticator struct {
|
||||
Tokens map[string]*user.DefaultInfo
|
||||
}
|
||||
|
||||
func New() *TokenAuthenticator {
|
||||
return &TokenAuthenticator{
|
||||
Tokens: make(map[string]*user.DefaultInfo),
|
||||
}
|
||||
}
|
||||
func (a *TokenAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) {
|
||||
user, ok := a.Tokens[value]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
return user, true, nil
|
||||
}
|
211
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/certs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// This file is an exact copy of
|
||||
// plugin/pkg/auth/authorizer/webhook/certs_test.go
|
||||
|
||||
package webhook
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA6IVXGPX5yP2Q6TAlQXIQsavzSqZ973iZvpQBGTI6M98gTSVm
|
||||
eBYE3o7S8e6WTI3DCnWwqc8Md1rT92FtaQLwv+uMNXijLio5RVBqjUEbunD5In/+
|
||||
T/y5sE9P3CzcWy6CEhIvORAZj6UlvgZzbRwI91+EVFR5jd8JU0e/L9Ds1jLZFyQw
|
||||
Kc1ADo+Tj9O4l0WtpRlrhzTgoor4C3fAQZm0mq+llTnxCmw+lhy8t88bPG1cMwdd
|
||||
DtUTbpetc++2JZ62Q3F1nqcX1EcHDidR0x3j+3357BLkXRK4MQsWLYLzeZ3X1ghW
|
||||
XT062H866PcIV+MX4H58spMN5cVYk5YTneGihQIDAQABAoIBAHU7FQieq4ssXK1U
|
||||
+tOeQNBzUzxl6MSd11YApPUhH7sbWdvLaXhOEbJr6+rSUbDTIGzbnXBf1XcvsgLd
|
||||
eh4hv2PjzFMBObSC0VEjFDWXh/VeFB3SzlNhpfVAZ5EohQjrz+RwiqKIfXqw1vCR
|
||||
rAxswBCIdd1WodpngvocCEaBXYc4MblaPhJDVtxQe8ndEakkSDlX9Z3qIaIGyXRa
|
||||
NvY/yURVuXhwDDd7C2QBT6CXGWhldAg7xrRVTcIoqAUfZCgfis0H8cQOa1cGNsbW
|
||||
t/oHm1fYTxMKFPhWQG0oimx+XJ07BeGgraDRLnxxNnGWTg/W33bc0ZCxCVT0Q5p9
|
||||
kMMfQUECgYEA9cewTK4ZRKC4bTdwqLTh3cyMkbyN4kBHmB1mS2FV/T0l4oZThM//
|
||||
OZ6KFnRCuvfuJIOa70s2bqUYky8NTQAidnnbTW2nZ/E5JdeIBs1fAfadAqiPdmkf
|
||||
MhvjBF/XfLnbCuXx3jA7GmNCpunJysuLtQzwlQlZLojN231uS+3LFbkCgYEA8jCC
|
||||
MgKYaDWssQbT7zfk5MxyZIH3F9N8K2RBIDSVuMo/E1LCIJ06/k+4jdv8nAWYJXcN
|
||||
eyLG7l0SXqrpMBSc9+ZTJgmbo0Mw+npvJHbJvAtD/XOSPjlIqkzPAUrxuiBYxa5S
|
||||
IfKZibygXKAbQMEwY7I4sTbBtIyiQmo9csxt2S0CgYEAiBi1VSCquUfOGBw09BaF
|
||||
Y85aoHCqmHhDrMXK2T7i4MG1csQzBz4t8/gIOvrR4LpdUjbV2l/pmkctXoMVeGf0
|
||||
rWo4t51ar8HxhTTeC/Y4/9tRgiFYn5cCQTsT8F4p8tTvqA9AaWqHr8r7I3Yd2X/w
|
||||
sqahqcVtbskuRLYmF0FrzXECgYAeiR0xPwCGSxYt78Vy6OI0Ms7Ne1FzMJf8RJSt
|
||||
gdPKy70uK4YMZKaWf+iuAimUZmQrfRo3B0h7r0JsqzHhfQfZfbHIHvf/mq4nNp6i
|
||||
w1NmISl+YD71F3Xg+vQynodhx0hKDFOQsizHn/+8DffBr1nxh/v75AKCSCUBKLH8
|
||||
sme7NQKBgDHQac2TmDSelE2uXTGxEVDQs/EpdJh7oCTLQ99Xud/DsaCOrt2s7aRX
|
||||
1FEohsCaUnqwS07/iH2o6Qb/qOteufB9I7FG85nAvqmP5dI4crGNNa8Rl6fXJaR8
|
||||
TUwpZmylTKEJ9zLt2PADglyDrQ2D+1WNzh966Oo9c+kZt4WJM0aF
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAKK9m2Cfg5uhMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQDohVcY9fnI/ZDpMCVBchCxq/NKpn3veJm+lAEZ
|
||||
Mjoz3yBNJWZ4FgTejtLx7pZMjcMKdbCpzwx3WtP3YW1pAvC/64w1eKMuKjlFUGqN
|
||||
QRu6cPkif/5P/LmwT0/cLNxbLoISEi85EBmPpSW+BnNtHAj3X4RUVHmN3wlTR78v
|
||||
0OzWMtkXJDApzUAOj5OP07iXRa2lGWuHNOCiivgLd8BBmbSar6WVOfEKbD6WHLy3
|
||||
zxs8bVwzB10O1RNul61z77YlnrZDcXWepxfURwcOJ1HTHeP7ffnsEuRdErgxCxYt
|
||||
gvN5ndfWCFZdPTrYfzro9whX4xfgfnyykw3lxViTlhOd4aKFAgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBSumZL6MMwmFGyhQAwl/v0lYDzdZjAfBgNVHSMEGDAWgBSumZL6MMwm
|
||||
FGyhQAwl/v0lYDzdZjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG
|
||||
6k+bZxKYq4PVZHWTKA7RSjv95FMMr4RSFwKn/n8TUD44ANWYqDrEfVmxAMn3NVK9
|
||||
ckA8mIRym4IGiWD9eBGgPNNtbAq8Wl/9+5qbDMerpXuRnG3wNY7RU75Rl008m52r
|
||||
c2i86ZPUi2fAJZyMf5StWE21oKiDYYQqlB6xxsIj6OHhf7536vEysoztNX5FpS2n
|
||||
q8wG0EhJVhG+Qyww8IlZA5Cjoh71Eqkcwb4cuLjPypxmLm0ywZ/6KgzV+IF+CT2v
|
||||
TJIpMokDUKlRi9cWSqkWXFE6xbCmhrrwKYsi0X6Vvi7a0pmOnSzKCQl8jN8u4A9R
|
||||
xar2YeJ6mCCzSAPM69DP
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAon7dRV4Br10dLcf8zgs/hOHouELveFr8tuWVIFivxSdnac2k
|
||||
6dM4iQ2uYS9nTXxNhyJJ/TX/MHEYc4gSXoqUbtx9jE3VA4mCKDhO7cJtCYxq0QV/
|
||||
PlQCiAPjn5nUMt9ACdii7/uTFDl46bK9K6ajvKHfHoWeYaJsF54kxBq5IMj+QaB2
|
||||
nc+pba00bGG09sYcHyD37QH+ugx64x+21xMYj2LB/uPoqZM0kj1GHPxAs8GqFq2P
|
||||
gwkv589AlHqt2iMCTAqED2jcg4FeS2r1DeYHwGyGAPfWTdA8RZ+gZ/P0Gj91T+4B
|
||||
9srR7BybUFjf1KxEcvPXBvP5r8OwOiYjS8hx/wIDAQABAoIBAQCVBQ9bfDjDX/tQ
|
||||
buVS+FHKRXss8IW4tIiqGqXGQk7/2YEnMKaaoVBpsBhJnDV6hBJ9aV69TnW3MSCh
|
||||
YxqlhSVW/fJNZ1uAoOyygeEwfmuMpC+ZfRcSS+z+W8K2LVbDSKXr4babqvVZSNOw
|
||||
TnDZxTrH1RNPZG65T0Ed77P7/B3nB7aeB2UMuHMQNZ3KrYDTck2R2uTGp+29TplN
|
||||
blS4VAg2/9KqFr7jkS3/C4jjxVd7d9mm0VdAvLcvENVXqSTYV8xDp+VLTnmtXi5f
|
||||
LXcopS+zKtKqT7MM7RA2sKrmSfrQBIXW2E1kfDFtpZHajhDutdYkSTH665W1G23M
|
||||
dIgy3ajhAoGBANE4AhMUVfQqXUCU0UjUDxiOy/8XcKiW/dKhRR1DOQY24J/k+UWv
|
||||
PEGVcBW4tgalYkTl/AW6hsNfubZaJuw05cHIKdL3df6ug7BUiJpmIv3sjrvPRYvA
|
||||
WY1UTb3EJrswGz8S2l5+2S3WFTCfK7S6N6Stfi1x6rMJBuOss7HGqdh3AoGBAMbU
|
||||
WavRqGRsvJFfE5bahXbFpkGWT++BTMP+lzK31z24JjmJdwO+ABWU4/xaXayA4skH
|
||||
PrzlYUcGJWIedb6W4dvz0sA59yflQzYmREkQPE+wbyor003y7mB8LpFiCnfaFhRn
|
||||
hoowkyIY+xM4UeDXWWt3DhBElgfA8fYZdiNJEhy5AoGBAMwYUw3BvMffu/CQPElL
|
||||
dR6DzsUeXKxZ/2pGIGIXfb1uM1pHyFQOSj3ARgMqmYeKNn73zA7akzRsYYJeF7I9
|
||||
OBT96q7+8IBuRdDx5gCYunHzHppf7HwUPEf+gYgpnY7lsu6ouZWNMNfiC/HOlJhN
|
||||
QJLJHFnA0y+sEqhvhSxbnLypAoGBALHCZ+kVKFegX3YYaosUEv589obsu8qE7vzL
|
||||
QKI3elfTq1kFbUILPEgPNUUIBXeUQy03LP/0k2PMOt/eG6apfoQHGQSCzlT8w3pF
|
||||
/AbWXRVhyAEL7X5jEntwirGv1WwRrmvPopkplGGHs/EbCRjbbzaE2i3xI7EK70f2
|
||||
u4gQbAEBAoGAVR4u8g5Tx2Gunzh7tfJJ5e3xGBGS3Yq+JqUVNI6t6KIAPh0rM+aD
|
||||
9tDgcwn8Vn5YU7YkqA2T8OOFsbJfrfZ7y7+oeMFukuIyxgmy9n/V/tCIrV/lR7A5
|
||||
3iYhanTUbQswx19pSRgsXi7fo9Fi/dmUwyHi18uz5FdLyCTsMbf3uA8=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAPqJyUfmRxGLMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCift1FXgGvXR0tx/zOCz+E4ei4Qu94Wvy25ZUg
|
||||
WK/FJ2dpzaTp0ziJDa5hL2dNfE2HIkn9Nf8wcRhziBJeipRu3H2MTdUDiYIoOE7t
|
||||
wm0JjGrRBX8+VAKIA+OfmdQy30AJ2KLv+5MUOXjpsr0rpqO8od8ehZ5homwXniTE
|
||||
GrkgyP5BoHadz6ltrTRsYbT2xhwfIPftAf66DHrjH7bXExiPYsH+4+ipkzSSPUYc
|
||||
/ECzwaoWrY+DCS/nz0CUeq3aIwJMCoQPaNyDgV5LavUN5gfAbIYA99ZN0DxFn6Bn
|
||||
8/QaP3VP7gH2ytHsHJtQWN/UrERy89cG8/mvw7A6JiNLyHH/AgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBS6IGeGHZCylibt0GzY0dP6C0J9VjAfBgNVHSMEGDAWgBS6IGeGHZCy
|
||||
libt0GzY0dP6C0J9VjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi
|
||||
A1dp75kbePFZsUNjxN6B/Pv0vSoaOjQkc4hpxKbI4VRCuPGmMRFYTlKCzoZ53OqQ
|
||||
2Jmu1Zbzel/bV5vXrW0BOfUpfWYzd/usIJEuTgU8ijBIB+IHAXYwwxeKRcz3C+7+
|
||||
9RBMF7gSg9pU2hrSvjhh7Q96IMJ42Z7tI3WD8SZaQLjY1NW1jrQVsg66ktdMke7x
|
||||
zC8oIRIBH4W6l5s7jtZx1k305NE04pigcFLxCxOmicKd66ysI5hAZkD7y0dgwgtL
|
||||
IqCQy6t7uJDydRiNRfPFr9Eg7uOu83JGw11f3bGVhJVCbzHyKddvkQsQbdaMHRgZ
|
||||
zgmWLORg+ls1H1oaJiNW
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/9bhfMr0LaEKng1lR
|
||||
XopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv0EUnyx7WR3V21ObZ
|
||||
iQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jNz+OM/VmqsgzoKLoa
|
||||
bGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvhjqo8NRqzZLg79ldY
|
||||
aKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJG8eZUmjQpUMv7Jk3
|
||||
qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABAoIBAQCjzeFijwzKKL4w
|
||||
0B1IBhi3WeReFPG4nkt1ssQPBYrrJPKBZgHO13A1STI78wFn/OdYpajfF8hI8HT1
|
||||
BiGVsu27Eb9TC60b/x6OtmeCEk+044LRbtu+9NZUb7HHHogI0l++X0KXZ0coE38L
|
||||
1izwNvfrmLa+QaIgHMtAg9EnJwJ993n4L31GovWh8MGmVyJX/F92y+agNwWkNYYp
|
||||
iLWFyon+HbNVL13WOOYnYEdA8Me3+Gucy1EOfWMF7mgmuO2vcfnxXd6b16VjAwtE
|
||||
jGCQfzgpWGHLpgwoBgDmnPUbdNPUT3MbA9jqG2mlnBSBQveYgKrmFdDYnAjnCM4L
|
||||
uF2ztBzhAoGBAOYc3sF3YjpIIMsyH9omqtfOuxO+oZkpb2vB9kgdXCDcG870M+BC
|
||||
bNzV7DCSV8QAUqjKQK1r3gq62UZMLXZbG8x5UnM8/EK0X1CSqygwSWjGpYxIQEhh
|
||||
O2lq69WipkNDnX1ZmrvEdHD2cxqkkXZ7bdRKRasrFJgvJa3XbiJ18KYxAoGBAMpe
|
||||
/72EcX9oL3KT8tJSpvasrw17p/XkMMCxTp3IDb3krF/4k5bYF61F68/LNSy3xkos
|
||||
ZrPUK/U160iuHSYCpMq4pPmlWgKq4hmUMOt+8Yy622zDlugarq9VLqvSdGHm+r6F
|
||||
5fHilXB0UsTXXOuLZWLcSQ0MBgiaVCLb2AmXZhhXAoGAEjSchw/r7JKCTbE0hezj
|
||||
PVm0wVYmsNhvYUYiNwhjnpHrfU8iv45h0IL4QcuCOBaSc5o0zcOn+I9Z207xldiV
|
||||
dXLvzAA6MQjWNai08+QGGs0EkfmxZEiVC70S1X8dylqSHjW1oT9kuv80khoNDCOt
|
||||
x8rsgiNRaMzqHTvbEczk8jECgYB2Od+wSULBSw2FI5fVdcHjFGlEODycs44j1LH4
|
||||
DZqxmHl3q9IVavMSIGouQCo1kLuAM8ZgQpDXtYNaN5YB0cOSRyLiUc5vBoQGq4OU
|
||||
4Nme/L8aIH315TiuZ9ZXPSEO3REZ40G9+UCSrPJ52tOHLC2z/ruSqraPqhGDN+pT
|
||||
WCamCwKBgEPa+kVrPs0khQH8+sbFbU9ifj4fhPAiSwj2fKuXFro2mE205vAMHye/
|
||||
SYs/mPzYzKSd7F+7Zk6oVrgFVskTiReW3phF+cIl+CdcnIenF0jW1PVgGw8znu+P
|
||||
SbHSdqV+tB7AW2J7sH8TZtfMUPAK2MJ4S+1uaHK86K79ym4Rz0E2
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIJAN7rkfhaX8FZMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfc2VydmVyMIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/
|
||||
9bhfMr0LaEKng1lRXopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv
|
||||
0EUnyx7WR3V21ObZiQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jN
|
||||
z+OM/VmqsgzoKLoabGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvh
|
||||
jqo8NRqzZLg79ldYaKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJ
|
||||
G8eZUmjQpUMv7Jk3qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABo0Aw
|
||||
PjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATAP
|
||||
BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCZHB9UCl2CfylWP3db
|
||||
xUamawnRoTYlsOcUh4f2tlHMY+vYiEStN+LECk62YpeaHl/nz/lk7g1Jx9aua39z
|
||||
wFIHiXYhwSWOtgmzpbxYLye1yajKXbbA1T7mEZJTjewDB9i1LcB9W3EV5VJ8Y1GY
|
||||
AYKuKQ4Cb1HrqLsrw/1PDm0VouWzf2ESv8CBvAv/pYLVfwgS6WsUqn9wycpLEnqQ
|
||||
RK66/AoiOaxUIjEP0O1q6pi6Mag7XAfeNtx8J0VGt4cRG4rvWCbKVUyvKfUCkipN
|
||||
gJu09S+KIz3x1CJLRuJX9tB+cFnnykDLQ2IKg7x44O83ikNk8+Di3iT/awCguWPE
|
||||
rHh5
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDjwQAlnHoLf/lz+Fh2
|
||||
DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V9Ll3aY/6xVRCenWi
|
||||
UNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL0q7vw003v5TBqzC/
|
||||
FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqSfbBg6B/A3OwpbIfx
|
||||
09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na+ZB+gMt4+e2Y7qNz
|
||||
76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABAoIBAQCJpGzJSzC2W8DM
|
||||
sMqBNdCUMKZ0cwq13b7W2BimGJKyCOOi3HxUZEaYf/2Leyt+PPBm72SML7dzvDh3
|
||||
qa269gKVqmkSqa2vF763qQbRuYo14msTQzA7+s3TUMbZs2UaDOE6nZIzs1QdEElp
|
||||
1DvYXHz+/rD7Adj9VF+mMnouqQoy5kgJTnVZ8sOyl/9R6F67xKBIvcrtPfqVZzuG
|
||||
2hGAMUnawxFUajQC7BynIeCWrk79SUmQgilyNgRdY6+rGh2uRupIxuiAukPtuag1
|
||||
Li+wnNl1UGECtv9ZnnboKvg2334k5vhYScGRJbwbr7Zt3ZaNd0Z/DE9kTtnhBS7v
|
||||
9qWdc7CBAoGBAPR4hz1fhHFiPmMEAGuiNms6WdyIfyonIRYas8ZDKUQGdxn/aO8a
|
||||
CURktHRlm6iYT+j1cbf3RnLEN9pNr3V2EySOMc+rXUNifcP7Vl53akAQmISUfQWG
|
||||
UfwaNLicbavf6m9UCiwWByAZghqDZSLiwmLHIjGcSJQiFuhZryioDydxAoGBAPED
|
||||
q1Z7oNhzwRYie9OB5ylnrCH8G3yFl8egBmQrPJKIQHA9mAGg01LEJwQNoWewyAWx
|
||||
jfeFtWvIgZkj49cluZgHYyF81jApaNraxtXAgIwC1n7oAIttmeklZ/V1HntknG3Y
|
||||
ow2bV/NA3aPOTPYxW8oDv7U9lvwve7kIFxeWjE/5AoGASfXI3G1wUSkqvKPySJ3b
|
||||
ntcZZpm49xS9csWDS+D3tAfMsoXNxkB3O0TIP0qaLAhgbJcM314k5wWr7BSCl6Ow
|
||||
KOgH887hOUirycXZHF0+PMGIktulcy1u0jlPZ+aTW2MztpiTN0E2yKRO8xx7VXGK
|
||||
431hP+cLIh2qFoNDdaZaZ1ECgYEArw++PWQxMefqgVxs2vXJZY7TPiA0Ct+ynqKC
|
||||
4fFx3vGu9JgYuF4MAVtPB6eq7HlA4LnWZ8ssOuz6DbU/AoB5bY84FxPpNDRv4D/3
|
||||
Gz3nYUuSZ72234+tsuaju2vlxzUOVs97qB+E48Di/N+VkWHKzVKpxkjFScpnsL/K
|
||||
niyRIGkCgYEAriuxbOCczL/j6u2Xq1ngEsGg+RXjtOYGoJWo7B8qlVL4nF8w1Nbd
|
||||
FxEmOChQgUnBdwb93qHCSq0Fidf7OfewrfJJkstWIh3zPS4umLZo7R3YblncpdfT
|
||||
M197uckIWccZml2jF/c7nvK+MjwDRhkOl2a6HzMxcdBwYUJmSwmIZ4k=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC7jCCAdagAwIBAgIJAN7rkfhaX8FaMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfY2xpZW50MIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDj
|
||||
wQAlnHoLf/lz+Fh2DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V
|
||||
9Ll3aY/6xVRCenWiUNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL
|
||||
0q7vw003v5TBqzC/FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqS
|
||||
fbBg6B/A3OwpbIfx09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na
|
||||
+ZB+gMt4+e2Y7qNz76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABoy8w
|
||||
LTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEA2IZNhkVrSTAIeP2N2WzOHqbFbGyO+NA8G9Hb5fiX
|
||||
e1YS2Ku3ERYNr+HLxNHCsXiSUKjjBmXMc4z0XaHJznEKEbotZftjTlTQlHi3/5vm
|
||||
dIG18pmO/E5ebVXl6pU96v/hBd8N5rWp9WUKgP0y59r/JA+oNpmd10A+RyaOyrFK
|
||||
rBm8Z8rvDYMrXSpOwx9BNDuhqzbdG8MYw5vO55Er3hwTXoapsMqSh5s9+OFFpUJi
|
||||
2uEoQlwWiYRtQj6g4wgr4woDEbv8XxsHqGfs+GSnmRsB69xRI24lEtC+nS6Rz3Sh
|
||||
YWeN0gD8PsQC1KJVv6xCGo1yXSEwytRMB23XYtAZahLdLg==
|
||||
-----END CERTIFICATE-----`)
|
131
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go
generated
vendored
Normal file
131
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go
generated
vendored
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
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 webhook implements the authenticator.Token interface using HTTP webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/util/cache"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
authenticationclient "k8s.io/client-go/kubernetes/typed/authentication/v1beta1"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
authentication "k8s.io/client-go/pkg/apis/authentication/v1beta1"
|
||||
|
||||
_ "k8s.io/client-go/pkg/apis/authentication/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{authentication.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
const retryBackoff = 500 * time.Millisecond
|
||||
|
||||
// Ensure WebhookTokenAuthenticator implements the authenticator.Token interface.
|
||||
var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil)
|
||||
|
||||
type WebhookTokenAuthenticator struct {
|
||||
tokenReview authenticationclient.TokenReviewInterface
|
||||
responseCache *cache.LRUExpireCache
|
||||
ttl time.Duration
|
||||
initialBackoff time.Duration
|
||||
}
|
||||
|
||||
// NewFromInterface creates a webhook authenticator using the given tokenReview client
|
||||
func NewFromInterface(tokenReview authenticationclient.TokenReviewInterface, ttl time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
return newWithBackoff(tokenReview, ttl, retryBackoff)
|
||||
}
|
||||
|
||||
// New creates a new WebhookTokenAuthenticator from the provided kubeconfig file.
|
||||
func New(kubeConfigFile string, ttl time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
tokenReview, err := tokenReviewInterfaceFromKubeconfig(kubeConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWithBackoff(tokenReview, ttl, retryBackoff)
|
||||
}
|
||||
|
||||
// newWithBackoff allows tests to skip the sleep.
|
||||
func newWithBackoff(tokenReview authenticationclient.TokenReviewInterface, ttl, initialBackoff time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
return &WebhookTokenAuthenticator{tokenReview, cache.NewLRUExpireCache(1024), ttl, initialBackoff}, nil
|
||||
}
|
||||
|
||||
// AuthenticateToken implements the authenticator.Token interface.
|
||||
func (w *WebhookTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
|
||||
r := &authentication.TokenReview{
|
||||
Spec: authentication.TokenReviewSpec{Token: token},
|
||||
}
|
||||
if entry, ok := w.responseCache.Get(r.Spec); ok {
|
||||
r.Status = entry.(authentication.TokenReviewStatus)
|
||||
} else {
|
||||
var (
|
||||
result *authentication.TokenReview
|
||||
err error
|
||||
)
|
||||
webhook.WithExponentialBackoff(w.initialBackoff, func() error {
|
||||
result, err = w.tokenReview.Create(r)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
r.Status = result.Status
|
||||
w.responseCache.Add(r.Spec, result.Status, w.ttl)
|
||||
}
|
||||
if !r.Status.Authenticated {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
var extra map[string][]string
|
||||
if r.Status.User.Extra != nil {
|
||||
extra = map[string][]string{}
|
||||
for k, v := range r.Status.User.Extra {
|
||||
extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{
|
||||
Name: r.Status.User.Username,
|
||||
UID: r.Status.User.UID,
|
||||
Groups: r.Status.User.Groups,
|
||||
Extra: extra,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
// tokenReviewInterfaceFromKubeconfig builds a client from the specified kubeconfig file,
|
||||
// and returns a TokenReviewInterface that uses that client. Note that the client submits TokenReview
|
||||
// requests to the exact path specified in the kubeconfig file, so arbitrary non-API servers can be targeted.
|
||||
func tokenReviewInterfaceFromKubeconfig(kubeConfigFile string) (authenticationclient.TokenReviewInterface, error) {
|
||||
gw, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tokenReviewClient{gw}, nil
|
||||
}
|
||||
|
||||
type tokenReviewClient struct {
|
||||
w *webhook.GenericWebhook
|
||||
}
|
||||
|
||||
func (t *tokenReviewClient) Create(tokenReview *authentication.TokenReview) (*authentication.TokenReview, error) {
|
||||
result := &authentication.TokenReview{}
|
||||
err := t.w.RestClient.Post().Body(tokenReview).Do().Into(result)
|
||||
return result, err
|
||||
}
|
564
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook_test.go
generated
vendored
Normal file
564
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook_test.go
generated
vendored
Normal file
|
@ -0,0 +1,564 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/pkg/apis/authentication/v1beta1"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
// Service mocks a remote authentication service.
|
||||
type Service interface {
|
||||
// Review looks at the TokenReviewSpec and provides an authentication
|
||||
// response in the TokenReviewStatus.
|
||||
Review(*v1beta1.TokenReview)
|
||||
HTTPStatusCode() int
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
const webhookPath = "/testserver"
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, fmt.Sprintf("unexpected method: %v", r.Method), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != webhookPath {
|
||||
http.Error(w, fmt.Sprintf("unexpected path: %v", r.URL.Path), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var review v1beta1.TokenReview
|
||||
bodyData, _ := ioutil.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(bodyData, &review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// ensure we received the serialized tokenreview as expected
|
||||
if review.APIVersion != "authentication.k8s.io/v1beta1" {
|
||||
http.Error(w, fmt.Sprintf("wrong api version: %s", string(bodyData)), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// once we have a successful request, always call the review to record that we were called
|
||||
s.Review(&review)
|
||||
if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
|
||||
http.Error(w, "HTTP Error", s.HTTPStatusCode())
|
||||
return
|
||||
}
|
||||
type userInfo struct {
|
||||
Username string `json:"username"`
|
||||
UID string `json:"uid"`
|
||||
Groups []string `json:"groups"`
|
||||
Extra map[string][]string `json:"extra"`
|
||||
}
|
||||
type status struct {
|
||||
Authenticated bool `json:"authenticated"`
|
||||
User userInfo `json:"user"`
|
||||
}
|
||||
|
||||
var extra map[string][]string
|
||||
if review.Status.User.Extra != nil {
|
||||
extra = map[string][]string{}
|
||||
for k, v := range review.Status.User.Extra {
|
||||
extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
resp := struct {
|
||||
Kind string `json:"kind"`
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
Kind: "TokenReview",
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Status: status{
|
||||
review.Status.Authenticated,
|
||||
userInfo{
|
||||
Username: review.Status.User.Username,
|
||||
UID: review.Status.User.UID,
|
||||
Groups: review.Status.User.Groups,
|
||||
Extra: extra,
|
||||
},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
|
||||
// Adjust the path to point to our custom path
|
||||
serverURL, _ := url.Parse(server.URL)
|
||||
serverURL.Path = webhookPath
|
||||
server.URL = serverURL.String()
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to say yes or no to authentication requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
statusCode int
|
||||
called int
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1beta1.TokenReview) {
|
||||
m.called++
|
||||
r.Status.Authenticated = m.allow
|
||||
if m.allow {
|
||||
r.Status.User.Username = "realHooman@email.com"
|
||||
}
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
func (m *mockService) HTTPStatusCode() int { return m.statusCode }
|
||||
|
||||
// newTokenAuthenticator creates a temporary kubeconfig file from the provided
|
||||
// arguments and attempts to load a new WebhookTokenAuthenticator from it.
|
||||
func newTokenAuthenticator(serverURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: serverURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := tokenReviewInterfaceFromKubeconfig(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newWithBackoff(c, cacheTime, 0)
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newTokenAuthenticator(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, 0)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
_, authenticated, err := wh.AuthenticateToken("t0k3n")
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making authorization request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !authenticated {
|
||||
t.Errorf("%s: failed to authenticate token", tt.test)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
_, authenticated, err = wh.AuthenticateToken("t0k3n")
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpectedly failed AuthenticateToken", tt.test)
|
||||
}
|
||||
if authenticated {
|
||||
t.Errorf("%s: incorrectly authenticated token", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// recorderService records all token review requests, and responds with the
|
||||
// provided TokenReviewStatus.
|
||||
type recorderService struct {
|
||||
lastRequest v1beta1.TokenReview
|
||||
response v1beta1.TokenReviewStatus
|
||||
}
|
||||
|
||||
func (rec *recorderService) Review(r *v1beta1.TokenReview) {
|
||||
rec.lastRequest = *r
|
||||
r.Status = rec.response
|
||||
}
|
||||
|
||||
func (rec *recorderService) HTTPStatusCode() int { return 200 }
|
||||
|
||||
func TestWebhookTokenAuthenticator(t *testing.T) {
|
||||
serv := &recorderService{}
|
||||
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expTypeMeta := metav1.TypeMeta{
|
||||
APIVersion: "authentication.k8s.io/v1beta1",
|
||||
Kind: "TokenReview",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
serverResponse v1beta1.TokenReviewStatus
|
||||
expectedAuthenticated bool
|
||||
expectedUser *user.DefaultInfo
|
||||
}{
|
||||
// Successful response should pass through all user info.
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: true,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "somebody",
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "somebody",
|
||||
},
|
||||
},
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: true,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "person@place.com",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"stuff-dev", "main-eng"},
|
||||
Extra: map[string]v1beta1.ExtraValue{"foo": {"bar", "baz"}},
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: true,
|
||||
expectedUser: &user.DefaultInfo{
|
||||
Name: "person@place.com",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"stuff-dev", "main-eng"},
|
||||
Extra: map[string][]string{"foo": {"bar", "baz"}},
|
||||
},
|
||||
},
|
||||
// Unauthenticated shouldn't even include extra provided info.
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: false,
|
||||
User: v1beta1.UserInfo{
|
||||
Username: "garbage",
|
||||
UID: "abcd-1234",
|
||||
Groups: []string{"not-actually-used"},
|
||||
},
|
||||
},
|
||||
expectedAuthenticated: false,
|
||||
expectedUser: nil,
|
||||
},
|
||||
{
|
||||
serverResponse: v1beta1.TokenReviewStatus{
|
||||
Authenticated: false,
|
||||
},
|
||||
expectedAuthenticated: false,
|
||||
expectedUser: nil,
|
||||
},
|
||||
}
|
||||
token := "my-s3cr3t-t0ken"
|
||||
for i, tt := range tests {
|
||||
serv.response = tt.serverResponse
|
||||
user, authenticated, err := wh.AuthenticateToken(token)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: authentication failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if serv.lastRequest.Spec.Token != token {
|
||||
t.Errorf("case %d: Server did not see correct token. Got %q, expected %q.",
|
||||
i, serv.lastRequest.Spec.Token, token)
|
||||
}
|
||||
if !reflect.DeepEqual(serv.lastRequest.TypeMeta, expTypeMeta) {
|
||||
t.Errorf("case %d: Server did not see correct TypeMeta. Got %v, expected %v",
|
||||
i, serv.lastRequest.TypeMeta, expTypeMeta)
|
||||
}
|
||||
if authenticated != tt.expectedAuthenticated {
|
||||
t.Errorf("case %d: Plugin returned incorrect authentication response. Got %t, expected %t.",
|
||||
i, authenticated, tt.expectedAuthenticated)
|
||||
}
|
||||
if user != nil && tt.expectedUser != nil && !reflect.DeepEqual(user, tt.expectedUser) {
|
||||
t.Errorf("case %d: Plugin returned incorrect user. Got %#v, expected %#v",
|
||||
i, user, tt.expectedUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type authenticationUserInfo v1beta1.UserInfo
|
||||
|
||||
func (a *authenticationUserInfo) GetName() string { return a.Username }
|
||||
func (a *authenticationUserInfo) GetUID() string { return a.UID }
|
||||
func (a *authenticationUserInfo) GetGroups() []string { return a.Groups }
|
||||
|
||||
func (a *authenticationUserInfo) GetExtra() map[string][]string {
|
||||
if a.Extra == nil {
|
||||
return nil
|
||||
}
|
||||
ret := map[string][]string{}
|
||||
for k, v := range a.Extra {
|
||||
ret[k] = []string(v)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Ensure v1beta1.UserInfo contains the fields necessary to implement the
|
||||
// user.Info interface.
|
||||
var _ user.Info = (*authenticationUserInfo)(nil)
|
||||
|
||||
// TestWebhookCache verifies that error responses from the server are not
|
||||
// cached, but successful responses are. It also ensures that the webhook
|
||||
// call is retried on 429 and 500+ errors
|
||||
func TestWebhookCacheAndRetry(t *testing.T) {
|
||||
serv := new(mockService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create an authenticator that caches successful responses "forever" (100 days).
|
||||
wh, err := newTokenAuthenticator(s.URL, clientCert, clientKey, caCert, 2400*time.Hour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
description string
|
||||
|
||||
token string
|
||||
allow bool
|
||||
code int
|
||||
|
||||
expectError bool
|
||||
expectOk bool
|
||||
expectCalls int
|
||||
}{
|
||||
{
|
||||
description: "t0k3n, 500 error, retries and fails",
|
||||
|
||||
token: "t0k3n",
|
||||
allow: false,
|
||||
code: 500,
|
||||
|
||||
expectError: true,
|
||||
expectOk: false,
|
||||
expectCalls: 5,
|
||||
},
|
||||
{
|
||||
description: "t0k3n, 404 error, fails (but no retry)",
|
||||
|
||||
token: "t0k3n",
|
||||
allow: false,
|
||||
code: 404,
|
||||
|
||||
expectError: true,
|
||||
expectOk: false,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
description: "t0k3n, 200 response, allowed, succeeds with a single call",
|
||||
|
||||
token: "t0k3n",
|
||||
allow: true,
|
||||
code: 200,
|
||||
|
||||
expectError: false,
|
||||
expectOk: true,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
description: "t0k3n, 500 response, disallowed, but never called because previous 200 response was cached",
|
||||
|
||||
token: "t0k3n",
|
||||
allow: false,
|
||||
code: 500,
|
||||
|
||||
expectError: false,
|
||||
expectOk: true,
|
||||
expectCalls: 0,
|
||||
},
|
||||
|
||||
{
|
||||
description: "an0th3r_t0k3n, 500 response, disallowed, should be called again with retries",
|
||||
|
||||
token: "an0th3r_t0k3n",
|
||||
allow: false,
|
||||
code: 500,
|
||||
|
||||
expectError: true,
|
||||
expectOk: false,
|
||||
expectCalls: 5,
|
||||
},
|
||||
{
|
||||
description: "an0th3r_t0k3n, 429 response, disallowed, should be called again with retries",
|
||||
|
||||
token: "an0th3r_t0k3n",
|
||||
allow: false,
|
||||
code: 429,
|
||||
|
||||
expectError: true,
|
||||
expectOk: false,
|
||||
expectCalls: 5,
|
||||
},
|
||||
{
|
||||
description: "an0th3r_t0k3n, 200 response, allowed, succeeds with a single call",
|
||||
|
||||
token: "an0th3r_t0k3n",
|
||||
allow: true,
|
||||
code: 200,
|
||||
|
||||
expectError: false,
|
||||
expectOk: true,
|
||||
expectCalls: 1,
|
||||
},
|
||||
{
|
||||
description: "an0th3r_t0k3n, 500 response, disallowed, but never called because previous 200 response was cached",
|
||||
|
||||
token: "an0th3r_t0k3n",
|
||||
allow: false,
|
||||
code: 500,
|
||||
|
||||
expectError: false,
|
||||
expectOk: true,
|
||||
expectCalls: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
func() {
|
||||
serv.allow = testcase.allow
|
||||
serv.statusCode = testcase.code
|
||||
serv.called = 0
|
||||
|
||||
_, ok, err := wh.AuthenticateToken(testcase.token)
|
||||
hasError := err != nil
|
||||
if hasError != testcase.expectError {
|
||||
t.Log(testcase.description)
|
||||
t.Errorf("Webhook returned HTTP %d, expected error=%v, but got error %v", testcase.code, testcase.expectError, err)
|
||||
}
|
||||
if serv.called != testcase.expectCalls {
|
||||
t.Log(testcase.description)
|
||||
t.Errorf("Expected %d calls, got %d", testcase.expectCalls, serv.called)
|
||||
}
|
||||
if ok != testcase.expectOk {
|
||||
t.Log(testcase.description)
|
||||
t.Errorf("Expected ok=%v, got %v", testcase.expectOk, ok)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
211
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/certs_test.go
generated
vendored
Normal file
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the webhook tests.
|
||||
|
||||
package webhook
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA6IVXGPX5yP2Q6TAlQXIQsavzSqZ973iZvpQBGTI6M98gTSVm
|
||||
eBYE3o7S8e6WTI3DCnWwqc8Md1rT92FtaQLwv+uMNXijLio5RVBqjUEbunD5In/+
|
||||
T/y5sE9P3CzcWy6CEhIvORAZj6UlvgZzbRwI91+EVFR5jd8JU0e/L9Ds1jLZFyQw
|
||||
Kc1ADo+Tj9O4l0WtpRlrhzTgoor4C3fAQZm0mq+llTnxCmw+lhy8t88bPG1cMwdd
|
||||
DtUTbpetc++2JZ62Q3F1nqcX1EcHDidR0x3j+3357BLkXRK4MQsWLYLzeZ3X1ghW
|
||||
XT062H866PcIV+MX4H58spMN5cVYk5YTneGihQIDAQABAoIBAHU7FQieq4ssXK1U
|
||||
+tOeQNBzUzxl6MSd11YApPUhH7sbWdvLaXhOEbJr6+rSUbDTIGzbnXBf1XcvsgLd
|
||||
eh4hv2PjzFMBObSC0VEjFDWXh/VeFB3SzlNhpfVAZ5EohQjrz+RwiqKIfXqw1vCR
|
||||
rAxswBCIdd1WodpngvocCEaBXYc4MblaPhJDVtxQe8ndEakkSDlX9Z3qIaIGyXRa
|
||||
NvY/yURVuXhwDDd7C2QBT6CXGWhldAg7xrRVTcIoqAUfZCgfis0H8cQOa1cGNsbW
|
||||
t/oHm1fYTxMKFPhWQG0oimx+XJ07BeGgraDRLnxxNnGWTg/W33bc0ZCxCVT0Q5p9
|
||||
kMMfQUECgYEA9cewTK4ZRKC4bTdwqLTh3cyMkbyN4kBHmB1mS2FV/T0l4oZThM//
|
||||
OZ6KFnRCuvfuJIOa70s2bqUYky8NTQAidnnbTW2nZ/E5JdeIBs1fAfadAqiPdmkf
|
||||
MhvjBF/XfLnbCuXx3jA7GmNCpunJysuLtQzwlQlZLojN231uS+3LFbkCgYEA8jCC
|
||||
MgKYaDWssQbT7zfk5MxyZIH3F9N8K2RBIDSVuMo/E1LCIJ06/k+4jdv8nAWYJXcN
|
||||
eyLG7l0SXqrpMBSc9+ZTJgmbo0Mw+npvJHbJvAtD/XOSPjlIqkzPAUrxuiBYxa5S
|
||||
IfKZibygXKAbQMEwY7I4sTbBtIyiQmo9csxt2S0CgYEAiBi1VSCquUfOGBw09BaF
|
||||
Y85aoHCqmHhDrMXK2T7i4MG1csQzBz4t8/gIOvrR4LpdUjbV2l/pmkctXoMVeGf0
|
||||
rWo4t51ar8HxhTTeC/Y4/9tRgiFYn5cCQTsT8F4p8tTvqA9AaWqHr8r7I3Yd2X/w
|
||||
sqahqcVtbskuRLYmF0FrzXECgYAeiR0xPwCGSxYt78Vy6OI0Ms7Ne1FzMJf8RJSt
|
||||
gdPKy70uK4YMZKaWf+iuAimUZmQrfRo3B0h7r0JsqzHhfQfZfbHIHvf/mq4nNp6i
|
||||
w1NmISl+YD71F3Xg+vQynodhx0hKDFOQsizHn/+8DffBr1nxh/v75AKCSCUBKLH8
|
||||
sme7NQKBgDHQac2TmDSelE2uXTGxEVDQs/EpdJh7oCTLQ99Xud/DsaCOrt2s7aRX
|
||||
1FEohsCaUnqwS07/iH2o6Qb/qOteufB9I7FG85nAvqmP5dI4crGNNa8Rl6fXJaR8
|
||||
TUwpZmylTKEJ9zLt2PADglyDrQ2D+1WNzh966Oo9c+kZt4WJM0aF
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAKK9m2Cfg5uhMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQDohVcY9fnI/ZDpMCVBchCxq/NKpn3veJm+lAEZ
|
||||
Mjoz3yBNJWZ4FgTejtLx7pZMjcMKdbCpzwx3WtP3YW1pAvC/64w1eKMuKjlFUGqN
|
||||
QRu6cPkif/5P/LmwT0/cLNxbLoISEi85EBmPpSW+BnNtHAj3X4RUVHmN3wlTR78v
|
||||
0OzWMtkXJDApzUAOj5OP07iXRa2lGWuHNOCiivgLd8BBmbSar6WVOfEKbD6WHLy3
|
||||
zxs8bVwzB10O1RNul61z77YlnrZDcXWepxfURwcOJ1HTHeP7ffnsEuRdErgxCxYt
|
||||
gvN5ndfWCFZdPTrYfzro9whX4xfgfnyykw3lxViTlhOd4aKFAgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBSumZL6MMwmFGyhQAwl/v0lYDzdZjAfBgNVHSMEGDAWgBSumZL6MMwm
|
||||
FGyhQAwl/v0lYDzdZjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG
|
||||
6k+bZxKYq4PVZHWTKA7RSjv95FMMr4RSFwKn/n8TUD44ANWYqDrEfVmxAMn3NVK9
|
||||
ckA8mIRym4IGiWD9eBGgPNNtbAq8Wl/9+5qbDMerpXuRnG3wNY7RU75Rl008m52r
|
||||
c2i86ZPUi2fAJZyMf5StWE21oKiDYYQqlB6xxsIj6OHhf7536vEysoztNX5FpS2n
|
||||
q8wG0EhJVhG+Qyww8IlZA5Cjoh71Eqkcwb4cuLjPypxmLm0ywZ/6KgzV+IF+CT2v
|
||||
TJIpMokDUKlRi9cWSqkWXFE6xbCmhrrwKYsi0X6Vvi7a0pmOnSzKCQl8jN8u4A9R
|
||||
xar2YeJ6mCCzSAPM69DP
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAon7dRV4Br10dLcf8zgs/hOHouELveFr8tuWVIFivxSdnac2k
|
||||
6dM4iQ2uYS9nTXxNhyJJ/TX/MHEYc4gSXoqUbtx9jE3VA4mCKDhO7cJtCYxq0QV/
|
||||
PlQCiAPjn5nUMt9ACdii7/uTFDl46bK9K6ajvKHfHoWeYaJsF54kxBq5IMj+QaB2
|
||||
nc+pba00bGG09sYcHyD37QH+ugx64x+21xMYj2LB/uPoqZM0kj1GHPxAs8GqFq2P
|
||||
gwkv589AlHqt2iMCTAqED2jcg4FeS2r1DeYHwGyGAPfWTdA8RZ+gZ/P0Gj91T+4B
|
||||
9srR7BybUFjf1KxEcvPXBvP5r8OwOiYjS8hx/wIDAQABAoIBAQCVBQ9bfDjDX/tQ
|
||||
buVS+FHKRXss8IW4tIiqGqXGQk7/2YEnMKaaoVBpsBhJnDV6hBJ9aV69TnW3MSCh
|
||||
YxqlhSVW/fJNZ1uAoOyygeEwfmuMpC+ZfRcSS+z+W8K2LVbDSKXr4babqvVZSNOw
|
||||
TnDZxTrH1RNPZG65T0Ed77P7/B3nB7aeB2UMuHMQNZ3KrYDTck2R2uTGp+29TplN
|
||||
blS4VAg2/9KqFr7jkS3/C4jjxVd7d9mm0VdAvLcvENVXqSTYV8xDp+VLTnmtXi5f
|
||||
LXcopS+zKtKqT7MM7RA2sKrmSfrQBIXW2E1kfDFtpZHajhDutdYkSTH665W1G23M
|
||||
dIgy3ajhAoGBANE4AhMUVfQqXUCU0UjUDxiOy/8XcKiW/dKhRR1DOQY24J/k+UWv
|
||||
PEGVcBW4tgalYkTl/AW6hsNfubZaJuw05cHIKdL3df6ug7BUiJpmIv3sjrvPRYvA
|
||||
WY1UTb3EJrswGz8S2l5+2S3WFTCfK7S6N6Stfi1x6rMJBuOss7HGqdh3AoGBAMbU
|
||||
WavRqGRsvJFfE5bahXbFpkGWT++BTMP+lzK31z24JjmJdwO+ABWU4/xaXayA4skH
|
||||
PrzlYUcGJWIedb6W4dvz0sA59yflQzYmREkQPE+wbyor003y7mB8LpFiCnfaFhRn
|
||||
hoowkyIY+xM4UeDXWWt3DhBElgfA8fYZdiNJEhy5AoGBAMwYUw3BvMffu/CQPElL
|
||||
dR6DzsUeXKxZ/2pGIGIXfb1uM1pHyFQOSj3ARgMqmYeKNn73zA7akzRsYYJeF7I9
|
||||
OBT96q7+8IBuRdDx5gCYunHzHppf7HwUPEf+gYgpnY7lsu6ouZWNMNfiC/HOlJhN
|
||||
QJLJHFnA0y+sEqhvhSxbnLypAoGBALHCZ+kVKFegX3YYaosUEv589obsu8qE7vzL
|
||||
QKI3elfTq1kFbUILPEgPNUUIBXeUQy03LP/0k2PMOt/eG6apfoQHGQSCzlT8w3pF
|
||||
/AbWXRVhyAEL7X5jEntwirGv1WwRrmvPopkplGGHs/EbCRjbbzaE2i3xI7EK70f2
|
||||
u4gQbAEBAoGAVR4u8g5Tx2Gunzh7tfJJ5e3xGBGS3Yq+JqUVNI6t6KIAPh0rM+aD
|
||||
9tDgcwn8Vn5YU7YkqA2T8OOFsbJfrfZ7y7+oeMFukuIyxgmy9n/V/tCIrV/lR7A5
|
||||
3iYhanTUbQswx19pSRgsXi7fo9Fi/dmUwyHi18uz5FdLyCTsMbf3uA8=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAPqJyUfmRxGLMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDwAwggEKAoIBAQCift1FXgGvXR0tx/zOCz+E4ei4Qu94Wvy25ZUg
|
||||
WK/FJ2dpzaTp0ziJDa5hL2dNfE2HIkn9Nf8wcRhziBJeipRu3H2MTdUDiYIoOE7t
|
||||
wm0JjGrRBX8+VAKIA+OfmdQy30AJ2KLv+5MUOXjpsr0rpqO8od8ehZ5homwXniTE
|
||||
GrkgyP5BoHadz6ltrTRsYbT2xhwfIPftAf66DHrjH7bXExiPYsH+4+ipkzSSPUYc
|
||||
/ECzwaoWrY+DCS/nz0CUeq3aIwJMCoQPaNyDgV5LavUN5gfAbIYA99ZN0DxFn6Bn
|
||||
8/QaP3VP7gH2ytHsHJtQWN/UrERy89cG8/mvw7A6JiNLyHH/AgMBAAGjUDBOMB0G
|
||||
A1UdDgQWBBS6IGeGHZCylibt0GzY0dP6C0J9VjAfBgNVHSMEGDAWgBS6IGeGHZCy
|
||||
libt0GzY0dP6C0J9VjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi
|
||||
A1dp75kbePFZsUNjxN6B/Pv0vSoaOjQkc4hpxKbI4VRCuPGmMRFYTlKCzoZ53OqQ
|
||||
2Jmu1Zbzel/bV5vXrW0BOfUpfWYzd/usIJEuTgU8ijBIB+IHAXYwwxeKRcz3C+7+
|
||||
9RBMF7gSg9pU2hrSvjhh7Q96IMJ42Z7tI3WD8SZaQLjY1NW1jrQVsg66ktdMke7x
|
||||
zC8oIRIBH4W6l5s7jtZx1k305NE04pigcFLxCxOmicKd66ysI5hAZkD7y0dgwgtL
|
||||
IqCQy6t7uJDydRiNRfPFr9Eg7uOu83JGw11f3bGVhJVCbzHyKddvkQsQbdaMHRgZ
|
||||
zgmWLORg+ls1H1oaJiNW
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/9bhfMr0LaEKng1lR
|
||||
XopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv0EUnyx7WR3V21ObZ
|
||||
iQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jNz+OM/VmqsgzoKLoa
|
||||
bGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvhjqo8NRqzZLg79ldY
|
||||
aKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJG8eZUmjQpUMv7Jk3
|
||||
qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABAoIBAQCjzeFijwzKKL4w
|
||||
0B1IBhi3WeReFPG4nkt1ssQPBYrrJPKBZgHO13A1STI78wFn/OdYpajfF8hI8HT1
|
||||
BiGVsu27Eb9TC60b/x6OtmeCEk+044LRbtu+9NZUb7HHHogI0l++X0KXZ0coE38L
|
||||
1izwNvfrmLa+QaIgHMtAg9EnJwJ993n4L31GovWh8MGmVyJX/F92y+agNwWkNYYp
|
||||
iLWFyon+HbNVL13WOOYnYEdA8Me3+Gucy1EOfWMF7mgmuO2vcfnxXd6b16VjAwtE
|
||||
jGCQfzgpWGHLpgwoBgDmnPUbdNPUT3MbA9jqG2mlnBSBQveYgKrmFdDYnAjnCM4L
|
||||
uF2ztBzhAoGBAOYc3sF3YjpIIMsyH9omqtfOuxO+oZkpb2vB9kgdXCDcG870M+BC
|
||||
bNzV7DCSV8QAUqjKQK1r3gq62UZMLXZbG8x5UnM8/EK0X1CSqygwSWjGpYxIQEhh
|
||||
O2lq69WipkNDnX1ZmrvEdHD2cxqkkXZ7bdRKRasrFJgvJa3XbiJ18KYxAoGBAMpe
|
||||
/72EcX9oL3KT8tJSpvasrw17p/XkMMCxTp3IDb3krF/4k5bYF61F68/LNSy3xkos
|
||||
ZrPUK/U160iuHSYCpMq4pPmlWgKq4hmUMOt+8Yy622zDlugarq9VLqvSdGHm+r6F
|
||||
5fHilXB0UsTXXOuLZWLcSQ0MBgiaVCLb2AmXZhhXAoGAEjSchw/r7JKCTbE0hezj
|
||||
PVm0wVYmsNhvYUYiNwhjnpHrfU8iv45h0IL4QcuCOBaSc5o0zcOn+I9Z207xldiV
|
||||
dXLvzAA6MQjWNai08+QGGs0EkfmxZEiVC70S1X8dylqSHjW1oT9kuv80khoNDCOt
|
||||
x8rsgiNRaMzqHTvbEczk8jECgYB2Od+wSULBSw2FI5fVdcHjFGlEODycs44j1LH4
|
||||
DZqxmHl3q9IVavMSIGouQCo1kLuAM8ZgQpDXtYNaN5YB0cOSRyLiUc5vBoQGq4OU
|
||||
4Nme/L8aIH315TiuZ9ZXPSEO3REZ40G9+UCSrPJ52tOHLC2z/ruSqraPqhGDN+pT
|
||||
WCamCwKBgEPa+kVrPs0khQH8+sbFbU9ifj4fhPAiSwj2fKuXFro2mE205vAMHye/
|
||||
SYs/mPzYzKSd7F+7Zk6oVrgFVskTiReW3phF+cIl+CdcnIenF0jW1PVgGw8znu+P
|
||||
SbHSdqV+tB7AW2J7sH8TZtfMUPAK2MJ4S+1uaHK86K79ym4Rz0E2
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC/zCCAeegAwIBAgIJAN7rkfhaX8FZMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfc2VydmVyMIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/
|
||||
9bhfMr0LaEKng1lRXopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv
|
||||
0EUnyx7WR3V21ObZiQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jN
|
||||
z+OM/VmqsgzoKLoabGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvh
|
||||
jqo8NRqzZLg79ldYaKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJ
|
||||
G8eZUmjQpUMv7Jk3qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABo0Aw
|
||||
PjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATAP
|
||||
BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCZHB9UCl2CfylWP3db
|
||||
xUamawnRoTYlsOcUh4f2tlHMY+vYiEStN+LECk62YpeaHl/nz/lk7g1Jx9aua39z
|
||||
wFIHiXYhwSWOtgmzpbxYLye1yajKXbbA1T7mEZJTjewDB9i1LcB9W3EV5VJ8Y1GY
|
||||
AYKuKQ4Cb1HrqLsrw/1PDm0VouWzf2ESv8CBvAv/pYLVfwgS6WsUqn9wycpLEnqQ
|
||||
RK66/AoiOaxUIjEP0O1q6pi6Mag7XAfeNtx8J0VGt4cRG4rvWCbKVUyvKfUCkipN
|
||||
gJu09S+KIz3x1CJLRuJX9tB+cFnnykDLQ2IKg7x44O83ikNk8+Di3iT/awCguWPE
|
||||
rHh5
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDjwQAlnHoLf/lz+Fh2
|
||||
DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V9Ll3aY/6xVRCenWi
|
||||
UNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL0q7vw003v5TBqzC/
|
||||
FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqSfbBg6B/A3OwpbIfx
|
||||
09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na+ZB+gMt4+e2Y7qNz
|
||||
76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABAoIBAQCJpGzJSzC2W8DM
|
||||
sMqBNdCUMKZ0cwq13b7W2BimGJKyCOOi3HxUZEaYf/2Leyt+PPBm72SML7dzvDh3
|
||||
qa269gKVqmkSqa2vF763qQbRuYo14msTQzA7+s3TUMbZs2UaDOE6nZIzs1QdEElp
|
||||
1DvYXHz+/rD7Adj9VF+mMnouqQoy5kgJTnVZ8sOyl/9R6F67xKBIvcrtPfqVZzuG
|
||||
2hGAMUnawxFUajQC7BynIeCWrk79SUmQgilyNgRdY6+rGh2uRupIxuiAukPtuag1
|
||||
Li+wnNl1UGECtv9ZnnboKvg2334k5vhYScGRJbwbr7Zt3ZaNd0Z/DE9kTtnhBS7v
|
||||
9qWdc7CBAoGBAPR4hz1fhHFiPmMEAGuiNms6WdyIfyonIRYas8ZDKUQGdxn/aO8a
|
||||
CURktHRlm6iYT+j1cbf3RnLEN9pNr3V2EySOMc+rXUNifcP7Vl53akAQmISUfQWG
|
||||
UfwaNLicbavf6m9UCiwWByAZghqDZSLiwmLHIjGcSJQiFuhZryioDydxAoGBAPED
|
||||
q1Z7oNhzwRYie9OB5ylnrCH8G3yFl8egBmQrPJKIQHA9mAGg01LEJwQNoWewyAWx
|
||||
jfeFtWvIgZkj49cluZgHYyF81jApaNraxtXAgIwC1n7oAIttmeklZ/V1HntknG3Y
|
||||
ow2bV/NA3aPOTPYxW8oDv7U9lvwve7kIFxeWjE/5AoGASfXI3G1wUSkqvKPySJ3b
|
||||
ntcZZpm49xS9csWDS+D3tAfMsoXNxkB3O0TIP0qaLAhgbJcM314k5wWr7BSCl6Ow
|
||||
KOgH887hOUirycXZHF0+PMGIktulcy1u0jlPZ+aTW2MztpiTN0E2yKRO8xx7VXGK
|
||||
431hP+cLIh2qFoNDdaZaZ1ECgYEArw++PWQxMefqgVxs2vXJZY7TPiA0Ct+ynqKC
|
||||
4fFx3vGu9JgYuF4MAVtPB6eq7HlA4LnWZ8ssOuz6DbU/AoB5bY84FxPpNDRv4D/3
|
||||
Gz3nYUuSZ72234+tsuaju2vlxzUOVs97qB+E48Di/N+VkWHKzVKpxkjFScpnsL/K
|
||||
niyRIGkCgYEAriuxbOCczL/j6u2Xq1ngEsGg+RXjtOYGoJWo7B8qlVL4nF8w1Nbd
|
||||
FxEmOChQgUnBdwb93qHCSq0Fidf7OfewrfJJkstWIh3zPS4umLZo7R3YblncpdfT
|
||||
M197uckIWccZml2jF/c7nvK+MjwDRhkOl2a6HzMxcdBwYUJmSwmIZ4k=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC7jCCAdagAwIBAgIJAN7rkfhaX8FaMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfY2xpZW50MIIBIjANBgkq
|
||||
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDj
|
||||
wQAlnHoLf/lz+Fh2DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V
|
||||
9Ll3aY/6xVRCenWiUNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL
|
||||
0q7vw003v5TBqzC/FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqS
|
||||
fbBg6B/A3OwpbIfx09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na
|
||||
+ZB+gMt4+e2Y7qNz76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABoy8w
|
||||
LTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEA2IZNhkVrSTAIeP2N2WzOHqbFbGyO+NA8G9Hb5fiX
|
||||
e1YS2Ku3ERYNr+HLxNHCsXiSUKjjBmXMc4z0XaHJznEKEbotZftjTlTQlHi3/5vm
|
||||
dIG18pmO/E5ebVXl6pU96v/hBd8N5rWp9WUKgP0y59r/JA+oNpmd10A+RyaOyrFK
|
||||
rBm8Z8rvDYMrXSpOwx9BNDuhqzbdG8MYw5vO55Er3hwTXoapsMqSh5s9+OFFpUJi
|
||||
2uEoQlwWiYRtQj6g4wgr4woDEbv8XxsHqGfs+GSnmRsB69xRI24lEtC+nS6Rz3Sh
|
||||
YWeN0gD8PsQC1KJVv6xCGo1yXSEwytRMB23XYtAZahLdLg==
|
||||
-----END CERTIFICATE-----`)
|
102
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/gencerts.sh
generated
vendored
Executable file
102
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/gencerts.sh
generated
vendored
Executable file
|
@ -0,0 +1,102 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook authz plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=webhook_authz_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=webhook_authz_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook_authz_server" -config server.conf
|
||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||
|
||||
# Create a client certiticate
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=webhook_authz_client" -config client.conf
|
||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package webhook" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
229
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go
generated
vendored
Normal file
229
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go
generated
vendored
Normal file
|
@ -0,0 +1,229 @@
|
|||
/*
|
||||
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 webhook implements the authorizer.Authorizer interface using HTTP webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/util/cache"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
authorizationclient "k8s.io/client-go/kubernetes/typed/authorization/v1beta1"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
authorization "k8s.io/client-go/pkg/apis/authorization/v1beta1"
|
||||
|
||||
_ "k8s.io/client-go/pkg/apis/authorization/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{authorization.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
const retryBackoff = 500 * time.Millisecond
|
||||
|
||||
// Ensure Webhook implements the authorizer.Authorizer interface.
|
||||
var _ authorizer.Authorizer = (*WebhookAuthorizer)(nil)
|
||||
|
||||
type WebhookAuthorizer struct {
|
||||
subjectAccessReview authorizationclient.SubjectAccessReviewInterface
|
||||
responseCache *cache.LRUExpireCache
|
||||
authorizedTTL time.Duration
|
||||
unauthorizedTTL time.Duration
|
||||
initialBackoff time.Duration
|
||||
}
|
||||
|
||||
// NewFromInterface creates a WebhookAuthorizer using the given subjectAccessReview client
|
||||
func NewFromInterface(subjectAccessReview authorizationclient.SubjectAccessReviewInterface, authorizedTTL, unauthorizedTTL time.Duration) (*WebhookAuthorizer, error) {
|
||||
return newWithBackoff(subjectAccessReview, authorizedTTL, unauthorizedTTL, retryBackoff)
|
||||
}
|
||||
|
||||
// New creates a new WebhookAuthorizer from the provided kubeconfig file.
|
||||
//
|
||||
// The config's cluster field is used to refer to the remote service, user refers to the returned authorizer.
|
||||
//
|
||||
// # clusters refers to the remote service.
|
||||
// clusters:
|
||||
// - name: name-of-remote-authz-service
|
||||
// cluster:
|
||||
// certificate-authority: /path/to/ca.pem # CA for verifying the remote service.
|
||||
// server: https://authz.example.com/authorize # URL of remote service to query. Must use 'https'.
|
||||
//
|
||||
// # users refers to the API server's webhook configuration.
|
||||
// users:
|
||||
// - name: name-of-api-server
|
||||
// user:
|
||||
// client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
|
||||
// client-key: /path/to/key.pem # key matching the cert
|
||||
//
|
||||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
|
||||
func New(kubeConfigFile string, authorizedTTL, unauthorizedTTL time.Duration) (*WebhookAuthorizer, error) {
|
||||
subjectAccessReview, err := subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWithBackoff(subjectAccessReview, authorizedTTL, unauthorizedTTL, retryBackoff)
|
||||
}
|
||||
|
||||
// newWithBackoff allows tests to skip the sleep.
|
||||
func newWithBackoff(subjectAccessReview authorizationclient.SubjectAccessReviewInterface, authorizedTTL, unauthorizedTTL, initialBackoff time.Duration) (*WebhookAuthorizer, error) {
|
||||
return &WebhookAuthorizer{
|
||||
subjectAccessReview: subjectAccessReview,
|
||||
responseCache: cache.NewLRUExpireCache(1024),
|
||||
authorizedTTL: authorizedTTL,
|
||||
unauthorizedTTL: unauthorizedTTL,
|
||||
initialBackoff: initialBackoff,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Authorize makes a REST request to the remote service describing the attempted action as a JSON
|
||||
// serialized api.authorization.v1beta1.SubjectAccessReview object. An example request body is
|
||||
// provided bellow.
|
||||
//
|
||||
// {
|
||||
// "apiVersion": "authorization.k8s.io/v1beta1",
|
||||
// "kind": "SubjectAccessReview",
|
||||
// "spec": {
|
||||
// "resourceAttributes": {
|
||||
// "namespace": "kittensandponies",
|
||||
// "verb": "GET",
|
||||
// "group": "group3",
|
||||
// "resource": "pods"
|
||||
// },
|
||||
// "user": "jane",
|
||||
// "group": [
|
||||
// "group1",
|
||||
// "group2"
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The remote service is expected to fill the SubjectAccessReviewStatus field to either allow or
|
||||
// disallow access. A permissive response would return:
|
||||
//
|
||||
// {
|
||||
// "apiVersion": "authorization.k8s.io/v1beta1",
|
||||
// "kind": "SubjectAccessReview",
|
||||
// "status": {
|
||||
// "allowed": true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// To disallow access, the remote service would return:
|
||||
//
|
||||
// {
|
||||
// "apiVersion": "authorization.k8s.io/v1beta1",
|
||||
// "kind": "SubjectAccessReview",
|
||||
// "status": {
|
||||
// "allowed": false,
|
||||
// "reason": "user does not have read access to the namespace"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bool, reason string, err error) {
|
||||
r := &authorization.SubjectAccessReview{}
|
||||
if user := attr.GetUser(); user != nil {
|
||||
r.Spec = authorization.SubjectAccessReviewSpec{
|
||||
User: user.GetName(),
|
||||
Groups: user.GetGroups(),
|
||||
Extra: convertToSARExtra(user.GetExtra()),
|
||||
}
|
||||
}
|
||||
|
||||
if attr.IsResourceRequest() {
|
||||
r.Spec.ResourceAttributes = &authorization.ResourceAttributes{
|
||||
Namespace: attr.GetNamespace(),
|
||||
Verb: attr.GetVerb(),
|
||||
Group: attr.GetAPIGroup(),
|
||||
Version: attr.GetAPIVersion(),
|
||||
Resource: attr.GetResource(),
|
||||
Subresource: attr.GetSubresource(),
|
||||
Name: attr.GetName(),
|
||||
}
|
||||
} else {
|
||||
r.Spec.NonResourceAttributes = &authorization.NonResourceAttributes{
|
||||
Path: attr.GetPath(),
|
||||
Verb: attr.GetVerb(),
|
||||
}
|
||||
}
|
||||
key, err := json.Marshal(r.Spec)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if entry, ok := w.responseCache.Get(string(key)); ok {
|
||||
r.Status = entry.(authorization.SubjectAccessReviewStatus)
|
||||
} else {
|
||||
var (
|
||||
result *authorization.SubjectAccessReview
|
||||
err error
|
||||
)
|
||||
webhook.WithExponentialBackoff(w.initialBackoff, func() error {
|
||||
result, err = w.subjectAccessReview.Create(r)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
// An error here indicates bad configuration or an outage. Log for debugging.
|
||||
glog.Errorf("Failed to make webhook authorizer request: %v", err)
|
||||
return false, "", err
|
||||
}
|
||||
r.Status = result.Status
|
||||
if r.Status.Allowed {
|
||||
w.responseCache.Add(string(key), r.Status, w.authorizedTTL)
|
||||
} else {
|
||||
w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL)
|
||||
}
|
||||
}
|
||||
return r.Status.Allowed, r.Status.Reason, nil
|
||||
}
|
||||
|
||||
func convertToSARExtra(extra map[string][]string) map[string]authorization.ExtraValue {
|
||||
if extra == nil {
|
||||
return nil
|
||||
}
|
||||
ret := map[string]authorization.ExtraValue{}
|
||||
for k, v := range extra {
|
||||
ret[k] = authorization.ExtraValue(v)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// subjectAccessReviewInterfaceFromKubeconfig builds a client from the specified kubeconfig file,
|
||||
// and returns a SubjectAccessReviewInterface that uses that client. Note that the client submits SubjectAccessReview
|
||||
// requests to the exact path specified in the kubeconfig file, so arbitrary non-API servers can be targeted.
|
||||
func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string) (authorizationclient.SubjectAccessReviewInterface, error) {
|
||||
gw, err := webhook.NewGenericWebhook(api.Registry, api.Codecs, kubeConfigFile, groupVersions, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &subjectAccessReviewClient{gw}, nil
|
||||
}
|
||||
|
||||
type subjectAccessReviewClient struct {
|
||||
w *webhook.GenericWebhook
|
||||
}
|
||||
|
||||
func (t *subjectAccessReviewClient) Create(subjectAccessReview *authorization.SubjectAccessReview) (*authorization.SubjectAccessReview, error) {
|
||||
result := &authorization.SubjectAccessReview{}
|
||||
err := t.w.RestClient.Post().Body(subjectAccessReview).Do().Into(result)
|
||||
return result, err
|
||||
}
|
620
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_test.go
generated
vendored
Normal file
620
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_test.go
generated
vendored
Normal file
|
@ -0,0 +1,620 @@
|
|||
/*
|
||||
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 webhook
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/pkg/apis/authorization/v1beta1"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
)
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
data := struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
}{
|
||||
CA: filepath.Join(dir, "ca.pem"),
|
||||
Cert: filepath.Join(dir, "clientcert.pem"),
|
||||
Key: filepath.Join(dir, "clientkey.pem"),
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{data.CA, caCert},
|
||||
{data.Cert, clientCert},
|
||||
{data.Key, clientKey},
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
configTmpl string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
msg: "a single cluster and single user",
|
||||
configTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://authz.example.com
|
||||
name: foobar
|
||||
users:
|
||||
- name: a cluster
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with no context",
|
||||
configTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://authz.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://authz.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with a context",
|
||||
configTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://authz.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://authz.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: barfoo
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
msg: "cluster with bad certificate path specified",
|
||||
configTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://authz.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://authz.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: foobar
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
err := func() error {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
|
||||
tmpl, err := template.New("test").Parse(tt.configTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
if err := tmpl.Execute(tempfile, data); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
// Create a new authorizer
|
||||
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error building sar client: %v", err)
|
||||
}
|
||||
_, err = newWithBackoff(sarClient, 0, 0, 0)
|
||||
return err
|
||||
}()
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
|
||||
}
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service mocks a remote service.
|
||||
type Service interface {
|
||||
Review(*v1beta1.SubjectAccessReview)
|
||||
HTTPStatusCode() int
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
const webhookPath = "/testserver"
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, fmt.Sprintf("unexpected method: %v", r.Method), http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if r.URL.Path != webhookPath {
|
||||
http.Error(w, fmt.Sprintf("unexpected path: %v", r.URL.Path), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var review v1beta1.SubjectAccessReview
|
||||
bodyData, _ := ioutil.ReadAll(r.Body)
|
||||
if err := json.Unmarshal(bodyData, &review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// ensure we received the serialized review as expected
|
||||
if review.APIVersion != "authorization.k8s.io/v1beta1" {
|
||||
http.Error(w, fmt.Sprintf("wrong api version: %s", string(bodyData)), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// once we have a successful request, always call the review to record that we were called
|
||||
s.Review(&review)
|
||||
if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
|
||||
http.Error(w, "HTTP Error", s.HTTPStatusCode())
|
||||
return
|
||||
}
|
||||
type status struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Reason string `json:"reason"`
|
||||
EvaluationError string `json:"evaluationError"`
|
||||
}
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Status: status{review.Status.Allowed, review.Status.Reason, review.Status.EvaluationError},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
|
||||
// Adjust the path to point to our custom path
|
||||
serverURL, _ := url.Parse(server.URL)
|
||||
serverURL.Path = webhookPath
|
||||
server.URL = serverURL.String()
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to allow all or deny all authorization requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
statusCode int
|
||||
called int
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1beta1.SubjectAccessReview) {
|
||||
m.called++
|
||||
r.Status.Allowed = m.allow
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
func (m *mockService) HTTPStatusCode() int { return m.statusCode }
|
||||
|
||||
// newAuthorizer creates a temporary kubeconfig file from the provided arguments and attempts to load
|
||||
// a new WebhookAuthorizer from it.
|
||||
func newAuthorizer(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration) (*WebhookAuthorizer, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sarClient, err := subjectAccessReviewInterfaceFromKubeconfig(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error building sar client: %v", err)
|
||||
}
|
||||
return newWithBackoff(sarClient, cacheTime, cacheTime, 0)
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantAuth, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
wantAuth: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAuth: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAuth: true,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newAuthorizer(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, 0)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := authorizer.AttributesRecord{User: &user.DefaultInfo{}}
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
authorized, _, err := wh.Authorize(attr)
|
||||
if tt.wantAuth {
|
||||
if !authorized {
|
||||
t.Errorf("expected successful authorization")
|
||||
}
|
||||
} else {
|
||||
if authorized {
|
||||
t.Errorf("expected failed authorization")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making authorization request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to authorize with AllowAll policy: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
if authorized, _, _ := wh.Authorize(attr); authorized {
|
||||
t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// recorderService records all access review requests.
|
||||
type recorderService struct {
|
||||
last v1beta1.SubjectAccessReview
|
||||
err error
|
||||
}
|
||||
|
||||
func (rec *recorderService) Review(r *v1beta1.SubjectAccessReview) {
|
||||
rec.last = v1beta1.SubjectAccessReview{}
|
||||
rec.last = *r
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
|
||||
func (rec *recorderService) Last() (v1beta1.SubjectAccessReview, error) {
|
||||
return rec.last, rec.err
|
||||
}
|
||||
|
||||
func (rec *recorderService) HTTPStatusCode() int { return 200 }
|
||||
|
||||
func TestWebhook(t *testing.T) {
|
||||
serv := new(recorderService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
wh, err := newAuthorizer(s.URL, clientCert, clientKey, caCert, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expTypeMeta := metav1.TypeMeta{
|
||||
APIVersion: "authorization.k8s.io/v1beta1",
|
||||
Kind: "SubjectAccessReview",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
attr authorizer.Attributes
|
||||
want v1beta1.SubjectAccessReview
|
||||
}{
|
||||
{
|
||||
attr: authorizer.AttributesRecord{User: &user.DefaultInfo{}},
|
||||
want: v1beta1.SubjectAccessReview{
|
||||
TypeMeta: expTypeMeta,
|
||||
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||
NonResourceAttributes: &v1beta1.NonResourceAttributes{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
attr: authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "jane"}},
|
||||
want: v1beta1.SubjectAccessReview{
|
||||
TypeMeta: expTypeMeta,
|
||||
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||
User: "jane",
|
||||
NonResourceAttributes: &v1beta1.NonResourceAttributes{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
attr: authorizer.AttributesRecord{
|
||||
User: &user.DefaultInfo{
|
||||
Name: "jane",
|
||||
UID: "1",
|
||||
Groups: []string{"group1", "group2"},
|
||||
},
|
||||
Verb: "GET",
|
||||
Namespace: "kittensandponies",
|
||||
APIGroup: "group3",
|
||||
APIVersion: "v7beta3",
|
||||
Resource: "pods",
|
||||
Subresource: "proxy",
|
||||
Name: "my-pod",
|
||||
ResourceRequest: true,
|
||||
Path: "/foo",
|
||||
},
|
||||
want: v1beta1.SubjectAccessReview{
|
||||
TypeMeta: expTypeMeta,
|
||||
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||
User: "jane",
|
||||
Groups: []string{"group1", "group2"},
|
||||
ResourceAttributes: &v1beta1.ResourceAttributes{
|
||||
Verb: "GET",
|
||||
Namespace: "kittensandponies",
|
||||
Group: "group3",
|
||||
Version: "v7beta3",
|
||||
Resource: "pods",
|
||||
Subresource: "proxy",
|
||||
Name: "my-pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
authorized, _, err := wh.Authorize(tt.attr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !authorized {
|
||||
t.Errorf("case %d: authorization failed", i)
|
||||
continue
|
||||
}
|
||||
|
||||
gotAttr, err := serv.Last()
|
||||
if err != nil {
|
||||
t.Errorf("case %d: failed to deserialize webhook request: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(gotAttr, tt.want) {
|
||||
t.Errorf("case %d: got != want:\n%s", i, diff.ObjectGoPrintDiff(gotAttr, tt.want))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type webhookCacheTestCase struct {
|
||||
attr authorizer.AttributesRecord
|
||||
|
||||
allow bool
|
||||
statusCode int
|
||||
|
||||
expectedErr bool
|
||||
expectedAuthorized bool
|
||||
expectedCalls int
|
||||
}
|
||||
|
||||
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *WebhookAuthorizer, tests []webhookCacheTestCase) {
|
||||
for i, test := range tests {
|
||||
serv.called = 0
|
||||
serv.allow = test.allow
|
||||
serv.statusCode = test.statusCode
|
||||
authorized, _, err := wh.Authorize(test.attr)
|
||||
if test.expectedErr && err == nil {
|
||||
t.Errorf("%d: Expected error", i)
|
||||
continue
|
||||
} else if !test.expectedErr && err != nil {
|
||||
t.Errorf("%d: unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if test.expectedAuthorized != authorized {
|
||||
t.Errorf("%d: expected authorized=%v, got %v", i, test.expectedAuthorized, authorized)
|
||||
}
|
||||
|
||||
if test.expectedCalls != serv.called {
|
||||
t.Errorf("%d: expected %d calls, got %d", i, test.expectedCalls, serv.called)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookCache verifies that error responses from the server are not
|
||||
// cached, but successful responses are.
|
||||
func TestWebhookCache(t *testing.T) {
|
||||
serv := new(mockService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create an authorizer that caches successful responses "forever" (100 days).
|
||||
wh, err := newAuthorizer(s.URL, clientCert, clientKey, caCert, 2400*time.Hour)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aliceAttr := authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "alice"}}
|
||||
bobAttr := authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "bob"}}
|
||||
|
||||
tests := []webhookCacheTestCase{
|
||||
// server error and 429's retry
|
||||
{attr: aliceAttr, allow: false, statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCalls: 5},
|
||||
{attr: aliceAttr, allow: false, statusCode: 429, expectedErr: true, expectedAuthorized: false, expectedCalls: 5},
|
||||
// regular errors return errors but do not retry
|
||||
{attr: aliceAttr, allow: false, statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCalls: 1},
|
||||
{attr: aliceAttr, allow: false, statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCalls: 1},
|
||||
{attr: aliceAttr, allow: false, statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCalls: 1},
|
||||
// successful responses are cached
|
||||
{attr: aliceAttr, allow: true, statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCalls: 1},
|
||||
// later requests within the cache window don't hit the backend
|
||||
{attr: aliceAttr, allow: false, statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCalls: 0},
|
||||
|
||||
// a request with different attributes doesn't hit the cache
|
||||
{attr: bobAttr, allow: false, statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCalls: 5},
|
||||
// successful response for other attributes is cached
|
||||
{attr: bobAttr, allow: true, statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCalls: 1},
|
||||
// later requests within the cache window don't hit the backend
|
||||
{attr: bobAttr, allow: false, statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCalls: 0},
|
||||
}
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, tests)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue