Add glide.yaml and vendor deps

This commit is contained in:
Dalton Hubble 2016-12-03 22:43:32 -08:00
parent db918f12ad
commit 5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions

17
vendor/k8s.io/kubernetes/plugin/pkg/auth/BUILD generated vendored Normal file
View file

@ -0,0 +1,17 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
tags = ["automanaged"],
)

3
vendor/k8s.io/kubernetes/plugin/pkg/auth/OWNERS generated vendored Normal file
View file

@ -0,0 +1,3 @@
assignees:
- erictune
- liggitt

View file

@ -0,0 +1,17 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
tags = ["automanaged"],
)

View 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/kubernetes/plugin/pkg/auth/authenticator"

View file

@ -0,0 +1,17 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["allow.go"],
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["allow_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [],
)

View 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/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/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
}

View 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)
}
}

View 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/kubernetes/plugin/pkg/auth/authenticator/password"

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"keystone.go",
],
tags = ["automanaged"],
deps = [
"//pkg/auth/user:go_default_library",
"//pkg/util/cert:go_default_library",
"//pkg/util/net:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/rackspace/gophercloud",
"//vendor:github.com/rackspace/gophercloud/openstack",
],
)

View 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/kubernetes/plugin/pkg/auth/authenticator/password/keystone"

View file

@ -0,0 +1,93 @@
/*
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"
"k8s.io/kubernetes/pkg/auth/user"
certutil "k8s.io/kubernetes/pkg/util/cert"
netutil "k8s.io/kubernetes/pkg/util/net"
)
// 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
}

View file

@ -0,0 +1,26 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["passwordfile.go"],
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["passwordfile_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View file

@ -0,0 +1,78 @@
/*
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"
"k8s.io/kubernetes/pkg/auth/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()
users := make(map[string]*userPasswordInfo)
reader := csv.NewReader(file)
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],
}
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
}

View file

@ -0,0 +1,121 @@
/*
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/kubernetes/pkg/auth/user"
)
func TestPasswordFile(t *testing.T) {
auth, err := newWithContents(t, `
password1,user1,uid1
password2,user2,uid2
`)
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",
},
{
Username: "user4",
Password: "password4",
},
}
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())
}

View file

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

View file

@ -0,0 +1,36 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package anonymous
import (
"net/http"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
)
const (
anonymousUser = user.Anonymous
unauthenticatedGroup = user.AllUnauthenticated
)
func NewAuthenticator() authenticator.Request {
return authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
return &user.DefaultInfo{Name: anonymousUser, Groups: []string{unauthenticatedGroup}}, true, nil
})
}

View 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 anonymous
import (
"testing"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
"k8s.io/kubernetes/pkg/util/sets"
)
func TestAnonymous(t *testing.T) {
var a authenticator.Request = NewAuthenticator()
u, ok, err := a.AuthenticateRequest(nil)
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if !ok {
t.Fatalf("Unexpectedly unauthenticated")
}
if u.GetName() != user.Anonymous {
t.Fatalf("Expected username %s, got %s", user.Anonymous, u.GetName())
}
if !sets.NewString(u.GetGroups()...).Equal(sets.NewString(user.AllUnauthenticated)) {
t.Fatalf("Expected group %s, got %v", user.AllUnauthenticated, u.GetGroups())
}
}

View file

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

View 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/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/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)
}

View 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/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/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
}
}
}

View file

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

View file

@ -0,0 +1,178 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package headerrequest
import (
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
"strings"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
utilcert "k8s.io/kubernetes/pkg/util/cert"
"k8s.io/kubernetes/pkg/util/sets"
x509request "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
)
type requestHeaderAuthRequestHandler struct {
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
nameHeaders []string
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
groupHeaders []string
// extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
// the user.Info.Extra. All values of all matching headers will be added.
extraHeaderPrefixes []string
}
func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
if err != nil {
return nil, err
}
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
if err != nil {
return nil, err
}
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
if err != nil {
return nil, err
}
return &requestHeaderAuthRequestHandler{
nameHeaders: trimmedNameHeaders,
groupHeaders: trimmedGroupHeaders,
extraHeaderPrefixes: trimmedExtraHeaderPrefixes,
}, nil
}
func trimHeaders(headerNames ...string) ([]string, error) {
ret := []string{}
for _, headerName := range headerNames {
trimmedHeader := strings.TrimSpace(headerName)
if len(trimmedHeader) == 0 {
return nil, fmt.Errorf("empty header %q", headerName)
}
ret = append(ret, trimmedHeader)
}
return ret, nil
}
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
if err != nil {
return nil, err
}
if len(clientCA) == 0 {
return nil, fmt.Errorf("missing clientCA file")
}
// Wrap with an x509 verifier
caData, err := ioutil.ReadFile(clientCA)
if err != nil {
return nil, fmt.Errorf("error reading %s: %v", clientCA, err)
}
opts := x509request.DefaultVerifyOptions()
opts.Roots = x509.NewCertPool()
certs, err := utilcert.ParseCertsPEM(caData)
if err != nil {
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
}
for _, cert := range certs {
opts.Roots.AddCert(cert)
}
return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil
}
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
name := headerValue(req.Header, a.nameHeaders)
if len(name) == 0 {
return nil, false, nil
}
groups := allHeaderValues(req.Header, a.groupHeaders)
extra := newExtra(req.Header, a.extraHeaderPrefixes)
// clear headers used for authentication
for _, headerName := range a.nameHeaders {
req.Header.Del(headerName)
}
for _, headerName := range a.groupHeaders {
req.Header.Del(headerName)
}
for k := range extra {
for _, prefix := range a.extraHeaderPrefixes {
req.Header.Del(prefix + k)
}
}
return &user.DefaultInfo{
Name: name,
Groups: groups,
Extra: extra,
}, true, nil
}
func headerValue(h http.Header, headerNames []string) string {
for _, headerName := range headerNames {
headerValue := h.Get(headerName)
if len(headerValue) > 0 {
return headerValue
}
}
return ""
}
func allHeaderValues(h http.Header, headerNames []string) []string {
ret := []string{}
for _, headerName := range headerNames {
values, ok := h[headerName]
if !ok {
continue
}
for _, headerValue := range values {
if len(headerValue) > 0 {
ret = append(ret, headerValue)
}
}
}
return ret
}
func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
ret := map[string][]string{}
// we have to iterate over prefixes first in order to have proper ordering inside the value slices
for _, prefix := range headerPrefixes {
for headerName, vv := range h {
if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
continue
}
extraKey := strings.ToLower(headerName[len(prefix):])
ret[extraKey] = append(ret[extraKey], vv...)
}
}
return ret
}

View file

@ -0,0 +1,159 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package headerrequest
import (
"net/http"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/auth/user"
)
func TestRequestHeader(t *testing.T) {
testcases := map[string]struct {
nameHeaders []string
groupHeaders []string
extraPrefixHeaders []string
requestHeaders http.Header
expectedUser user.Info
expectedOk bool
}{
"empty": {},
"user no match": {
nameHeaders: []string{"X-Remote-User"},
},
"user match": {
nameHeaders: []string{"X-Remote-User"},
requestHeaders: http.Header{"X-Remote-User": {"Bob"}},
expectedUser: &user.DefaultInfo{
Name: "Bob",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"user exact match": {
nameHeaders: []string{"X-Remote-User"},
requestHeaders: http.Header{
"Prefixed-X-Remote-User-With-Suffix": {"Bob"},
"X-Remote-User-With-Suffix": {"Bob"},
},
},
"user first match": {
nameHeaders: []string{
"X-Remote-User",
"A-Second-X-Remote-User",
"Another-X-Remote-User",
},
requestHeaders: http.Header{
"X-Remote-User": {"", "First header, second value"},
"A-Second-X-Remote-User": {"Second header, first value", "Second header, second value"},
"Another-X-Remote-User": {"Third header, first value"}},
expectedUser: &user.DefaultInfo{
Name: "Second header, first value",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"user case-insensitive": {
nameHeaders: []string{"x-REMOTE-user"}, // configured headers can be case-insensitive
requestHeaders: http.Header{"X-Remote-User": {"Bob"}}, // the parsed headers are normalized by the http package
expectedUser: &user.DefaultInfo{
Name: "Bob",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"groups none": {
nameHeaders: []string{"X-Remote-User"},
groupHeaders: []string{"X-Remote-Group"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
Groups: []string{},
Extra: map[string][]string{},
},
expectedOk: true,
},
"groups all matches": {
nameHeaders: []string{"X-Remote-User"},
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
Extra: map[string][]string{},
},
expectedOk: true,
},
"extra prefix matches case-insensitive": {
nameHeaders: []string{"X-Remote-User"},
groupHeaders: []string{"X-Remote-Group-1", "X-Remote-Group-2"},
extraPrefixHeaders: []string{"X-Remote-Extra-1-", "X-Remote-Extra-2-"},
requestHeaders: http.Header{
"X-Remote-User": {"Bob"},
"X-Remote-Group-1": {"one-a", "one-b"},
"X-Remote-Group-2": {"two-a", "two-b"},
"X-Remote-extra-1-key1": {"alfa", "bravo"},
"X-Remote-Extra-1-Key2": {"charlie", "delta"},
"X-Remote-Extra-1-": {"india", "juliet"},
"X-Remote-extra-2-": {"kilo", "lima"},
"X-Remote-extra-2-Key1": {"echo", "foxtrot"},
"X-Remote-Extra-2-key2": {"golf", "hotel"},
},
expectedUser: &user.DefaultInfo{
Name: "Bob",
Groups: []string{"one-a", "one-b", "two-a", "two-b"},
Extra: map[string][]string{
"key1": {"alfa", "bravo", "echo", "foxtrot"},
"key2": {"charlie", "delta", "golf", "hotel"},
"": {"india", "juliet", "kilo", "lima"},
},
},
expectedOk: true,
},
}
for k, testcase := range testcases {
auth, err := New(testcase.nameHeaders, testcase.groupHeaders, testcase.extraPrefixHeaders)
if err != nil {
t.Fatal(err)
}
req := &http.Request{Header: testcase.requestHeaders}
user, ok, _ := auth.AuthenticateRequest(req)
if testcase.expectedOk != ok {
t.Errorf("%v: expected %v, got %v", k, testcase.expectedOk, ok)
}
if e, a := testcase.expectedUser, user; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected %#v, got %#v", k, e, a)
}
}
}

View file

@ -0,0 +1,30 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["union.go"],
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/errors:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["unionauth_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View file

@ -0,0 +1,72 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package union
import (
"net/http"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
)
// unionAuthRequestHandler authenticates requests using a chain of authenticator.Requests
type unionAuthRequestHandler struct {
// Handlers is a chain of request authenticators to delegate to
Handlers []authenticator.Request
// FailOnError determines whether an error returns short-circuits the chain
FailOnError bool
}
// New returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
// The entire chain is tried until one succeeds. If all fail, an aggregate error is returned.
func New(authRequestHandlers ...authenticator.Request) authenticator.Request {
if len(authRequestHandlers) == 1 {
return authRequestHandlers[0]
}
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: false}
}
// NewFailOnError returns a request authenticator that validates credentials using a chain of authenticator.Request objects.
// The first error short-circuits the chain.
func NewFailOnError(authRequestHandlers ...authenticator.Request) authenticator.Request {
if len(authRequestHandlers) == 1 {
return authRequestHandlers[0]
}
return &unionAuthRequestHandler{Handlers: authRequestHandlers, FailOnError: true}
}
// AuthenticateRequest authenticates the request using a chain of authenticator.Request objects.
func (authHandler *unionAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
var errlist []error
for _, currAuthRequestHandler := range authHandler.Handlers {
info, ok, err := currAuthRequestHandler.AuthenticateRequest(req)
if err != nil {
if authHandler.FailOnError {
return info, ok, err
}
errlist = append(errlist, err)
continue
}
if ok {
return info, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
}

View file

@ -0,0 +1,166 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package union
import (
"errors"
"net/http"
"reflect"
"strings"
"testing"
"k8s.io/kubernetes/pkg/auth/user"
)
type mockAuthRequestHandler struct {
returnUser user.Info
isAuthenticated bool
err error
}
var (
user1 = &user.DefaultInfo{Name: "fresh_ferret", UID: "alfa"}
user2 = &user.DefaultInfo{Name: "elegant_sheep", UID: "bravo"}
)
func (mock *mockAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
return mock.returnUser, mock.isAuthenticated, mock.err
}
func TestAuthenticateRequestSecondPasses(t *testing.T) {
handler1 := &mockAuthRequestHandler{returnUser: user1}
handler2 := &mockAuthRequestHandler{returnUser: user2, isAuthenticated: true}
authRequestHandler := New(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !isAuthenticated {
t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated)
}
if !reflect.DeepEqual(user2, authenticatedUser) {
t.Errorf("Expected %v, got %v", user2, authenticatedUser)
}
}
func TestAuthenticateRequestFirstPasses(t *testing.T) {
handler1 := &mockAuthRequestHandler{returnUser: user1, isAuthenticated: true}
handler2 := &mockAuthRequestHandler{returnUser: user2}
authRequestHandler := New(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !isAuthenticated {
t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated)
}
if !reflect.DeepEqual(user1, authenticatedUser) {
t.Errorf("Expected %v, got %v", user1, authenticatedUser)
}
}
func TestAuthenticateRequestSuppressUnnecessaryErrors(t *testing.T) {
handler1 := &mockAuthRequestHandler{err: errors.New("first")}
handler2 := &mockAuthRequestHandler{isAuthenticated: true}
authRequestHandler := New(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
_, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if !isAuthenticated {
t.Errorf("Unexpectedly unauthenticated: %v", isAuthenticated)
}
}
func TestAuthenticateRequestNoAuthenticators(t *testing.T) {
authRequestHandler := New()
req, _ := http.NewRequest("GET", "http://example.org", nil)
authenticatedUser, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if isAuthenticated {
t.Errorf("Unexpectedly authenticated: %v", isAuthenticated)
}
if authenticatedUser != nil {
t.Errorf("Unexpected authenticatedUser: %v", authenticatedUser)
}
}
func TestAuthenticateRequestNonePass(t *testing.T) {
handler1 := &mockAuthRequestHandler{}
handler2 := &mockAuthRequestHandler{}
authRequestHandler := New(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
_, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if isAuthenticated {
t.Errorf("Unexpectedly authenticated: %v", isAuthenticated)
}
}
func TestAuthenticateRequestAdditiveErrors(t *testing.T) {
handler1 := &mockAuthRequestHandler{err: errors.New("first")}
handler2 := &mockAuthRequestHandler{err: errors.New("second")}
authRequestHandler := New(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
_, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err == nil {
t.Errorf("Expected an error")
}
if !strings.Contains(err.Error(), "first") {
t.Errorf("Expected error containing %v, got %v", "first", err)
}
if !strings.Contains(err.Error(), "second") {
t.Errorf("Expected error containing %v, got %v", "second", err)
}
if isAuthenticated {
t.Errorf("Unexpectedly authenticated: %v", isAuthenticated)
}
}
func TestAuthenticateRequestFailEarly(t *testing.T) {
handler1 := &mockAuthRequestHandler{err: errors.New("first")}
handler2 := &mockAuthRequestHandler{err: errors.New("second")}
authRequestHandler := NewFailOnError(handler1, handler2)
req, _ := http.NewRequest("GET", "http://example.org", nil)
_, isAuthenticated, err := authRequestHandler.AuthenticateRequest(req)
if err == nil {
t.Errorf("Expected an error")
}
if !strings.Contains(err.Error(), "first") {
t.Errorf("Expected error containing %v, got %v", "first", err)
}
if strings.Contains(err.Error(), "second") {
t.Errorf("Did not expect second error, got %v", err)
}
if isAuthenticated {
t.Errorf("Unexpectedly authenticated: %v", isAuthenticated)
}
}

View file

@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"x509.go",
],
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/errors:go_default_library",
"//pkg/util/sets:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = ["x509_test.go"],
data = glob(["testdata/*"]),
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/sets:go_default_library",
],
)

View file

@ -0,0 +1,19 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package x509 provides a request authenticator that validates and
// extracts user information from client certificates
package x509 // import "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBpTCCAUugAwIBAgIUPV4LAC5KK8YWY1FegyTuhkGUr3EwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMB4XDTkwMTIzMTIzNTkwMFoXDTkw
MTIzMTIzNTkwMFowFDESMBAGA1UEAxMJTXkgQ2xpZW50MFkwEwYHKoZIzj0CAQYI
KoZIzj0DAQcDQgAEyYUnseNUN87rfHgekrfZu5sj4wlt5LYr3JYZZkfSbsb+BW3/
RzX02ifjp+8w7mI4qUGg6y6J7oXHGFT3uj9kj6N1MHMwDgYDVR0PAQH/BAQDAgWg
MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKsX
EnXwDg8j2LIEM1QzmFrE6537MB8GA1UdIwQYMBaAFF+p0JcY31pz+mjNZnjv0Gum
92vZMAoGCCqGSM49BAMCA0gAMEUCIG4FBcb57oqOCoaFiJ+Yx6S0zkaash7bTv3V
CIy9JvFdAiEAy8bf2S9EkvZyURZ6ycgEMnekll57Ebze6rjlPx8+B1Y=
-----END CERTIFICATE-----

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw
GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx
MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB
BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb
KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU
K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q
a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5
MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps=
-----END CERTIFICATE-----

View file

@ -0,0 +1,24 @@
{
"signing": {
"profiles": {
"valid": {
"expiry": "876000h",
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"expired": {
"expiry": "1h",
"not_before": "1990-12-31T23:59:00Z",
"not_after": "1990-12-31T23:59:00Z",
"usages": [
"signing",
"key encipherment",
"client auth"
]
}
}
}
}

View file

@ -0,0 +1,3 @@
{
"CN": "My Client"
}

View file

@ -0,0 +1,24 @@
#!/usr/bin/env 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.
cfssl gencert -initca root.csr.json | cfssljson -bare root
cfssl gencert -initca intermediate.csr.json | cfssljson -bare intermediate
cfssl sign -ca root.pem -ca-key root-key.pem -config intermediate.config.json intermediate.csr | cfssljson -bare intermediate
cfssl gencert -ca intermediate.pem -ca-key intermediate-key.pem -config client.config.json --profile=valid client.csr.json | cfssljson -bare client-valid
cfssl gencert -ca intermediate.pem -ca-key intermediate-key.pem -config client.config.json --profile=expired client.csr.json | cfssljson -bare client-expired

View file

@ -0,0 +1,18 @@
{
"signing": {
"default": {
"usages": [
"digital signature",
"cert sign",
"crl sign",
"signing",
"key encipherment",
"client auth"
],
"expiry": "876000h",
"ca_constraint": {
"is_ca": true
}
}
}
}

View file

@ -0,0 +1,6 @@
{
"CN": "Intermediate-CA",
"ca": {
"expiry": "876000h"
}
}

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw
EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1
MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI
KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD
kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm
MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b
dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz
3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c=
-----END CERTIFICATE-----

View file

@ -0,0 +1,6 @@
{
"CN": "Root-CA",
"ca": {
"expiry": "876000h"
}
}

View file

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBizCCATGgAwIBAgIUH4plk9qwD61FVXgiOTngFU5FeSkwCgYIKoZIzj0EAwIw
EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1
MDYwMFowEjEQMA4GA1UEAxMHUm9vdC1DQTBZMBMGByqGSM49AgEGCCqGSM49AwEH
A0IABI2CsrAnMGT8P2VGU2MLo5pv86Z74kcV9hgkLJUkSaeNyc1s89w7X5V2wvwu
iWEJRGm5RoZJausmyZLZEoKEVXejYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQez+vouP6XzY6mYCb/23YAOxXuLTAfBgNVHSME
GDAWgBQez+vouP6XzY6mYCb/23YAOxXuLTAKBggqhkjOPQQDAgNIADBFAiBGclts
vJRM+QMVoV/1L9b+hvhgLIp/OupUFsSOReefIwIhALY06hBklyh8eFwuBtyX2VcE
8xlVn4/5idUvc3Xv2h9s
-----END CERTIFICATE-----

View file

@ -0,0 +1,185 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package x509
import (
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"net/http"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/sets"
)
// UserConversion defines an interface for extracting user info from a client certificate chain
type UserConversion interface {
User(chain []*x509.Certificate) (user.Info, bool, error)
}
// UserConversionFunc is a function that implements the UserConversion interface.
type UserConversionFunc func(chain []*x509.Certificate) (user.Info, bool, error)
// User implements x509.UserConversion
func (f UserConversionFunc) User(chain []*x509.Certificate) (user.Info, bool, error) {
return f(chain)
}
// Authenticator implements request.Authenticator by extracting user info from verified client certificates
type Authenticator struct {
opts x509.VerifyOptions
user UserConversion
}
// New returns a request.Authenticator that verifies client certificates using the provided
// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
return &Authenticator{opts, user}
}
// AuthenticateRequest authenticates the request using presented client certificates
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// Use intermediates, if provided
optsCopy := a.opts
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
optsCopy.Intermediates.AddCert(intermediate)
}
}
chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
if err != nil {
return nil, false, err
}
var errlist []error
for _, chain := range chains {
user, ok, err := a.user.User(chain)
if err != nil {
errlist = append(errlist, err)
continue
}
if ok {
return user, ok, err
}
}
return nil, false, utilerrors.NewAggregate(errlist)
}
// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
type Verifier struct {
opts x509.VerifyOptions
auth authenticator.Request
// allowedCommonNames contains the common names which a verified certificate is allowed to have.
// If empty, all verified certificates are allowed.
allowedCommonNames sets.String
}
// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
return &Verifier{opts, auth, allowedCommonNames}
}
// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
return nil, false, nil
}
// Use intermediates, if provided
optsCopy := a.opts
if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
optsCopy.Intermediates = x509.NewCertPool()
for _, intermediate := range req.TLS.PeerCertificates[1:] {
optsCopy.Intermediates.AddCert(intermediate)
}
}
if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
return nil, false, err
}
if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
return nil, false, err
}
return a.auth.AuthenticateRequest(req)
}
func (a *Verifier) verifySubject(subject pkix.Name) error {
// No CN restrictions
if len(a.allowedCommonNames) == 0 {
return nil
}
// Enforce CN restrictions
if a.allowedCommonNames.Has(subject.CommonName) {
return nil
}
glog.Warningf("x509: subject with cn=%s is not in the allowed list: %v", subject.CommonName, a.allowedCommonNames.List())
return fmt.Errorf("x509: subject with cn=%s is not allowed", subject.CommonName)
}
// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
func DefaultVerifyOptions() x509.VerifyOptions {
return x509.VerifyOptions{
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}
}
// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
if len(chain[0].Subject.CommonName) == 0 {
return nil, false, nil
}
return &user.DefaultInfo{
Name: chain[0].Subject.CommonName,
Groups: chain[0].Subject.Organization,
}, true, nil
})
// DNSNameUserConversion builds user info from a certificate chain using the first DNSName on the certificate
var DNSNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
if len(chain[0].DNSNames) == 0 {
return nil, false, nil
}
return &user.DefaultInfo{Name: chain[0].DNSNames[0]}, true, nil
})
// EmailAddressUserConversion builds user info from a certificate chain using the first EmailAddress on the certificate
var EmailAddressUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
var emailAddressOID asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 9, 1}
if len(chain[0].EmailAddresses) == 0 {
for _, name := range chain[0].Subject.Names {
if name.Type.Equal(emailAddressOID) {
return &user.DefaultInfo{Name: name.Value.(string)}, true, nil
}
}
return nil, false, nil
}
return &user.DefaultInfo{Name: chain[0].EmailAddresses[0]}, true, nil
})

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["anytoken.go"],
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["anytoken_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View 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/kubernetes/pkg/auth/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
}

View 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/kubernetes/pkg/auth/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)
}
}
}

View file

@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["oidc.go"],
tags = ["automanaged"],
deps = [
"//pkg/auth/user:go_default_library",
"//pkg/util/cert:go_default_library",
"//pkg/util/net:go_default_library",
"//pkg/util/runtime:go_default_library",
"//vendor:github.com/coreos/go-oidc/jose",
"//vendor:github.com/coreos/go-oidc/oidc",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = ["oidc_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/auth/user:go_default_library",
"//plugin/pkg/auth/authenticator/token/oidc/testing:go_default_library",
"//vendor:github.com/coreos/go-oidc/jose",
"//vendor:github.com/coreos/go-oidc/oidc",
],
)

View file

@ -0,0 +1,2 @@
assignees:
- ericchiang

View file

@ -0,0 +1,266 @@
/*
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/kubernetes/pkg/auth/user"
certutil "k8s.io/kubernetes/pkg/util/cert"
"k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/runtime"
)
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":
// TODO(yifan): Check 'email_verified' to make sure the email is valid.
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
}

View file

@ -0,0 +1,322 @@
/*
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/kubernetes/pkg/auth/user"
oidctesting "k8s.io/kubernetes/plugin/pkg/auth/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) string {
signer := op.PrivKey.Signer()
claims := oidc.NewClaims(iss, sub, aud, iat, exp)
claims.Add(usernameClaim, value)
if groups != nil && groupsClaim != "" {
claims.Add(groupsClaim, groups)
}
jwt, err := jose.NewSignedJWT(claims, signer)
if err != nil {
t.Fatalf("Cannot generate token: %v", err)
return ""
}
return jwt.Encode()
}
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))
}
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)) + "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))
}
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",
},
{
"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()
}
}
}

View file

@ -0,0 +1,22 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["provider.go"],
tags = ["automanaged"],
deps = [
"//vendor:github.com/coreos/go-oidc/jose",
"//vendor:github.com/coreos/go-oidc/key",
"//vendor:github.com/coreos/go-oidc/oidc",
],
)

View 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)
}
}

View file

@ -0,0 +1,26 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["tokenfile.go"],
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["tokenfile_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View file

@ -0,0 +1,85 @@
/*
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 tokenfile
import (
"encoding/csv"
"fmt"
"io"
"os"
"strings"
"k8s.io/kubernetes/pkg/auth/user"
)
type TokenAuthenticator struct {
tokens map[string]*user.DefaultInfo
}
// New returns a TokenAuthenticator for a single token
func New(tokens map[string]*user.DefaultInfo) *TokenAuthenticator {
return &TokenAuthenticator{
tokens: tokens,
}
}
// NewCSV returns a TokenAuthenticator, populated from a CSV file.
// The CSV file must contain records in the format "token,username,useruid"
func NewCSV(path string) (*TokenAuthenticator, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
tokens := make(map[string]*user.DefaultInfo)
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("token file '%s' must have at least 3 columns (token, user name, user uid), found %d", path, len(record))
}
obj := &user.DefaultInfo{
Name: record[1],
UID: record[2],
}
tokens[record[0]] = obj
if len(record) >= 4 {
obj.Groups = strings.Split(record[3], ",")
}
}
return &TokenAuthenticator{
tokens: tokens,
}, nil
}
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
}

View file

@ -0,0 +1,141 @@
/*
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 tokenfile
import (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/kubernetes/pkg/auth/user"
)
func TestTokenFile(t *testing.T) {
auth, err := newWithContents(t, `
token1,user1,uid1
token2,user2,uid2
token3,user3,uid3,"group1,group2"
token4,user4,uid4,"group2"
token5,user5,uid5,group5
token6,user6,uid6,group5,otherdata
token7,user7,uid7,"group1,group2",otherdata
`)
if err != nil {
t.Fatalf("unable to read tokenfile: %v", err)
}
testCases := []struct {
Token string
User *user.DefaultInfo
Ok bool
Err bool
}{
{
Token: "token1",
User: &user.DefaultInfo{Name: "user1", UID: "uid1"},
Ok: true,
},
{
Token: "token2",
User: &user.DefaultInfo{Name: "user2", UID: "uid2"},
Ok: true,
},
{
Token: "token3",
User: &user.DefaultInfo{Name: "user3", UID: "uid3", Groups: []string{"group1", "group2"}},
Ok: true,
},
{
Token: "token4",
User: &user.DefaultInfo{Name: "user4", UID: "uid4", Groups: []string{"group2"}},
Ok: true,
},
{
Token: "token5",
User: &user.DefaultInfo{Name: "user5", UID: "uid5", Groups: []string{"group5"}},
Ok: true,
},
{
Token: "token6",
User: &user.DefaultInfo{Name: "user6", UID: "uid6", Groups: []string{"group5"}},
Ok: true,
},
{
Token: "token7",
User: &user.DefaultInfo{Name: "user7", UID: "uid7", Groups: []string{"group1", "group2"}},
Ok: true,
},
{
Token: "token8",
},
}
for i, testCase := range testCases {
user, ok, err := auth.AuthenticateToken(testCase.Token)
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)
}
switch {
case err == nil && testCase.Err:
t.Errorf("%d: unexpected nil error", i)
case err != nil && !testCase.Err:
t.Errorf("%d: unexpected error: %v", i, err)
}
}
}
func TestBadTokenFile(t *testing.T) {
_, err := newWithContents(t, `
token1,user1,uid1
token2,user2,uid2
token3,user3
token4
`)
if err == nil {
t.Fatalf("unexpected non error")
}
}
func TestInsufficientColumnsTokenFile(t *testing.T) {
_, err := newWithContents(t, "token4\n")
if err == nil {
t.Fatalf("unexpected non error")
}
}
func newWithContents(t *testing.T, contents string) (auth *TokenAuthenticator, err error) {
f, err := ioutil.TempFile("", "tokenfile_test")
if err != nil {
t.Fatalf("unexpected error creating tokenfile: %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 tokenfile: %v", err)
}
return NewCSV(f.Name())
}

View file

@ -0,0 +1,18 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["tokentest.go"],
tags = ["automanaged"],
deps = ["//pkg/auth/user:go_default_library"],
)

View 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/kubernetes/pkg/auth/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
}

View file

@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["webhook.go"],
tags = ["automanaged"],
deps = [
"//pkg/apis/authentication/install:go_default_library",
"//pkg/apis/authentication/v1beta1:go_default_library",
"//pkg/auth/authenticator:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/util/cache:go_default_library",
"//plugin/pkg/webhook:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"certs_test.go",
"webhook_test.go",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/apis/authentication/v1beta1:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/client/unversioned/clientcmd/api/v1:go_default_library",
],
)

View 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-----`)

View file

@ -0,0 +1,129 @@
/*
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/kubernetes/pkg/apis/authentication/install"
authentication "k8s.io/kubernetes/pkg/apis/authentication/v1beta1"
"k8s.io/kubernetes/pkg/auth/authenticator"
"k8s.io/kubernetes/pkg/auth/user"
authenticationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authentication/v1beta1"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/cache"
"k8s.io/kubernetes/plugin/pkg/webhook"
)
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(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
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,18 @@
/*
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 authorizer contains implementations for pkg/auth/authorizer interfaces
package authorizer // import "k8s.io/kubernetes/plugin/pkg/auth/authorizer"

View file

@ -0,0 +1,44 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"rbac.go",
"subject_locator.go",
],
tags = ["automanaged"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/auth/authorizer:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/util/errors:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"rbac_test.go",
"subject_locator_test.go",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/auth/authorizer:go_default_library",
"//pkg/auth/user:go_default_library",
],
)

View file

@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"controller_policy.go",
"policy.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/rbac:go_default_library",
"//pkg/auth/user:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_xtest",
srcs = ["policy_test.go"],
tags = ["automanaged"],
deps = [
"//pkg/apis/rbac:go_default_library",
"//pkg/apis/rbac/validation:go_default_library",
"//pkg/util/sets:go_default_library",
"//plugin/pkg/auth/authorizer/rbac/bootstrappolicy:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["controller_policy_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = ["//pkg/util/sets:go_default_library"],
)

View file

@ -0,0 +1,222 @@
/*
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 bootstrappolicy
import (
"strings"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
)
const saRolePrefix = "system:controller:"
var (
// controllerRoles is a slice of roles used for controllers
controllerRoles = []rbac.ClusterRole{}
// controllerRoleBindings is a slice of roles used for controllers
controllerRoleBindings = []rbac.ClusterRoleBinding{}
)
func addControllerRole(role rbac.ClusterRole) {
if !strings.HasPrefix(role.Name, saRolePrefix) {
glog.Fatalf(`role %q must start with %q`, role.Name, saRolePrefix)
}
for _, existingRole := range controllerRoles {
if role.Name == existingRole.Name {
glog.Fatalf("role %q was already registered", role.Name)
}
}
controllerRoles = append(controllerRoles, role)
controllerRoleBindings = append(controllerRoleBindings,
rbac.NewClusterBinding(role.Name).SAs("kube-system", role.Name[len(saRolePrefix):]).BindingOrDie())
}
func eventsRule() rbac.PolicyRule {
return rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie()
}
func init() {
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "attachdetach-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("persistentvolumes", "persistentvolumeclaims").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("patch", "update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "cronjob-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(batchGroup).Resources("cronjobs").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "create", "delete").Groups(batchGroup).Resources("jobs").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup).Resources("cronjobs/status").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "daemon-set-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(extensionsGroup).Resources("daemonsets").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup).Resources("daemonsets/status").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("pods/binding").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "deployment-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(extensionsGroup).Resources("deployments").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup).Resources("deployments/status").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "create", "update", "delete").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(),
// TODO: remove "update" once
// https://github.com/kubernetes/kubernetes/issues/36897 is resolved.
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "disruption-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list").Groups(extensionsGroup).Resources("deployments").RuleOrDie(),
rbac.NewRule("get", "list").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(),
rbac.NewRule("get", "list").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(policyGroup).Resources("poddisruptionbudgets").RuleOrDie(),
rbac.NewRule("update").Groups(policyGroup).Resources("poddisruptionbudgets/status").RuleOrDie(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "endpoint-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("services", "pods").RuleOrDie(),
rbac.NewRule("get", "list", "create", "update", "delete").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
rbac.NewRule("create").Groups(legacyGroup).Resources("endpoints/restricted").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "horizontal-pod-autoscaler"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(autoscalingGroup, extensionsGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule("update").Groups(autoscalingGroup, extensionsGroup).Resources("horizontalpodautoscalers/status").RuleOrDie(),
rbac.NewRule("get", "update").Groups(legacyGroup).Resources("replicationcontrollers/scale").RuleOrDie(),
rbac.NewRule("get", "update").Groups(extensionsGroup).Resources("deployments/scale", "replicasets/scale").RuleOrDie(),
rbac.NewRule("list").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: fix MetricsClient to no longer require root proxy access
// TODO: restrict this to the appropriate namespace
rbac.NewRule("proxy").Groups(legacyGroup).Resources("services").Names("https:heapster:").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "job-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(batchGroup, extensionsGroup).Resources("jobs").RuleOrDie(),
rbac.NewRule("update").Groups(batchGroup, extensionsGroup).Resources("jobs/status").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "namespace-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "delete").Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("namespaces/finalize", "namespaces/status").RuleOrDie(),
rbac.NewRule("get", "list", "delete", "deletecollection").Groups("*").Resources("*").RuleOrDie(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "persistent-volume-binder"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update", "create", "delete").Groups(legacyGroup).Resources("persistentvolumes").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumes/status").RuleOrDie(),
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("persistentvolumeclaims/status").RuleOrDie(),
rbac.NewRule("list", "watch", "get", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// glusterfs
rbac.NewRule("get", "list", "watch").Groups(storageGroup).Resources("storageclasses").RuleOrDie(),
rbac.NewRule("get", "create", "delete").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "pod-garbage-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "replicaset-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch", "update").Groups(extensionsGroup).Resources("replicasets").RuleOrDie(),
rbac.NewRule("update").Groups(extensionsGroup).Resources("replicasets/status").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "replication-controller"},
Rules: []rbac.PolicyRule{
// 1.0 controllers needed get, update, so without these old controllers break on new servers
rbac.NewRule("get", "list", "watch", "update").Groups(legacyGroup).Resources("replicationcontrollers").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("replicationcontrollers/status").RuleOrDie(),
rbac.NewRule("list", "watch", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "service-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("services").RuleOrDie(),
rbac.NewRule("update").Groups(legacyGroup).Resources("services/status").RuleOrDie(),
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
eventsRule(),
},
})
addControllerRole(rbac.ClusterRole{
ObjectMeta: api.ObjectMeta{Name: saRolePrefix + "statefulset-controller"},
Rules: []rbac.PolicyRule{
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("get", "list", "watch").Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule("update").Groups(appsGroup).Resources("statefulsets/status").RuleOrDie(),
rbac.NewRule("get", "create", "delete", "update").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbac.NewRule("get", "create").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
eventsRule(),
},
})
}
// ControllerRoles returns the cluster roles used by controllers
func ControllerRoles() []rbac.ClusterRole {
return controllerRoles
}
// ControllerRoleBindings returns the role bindings used by controllers
func ControllerRoleBindings() []rbac.ClusterRoleBinding {
return controllerRoleBindings
}

View file

@ -0,0 +1,60 @@
/*
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 bootstrappolicy
import (
"testing"
"k8s.io/kubernetes/pkg/util/sets"
)
// rolesWithAllowStar are the controller roles which are allowed to contain a *. These are
// namespace lifecycle and GC which have to delete anything. If you're adding to this list
// tag sig-auth
var rolesWithAllowStar = sets.NewString(
saRolePrefix+"namespace-controller",
saRolePrefix+"generic-garbage-collector",
)
// TestNoStarsForControllers confirms that no controller role has star verbs, groups,
// or resources. There are two known exceptions, namespace lifecycle and GC which have to
// delete anything
func TestNoStarsForControllers(t *testing.T) {
for _, role := range ControllerRoles() {
if rolesWithAllowStar.Has(role.Name) {
continue
}
for i, rule := range role.Rules {
for j, verb := range rule.Verbs {
if verb == "*" {
t.Errorf("%s.Rule[%d].Verbs[%d] is star", role.Name, i, j)
}
}
for j, group := range rule.APIGroups {
if group == "*" {
t.Errorf("%s.Rule[%d].APIGroups[%d] is star", role.Name, i, j)
}
}
for j, resource := range rule.Resources {
if resource == "*" {
t.Errorf("%s.Rule[%d].Resources[%d] is star", role.Name, i, j)
}
}
}
}
}

View file

@ -0,0 +1,218 @@
/*
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 bootstrappolicy
import (
"k8s.io/kubernetes/pkg/api"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/auth/user"
)
var (
ReadWrite = []string{"get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"}
Read = []string{"get", "list", "watch"}
)
const (
legacyGroup = ""
appsGroup = "apps"
authenticationGroup = "authentication.k8s.io"
authorizationGroup = "authorization.k8s.io"
autoscalingGroup = "autoscaling"
batchGroup = "batch"
certificatesGroup = "certificates.k8s.io"
extensionsGroup = "extensions"
policyGroup = "policy"
rbacGroup = "rbac.authorization.k8s.io"
storageGroup = "storage.k8s.io"
)
// ClusterRoles returns the cluster roles to bootstrap an API server with
func ClusterRoles() []rbac.ClusterRole {
return []rbac.ClusterRole{
{
// a "root" role which can do absolutely anything
ObjectMeta: api.ObjectMeta{Name: "cluster-admin"},
Rules: []rbac.PolicyRule{
rbac.NewRule("*").Groups("*").Resources("*").RuleOrDie(),
rbac.NewRule("*").URLs("*").RuleOrDie(),
},
},
{
// a role which provides just enough power to discovery API versions for negotiation
ObjectMeta: api.ObjectMeta{Name: "system:discovery"},
Rules: []rbac.PolicyRule{
rbac.NewRule("get").URLs("/version", "/api", "/api/*", "/apis", "/apis/*").RuleOrDie(),
},
},
{
// a role which provides minimal resource access to allow a "normal" user to learn information about themselves
ObjectMeta: api.ObjectMeta{Name: "system:basic-user"},
Rules: []rbac.PolicyRule{
// TODO add future selfsubjectrulesreview, project request APIs, project listing APIs
rbac.NewRule("create").Groups(authorizationGroup).Resources("selfsubjectaccessreviews").RuleOrDie(),
},
},
{
// a role for a namespace level admin. It is `edit` plus the power to grant permissions to other users.
ObjectMeta: api.ObjectMeta{Name: "admin"},
Rules: []rbac.PolicyRule{
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "services/proxy", "endpoints", "persistentvolumeclaims", "configmaps", "secrets").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(batchGroup).Resources("jobs", "cronjobs", "scheduledjobs").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("jobs", "daemonsets", "horizontalpodautoscalers",
"replicationcontrollers/scale", "replicasets", "replicasets/scale", "deployments", "deployments/scale").RuleOrDie(),
// additional admin powers
rbac.NewRule("create").Groups(authorizationGroup).Resources("localsubjectaccessreviews").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(rbacGroup).Resources("roles", "rolebindings").RuleOrDie(),
},
},
{
// a role for a namespace level editor. It grants access to all user level actions in a namespace.
// It does not grant powers for "privileged" resources which are domain of the system: `/status`
// subresources or `quota`/`limits` which are used to control namespaces
ObjectMeta: api.ObjectMeta{Name: "edit"},
Rules: []rbac.PolicyRule{
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("pods", "pods/attach", "pods/proxy", "pods/exec", "pods/portforward").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(legacyGroup).Resources("replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "services/proxy", "endpoints", "persistentvolumeclaims", "configmaps", "secrets").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule("impersonate").Groups(legacyGroup).Resources("serviceaccounts").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(batchGroup).Resources("jobs", "cronjobs", "scheduledjobs").RuleOrDie(),
rbac.NewRule(ReadWrite...).Groups(extensionsGroup).Resources("jobs", "daemonsets", "horizontalpodautoscalers",
"replicationcontrollers/scale", "replicasets", "replicasets/scale", "deployments", "deployments/scale").RuleOrDie(),
},
},
{
// a role for namespace level viewing. It grants Read-only access to non-escalating resources in
// a namespace.
ObjectMeta: api.ObjectMeta{Name: "view"},
Rules: []rbac.PolicyRule{
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods", "replicationcontrollers", "replicationcontrollers/scale", "serviceaccounts",
"services", "endpoints", "persistentvolumeclaims", "configmaps").RuleOrDie(),
rbac.NewRule(Read...).Groups(legacyGroup).Resources("limitranges", "resourcequotas", "bindings", "events",
"pods/status", "resourcequotas/status", "namespaces/status", "replicationcontrollers/status", "pods/log").RuleOrDie(),
// read access to namespaces at the namespace scope means you can read *this* namespace. This can be used as an
// indicator of which namespaces you have access to.
rbac.NewRule(Read...).Groups(legacyGroup).Resources("namespaces").RuleOrDie(),
rbac.NewRule(Read...).Groups(appsGroup).Resources("statefulsets").RuleOrDie(),
rbac.NewRule(Read...).Groups(autoscalingGroup).Resources("horizontalpodautoscalers").RuleOrDie(),
rbac.NewRule(Read...).Groups(batchGroup).Resources("jobs", "cronjobs", "scheduledjobs").RuleOrDie(),
rbac.NewRule(Read...).Groups(extensionsGroup).Resources("jobs", "daemonsets", "horizontalpodautoscalers",
"replicationcontrollers/scale", "replicasets", "replicasets/scale", "deployments", "deployments/scale").RuleOrDie(),
},
},
{
// a role for nodes to use to have the access they need for running pods
ObjectMeta: api.ObjectMeta{Name: "system:node"},
Rules: []rbac.PolicyRule{
// Needed to check API access. These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews", "localsubjectaccessreviews").RuleOrDie(),
// Needed to build serviceLister, to populate env vars for services
rbac.NewRule(Read...).Groups(legacyGroup).Resources("services").RuleOrDie(),
// Nodes can register themselves
// TODO: restrict to creating a node with the same name they announce
rbac.NewRule("create", "get", "list", "watch").Groups(legacyGroup).Resources("nodes").RuleOrDie(),
// TODO: restrict to the bound node once supported
rbac.NewRule("update").Groups(legacyGroup).Resources("nodes/status").RuleOrDie(),
// TODO: restrict to the bound node as creator once supported
rbac.NewRule("create", "update", "patch").Groups(legacyGroup).Resources("events").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once supported
rbac.NewRule(Read...).Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: remove once mirror pods are removed
// TODO: restrict deletion to mirror pods created by the bound node once supported
// Needed for the node to create/delete mirror pods
rbac.NewRule("get", "create", "delete").Groups(legacyGroup).Resources("pods").RuleOrDie(),
// TODO: restrict to pods scheduled on the bound node once supported
rbac.NewRule("update").Groups(legacyGroup).Resources("pods/status").RuleOrDie(),
// TODO: restrict to secrets and configmaps used by pods scheduled on bound node once supported
// Needed for imagepullsecrets, rbd/ceph and secret volumes, and secrets in envs
// Needed for configmap volume and envs
rbac.NewRule("get").Groups(legacyGroup).Resources("secrets", "configmaps").RuleOrDie(),
// TODO: restrict to claims/volumes used by pods scheduled on bound node once supported
// Needed for persistent volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("persistentvolumeclaims", "persistentvolumes").RuleOrDie(),
// TODO: restrict to namespaces of pods scheduled on bound node once supported
// TODO: change glusterfs to use DNS lookup so this isn't needed?
// Needed for glusterfs volumes
rbac.NewRule("get").Groups(legacyGroup).Resources("endpoints").RuleOrDie(),
},
},
{
// a role to use for setting up a proxy
ObjectMeta: api.ObjectMeta{Name: "system:node-proxier"},
Rules: []rbac.PolicyRule{
// Used to build serviceLister
rbac.NewRule("list", "watch").Groups(legacyGroup).Resources("services", "endpoints").RuleOrDie(),
},
},
{
// a role to use for allowing authentication and authorization delegation
ObjectMeta: api.ObjectMeta{Name: "system:auth-delegator"},
Rules: []rbac.PolicyRule{
// These creates are non-mutating
rbac.NewRule("create").Groups(authenticationGroup).Resources("tokenreviews").RuleOrDie(),
rbac.NewRule("create").Groups(authorizationGroup).Resources("subjectaccessreviews").RuleOrDie(),
},
},
}
}
// ClusterRoleBindings return default rolebindings to the default roles
func ClusterRoleBindings() []rbac.ClusterRoleBinding {
return []rbac.ClusterRoleBinding{
rbac.NewClusterBinding("cluster-admin").Groups(user.SystemPrivilegedGroup).BindingOrDie(),
rbac.NewClusterBinding("system:discovery").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:basic-user").Groups(user.AllAuthenticated, user.AllUnauthenticated).BindingOrDie(),
rbac.NewClusterBinding("system:node").Groups(user.NodesGroup).BindingOrDie(),
rbac.NewClusterBinding("system:node-proxier").Groups(user.NodesGroup).BindingOrDie(),
}
}

View file

@ -0,0 +1,138 @@
/*
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 bootstrappolicy_test
import (
"testing"
rbac "k8s.io/kubernetes/pkg/apis/rbac"
rbacvalidation "k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac/bootstrappolicy"
)
// semanticRoles is a few enumerated roles for which the relationships are well established
// and we want to maintain symmetric roles
type semanticRoles struct {
admin *rbac.ClusterRole
edit *rbac.ClusterRole
view *rbac.ClusterRole
}
func getSemanticRoles(roles []rbac.ClusterRole) semanticRoles {
ret := semanticRoles{}
for i := range roles {
role := roles[i]
switch role.Name {
case "admin":
ret.admin = &role
case "edit":
ret.edit = &role
case "view":
ret.view = &role
}
}
return ret
}
// Some roles should always cover others
func TestCovers(t *testing.T) {
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("failed to cover: %#v", miss)
}
}
// additionalAdminPowers is the list of powers that we expect to be different than the editor role.
// one resource per rule to make the "does not already contain" check easy
var additionalAdminPowers = []rbac.PolicyRule{
rbac.NewRule("create").Groups("authorization.k8s.io").Resources("localsubjectaccessreviews").RuleOrDie(),
rbac.NewRule(bootstrappolicy.ReadWrite...).Groups("rbac.authorization.k8s.io").Resources("rolebindings").RuleOrDie(),
rbac.NewRule(bootstrappolicy.ReadWrite...).Groups("rbac.authorization.k8s.io").Resources("roles").RuleOrDie(),
}
func TestAdminEditRelationship(t *testing.T) {
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
// confirm that the edit role doesn't already have extra powers
for _, rule := range additionalAdminPowers {
if covers, _ := rbacvalidation.Covers(semanticRoles.edit.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("edit has extra powers: %#v", rule)
}
}
semanticRoles.edit.Rules = append(semanticRoles.edit.Rules, additionalAdminPowers...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacvalidation.Covers(semanticRoles.admin.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("admin has lost rules for: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.admin.Rules); !covers {
t.Errorf("edit is missing rules for: %#v\nIf these should only be admin powers, add them to the list. Otherwise, add them to the edit role.", miss)
}
}
// viewEscalatingNamespaceResources is the list of rules that would allow privilege escalation attacks based on
// ability to view (GET) them
var viewEscalatingNamespaceResources = []rbac.PolicyRule{
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/attach").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/proxy").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/exec").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("pods/portforward").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("secrets").RuleOrDie(),
rbac.NewRule(bootstrappolicy.Read...).Groups("").Resources("services/proxy").RuleOrDie(),
}
func TestEditViewRelationship(t *testing.T) {
readVerbs := sets.NewString(bootstrappolicy.Read...)
semanticRoles := getSemanticRoles(bootstrappolicy.ClusterRoles())
// modify the edit role rules to make then read-only for comparison against view role rules
for i := range semanticRoles.edit.Rules {
rule := semanticRoles.edit.Rules[i]
remainingVerbs := []string{}
for _, verb := range rule.Verbs {
if readVerbs.Has(verb) {
remainingVerbs = append(remainingVerbs, verb)
}
}
rule.Verbs = remainingVerbs
semanticRoles.edit.Rules[i] = rule
}
// confirm that the view role doesn't already have extra powers
for _, rule := range viewEscalatingNamespaceResources {
if covers, _ := rbacvalidation.Covers(semanticRoles.view.Rules, []rbac.PolicyRule{rule}); covers {
t.Errorf("view has extra powers: %#v", rule)
}
}
semanticRoles.view.Rules = append(semanticRoles.view.Rules, viewEscalatingNamespaceResources...)
// at this point, we should have a two way covers relationship
if covers, miss := rbacvalidation.Covers(semanticRoles.edit.Rules, semanticRoles.view.Rules); !covers {
t.Errorf("edit has lost rules for: %#v", miss)
}
if covers, miss := rbacvalidation.Covers(semanticRoles.view.Rules, semanticRoles.edit.Rules); !covers {
t.Errorf("view is missing rules for: %#v\nIf these are escalating powers, add them to the list. Otherwise, add them to the view role.", miss)
}
}

View file

@ -0,0 +1,89 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package rbac implements the authorizer.Authorizer interface using roles base access control.
package rbac
import (
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
)
type RequestToRuleMapper interface {
// RulesFor returns all known PolicyRules and any errors that happened while locating those rules.
// Any rule returned is still valid, since rules are deny by default. If you can pass with the rules
// supplied, you do not have to fail the request. If you cannot, you should indicate the error along
// with your denial.
RulesFor(subject user.Info, namespace string) ([]rbac.PolicyRule, error)
}
type RBACAuthorizer struct {
superUser string
authorizationRuleResolver RequestToRuleMapper
}
func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (bool, string, error) {
if r.superUser != "" && requestAttributes.GetUser() != nil && requestAttributes.GetUser().GetName() == r.superUser {
return true, "", nil
}
rules, ruleResolutionError := r.authorizationRuleResolver.RulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace())
if RulesAllow(requestAttributes, rules...) {
return true, "", nil
}
return false, "", ruleResolutionError
}
func New(roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister, superUser string) *RBACAuthorizer {
authorizer := &RBACAuthorizer{
superUser: superUser,
authorizationRuleResolver: validation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return authorizer
}
func RulesAllow(requestAttributes authorizer.Attributes, rules ...rbac.PolicyRule) bool {
for _, rule := range rules {
if RuleAllows(requestAttributes, rule) {
return true
}
}
return false
}
func RuleAllows(requestAttributes authorizer.Attributes, rule rbac.PolicyRule) bool {
if requestAttributes.IsResourceRequest() {
resource := requestAttributes.GetResource()
if len(requestAttributes.GetSubresource()) > 0 {
resource = requestAttributes.GetResource() + "/" + requestAttributes.GetSubresource()
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.APIGroupMatches(rule, requestAttributes.GetAPIGroup()) &&
rbac.ResourceMatches(rule, resource) &&
rbac.ResourceNameMatches(rule, requestAttributes.GetName())
}
return rbac.VerbMatches(rule, requestAttributes.GetVerb()) &&
rbac.NonResourceURLMatches(rule, requestAttributes.GetPath())
}

View file

@ -0,0 +1,418 @@
/*
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 rbac
import (
"fmt"
"strings"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
)
func newRule(verbs, apiGroups, resources, nonResourceURLs string) rbac.PolicyRule {
return rbac.PolicyRule{
Verbs: strings.Split(verbs, ","),
APIGroups: strings.Split(apiGroups, ","),
Resources: strings.Split(resources, ","),
NonResourceURLs: strings.Split(nonResourceURLs, ","),
}
}
func newRole(name, namespace string, rules ...rbac.PolicyRule) *rbac.Role {
return &rbac.Role{ObjectMeta: api.ObjectMeta{Namespace: namespace, Name: name}, Rules: rules}
}
func newClusterRole(name string, rules ...rbac.PolicyRule) *rbac.ClusterRole {
return &rbac.ClusterRole{ObjectMeta: api.ObjectMeta{Name: name}, Rules: rules}
}
const (
bindToRole uint16 = 0x0
bindToClusterRole uint16 = 0x1
)
func newClusterRoleBinding(roleName string, subjects ...string) *rbac.ClusterRoleBinding {
r := &rbac.ClusterRoleBinding{
ObjectMeta: api.ObjectMeta{},
RoleRef: rbac.RoleRef{
APIGroup: rbac.GroupName,
Kind: "ClusterRole", // ClusterRoleBindings can only refer to ClusterRole
Name: roleName,
},
}
r.Subjects = make([]rbac.Subject, len(subjects))
for i, subject := range subjects {
split := strings.SplitN(subject, ":", 2)
r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
}
return r
}
func newRoleBinding(namespace, roleName string, bindType uint16, subjects ...string) *rbac.RoleBinding {
r := &rbac.RoleBinding{ObjectMeta: api.ObjectMeta{Namespace: namespace}}
switch bindType {
case bindToRole:
r.RoleRef = rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "Role", Name: roleName}
case bindToClusterRole:
r.RoleRef = rbac.RoleRef{APIGroup: rbac.GroupName, Kind: "ClusterRole", Name: roleName}
}
r.Subjects = make([]rbac.Subject, len(subjects))
for i, subject := range subjects {
split := strings.SplitN(subject, ":", 2)
r.Subjects[i].Kind, r.Subjects[i].Name = split[0], split[1]
}
return r
}
type defaultAttributes struct {
user string
groups string
verb string
resource string
subresource string
namespace string
apiGroup string
}
func (d *defaultAttributes) String() string {
return fmt.Sprintf("user=(%s), groups=(%s), verb=(%s), resource=(%s), namespace=(%s), apiGroup=(%s)",
d.user, strings.Split(d.groups, ","), d.verb, d.resource, d.namespace, d.apiGroup)
}
func (d *defaultAttributes) GetUser() user.Info {
return &user.DefaultInfo{Name: d.user, Groups: strings.Split(d.groups, ",")}
}
func (d *defaultAttributes) GetVerb() string { return d.verb }
func (d *defaultAttributes) IsReadOnly() bool { return d.verb == "get" || d.verb == "watch" }
func (d *defaultAttributes) GetNamespace() string { return d.namespace }
func (d *defaultAttributes) GetResource() string { return d.resource }
func (d *defaultAttributes) GetSubresource() string { return d.subresource }
func (d *defaultAttributes) GetName() string { return "" }
func (d *defaultAttributes) GetAPIGroup() string { return d.apiGroup }
func (d *defaultAttributes) GetAPIVersion() string { return "" }
func (d *defaultAttributes) IsResourceRequest() bool { return true }
func (d *defaultAttributes) GetPath() string { return "" }
func TestAuthorizer(t *testing.T) {
tests := []struct {
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
superUser string
shouldPass []authorizer.Attributes
shouldFail []authorizer.Attributes
}{
{
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "Pods", "", "ns1", ""},
&defaultAttributes{"admin", "", "watch", "Pods", "", "ns1", ""},
&defaultAttributes{"admin", "group1", "watch", "Foobar", "", "ns1", ""},
&defaultAttributes{"joe", "admins", "watch", "Foobar", "", "ns1", ""},
&defaultAttributes{"joe", "group1,admins", "watch", "Foobar", "", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "GET", "Pods", "", "ns2", ""},
&defaultAttributes{"admin", "", "GET", "Nodes", "", "", ""},
&defaultAttributes{"admin", "admins", "GET", "Pods", "", "ns2", ""},
&defaultAttributes{"admin", "admins", "GET", "Nodes", "", "", ""},
},
},
{
// Non-resource-url tests
clusterRoles: []*rbac.ClusterRole{
newClusterRole("non-resource-url-getter", newRule("get", "", "", "/apis")),
newClusterRole("non-resource-url", newRule("*", "", "", "/apis")),
newClusterRole("non-resource-url-prefix", newRule("get", "", "", "/apis/*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("non-resource-url-getter", "User:foo", "Group:bar"),
newClusterRoleBinding("non-resource-url", "User:admin", "Group:admin"),
newClusterRoleBinding("non-resource-url-prefix", "User:prefixed", "Group:prefixed"),
},
shouldPass: []authorizer.Attributes{
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/apis/v1/foobar"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/apis/v1/foorbar"},
},
shouldFail: []authorizer.Attributes{
// wrong verb
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "watch", Path: "/apis"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "watch", Path: "/apis"},
// wrong path
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "foo"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"bar"}}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "admin"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"admin"}}, Verb: "get", Path: "/api/v1"},
// not covered by prefix
authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "prefixed"}, Verb: "get", Path: "/api/v1"},
authorizer.AttributesRecord{User: &user.DefaultInfo{Groups: []string{"prefixed"}}, Verb: "get", Path: "/api/v1"},
},
},
{
// test subresource resolution
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "pods", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
},
},
{
// test subresource resolution
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "pods/status", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
shouldPass: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "status", "ns1", ""},
},
shouldFail: []authorizer.Attributes{
&defaultAttributes{"admin", "", "get", "pods", "", "ns1", ""},
},
},
}
for i, tt := range tests {
ruleResolver, _ := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := RBACAuthorizer{tt.superUser, ruleResolver}
for _, attr := range tt.shouldPass {
if authorized, _, _ := a.Authorize(attr); !authorized {
t.Errorf("case %d: incorrectly restricted %s", i, attr)
}
}
for _, attr := range tt.shouldFail {
if authorized, _, _ := a.Authorize(attr); authorized {
t.Errorf("case %d: incorrectly passed %s", i, attr)
}
}
}
}
func TestRuleMatches(t *testing.T) {
tests := []struct {
name string
rule rbac.PolicyRule
requestsToExpected map[authorizer.AttributesRecord]bool
}{
{
name: "star verb, exact match other",
rule: rbac.NewRule("*").Groups("group1").Resources("resource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "star group, exact match other",
rule: rbac.NewRule("verb1").Groups("*").Resources("resource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "star resource, exact match other",
rule: rbac.NewRule("verb1").Groups("group1").Resources("*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): false,
},
},
{
name: "tuple expansion",
rule: rbac.NewRule("verb1", "verb2").Groups("group1", "group2").Resources("resource1", "resource2").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb1").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource2").New(): true,
resourceRequest("verb2").Group("group1").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").New(): true,
resourceRequest("verb2").Group("group1").Resource("resource2").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource2").New(): true,
},
},
{
name: "subresource expansion",
rule: rbac.NewRule("*").Groups("*").Resources("resource1/subresource1").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
resourceRequest("verb1").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
resourceRequest("verb1").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
resourceRequest("verb1").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb1").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource1").Subresource("subresource1").New(): true,
resourceRequest("verb2").Group("group2").Resource("resource1").Subresource("subresource2").New(): false,
resourceRequest("verb2").Group("group1").Resource("resource2").Subresource("subresource1").New(): false,
resourceRequest("verb2").Group("group2").Resource("resource2").Subresource("subresource1").New(): false,
},
},
{
name: "star nonresource, exact match other",
rule: rbac.NewRule("verb1").URLs("*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): true,
nonresourceRequest("verb1").URL("/foo/bar").New(): true,
nonresourceRequest("verb1").URL("/foo/baz").New(): true,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): true,
nonresourceRequest("verb2").URL("/foo").New(): false,
nonresourceRequest("verb2").URL("/foo/bar").New(): false,
nonresourceRequest("verb2").URL("/foo/baz").New(): false,
nonresourceRequest("verb2").URL("/foo/bar/one").New(): false,
nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
},
},
{
name: "star nonresource subpath",
rule: rbac.NewRule("verb1").URLs("/foo/*").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): false,
nonresourceRequest("verb1").URL("/foo/bar").New(): true,
nonresourceRequest("verb1").URL("/foo/baz").New(): true,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): true,
nonresourceRequest("verb1").URL("/notfoo").New(): false,
nonresourceRequest("verb1").URL("/notfoo/bar").New(): false,
nonresourceRequest("verb1").URL("/notfoo/baz").New(): false,
nonresourceRequest("verb1").URL("/notfoo/bar/one").New(): false,
nonresourceRequest("verb1").URL("/notfoo/baz/one").New(): false,
},
},
{
name: "star verb, exact nonresource",
rule: rbac.NewRule("*").URLs("/foo", "/foo/bar/one").RuleOrDie(),
requestsToExpected: map[authorizer.AttributesRecord]bool{
nonresourceRequest("verb1").URL("/foo").New(): true,
nonresourceRequest("verb1").URL("/foo/bar").New(): false,
nonresourceRequest("verb1").URL("/foo/baz").New(): false,
nonresourceRequest("verb1").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb1").URL("/foo/baz/one").New(): false,
nonresourceRequest("verb2").URL("/foo").New(): true,
nonresourceRequest("verb2").URL("/foo/bar").New(): false,
nonresourceRequest("verb2").URL("/foo/baz").New(): false,
nonresourceRequest("verb2").URL("/foo/bar/one").New(): true,
nonresourceRequest("verb2").URL("/foo/baz/one").New(): false,
},
},
}
for _, tc := range tests {
for request, expected := range tc.requestsToExpected {
if e, a := expected, RuleAllows(request, tc.rule); e != a {
t.Errorf("%q: expected %v, got %v for %v", tc.name, e, a, request)
}
}
}
}
type requestAttributeBuilder struct {
request authorizer.AttributesRecord
}
func resourceRequest(verb string) *requestAttributeBuilder {
return &requestAttributeBuilder{
request: authorizer.AttributesRecord{ResourceRequest: true, Verb: verb},
}
}
func nonresourceRequest(verb string) *requestAttributeBuilder {
return &requestAttributeBuilder{
request: authorizer.AttributesRecord{ResourceRequest: false, Verb: verb},
}
}
func (r *requestAttributeBuilder) Group(group string) *requestAttributeBuilder {
r.request.APIGroup = group
return r
}
func (r *requestAttributeBuilder) Resource(resource string) *requestAttributeBuilder {
r.request.Resource = resource
return r
}
func (r *requestAttributeBuilder) Subresource(subresource string) *requestAttributeBuilder {
r.request.Subresource = subresource
return r
}
func (r *requestAttributeBuilder) Name(name string) *requestAttributeBuilder {
r.request.Name = name
return r
}
func (r *requestAttributeBuilder) URL(url string) *requestAttributeBuilder {
r.request.Path = url
return r
}
func (r *requestAttributeBuilder) New() authorizer.AttributesRecord {
return r.request
}

View file

@ -0,0 +1,117 @@
/*
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 rbac implements the authorizer.Authorizer interface using roles base access control.
package rbac
import (
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
utilerrors "k8s.io/kubernetes/pkg/util/errors"
)
type RoleToRuleMapper interface {
// GetRoleReferenceRules attempts to resolve the role reference of a RoleBinding or ClusterRoleBinding. The passed namespace should be the namespace
// of the role binding, the empty string if a cluster role binding.
GetRoleReferenceRules(roleRef rbac.RoleRef, namespace string) ([]rbac.PolicyRule, error)
}
type SubjectAccessEvaluator struct {
superUser string
roleBindingLister validation.RoleBindingLister
clusterRoleBindingLister validation.ClusterRoleBindingLister
roleToRuleMapper RoleToRuleMapper
}
func NewSubjectAccessEvaluator(roles validation.RoleGetter, roleBindings validation.RoleBindingLister, clusterRoles validation.ClusterRoleGetter, clusterRoleBindings validation.ClusterRoleBindingLister, superUser string) *SubjectAccessEvaluator {
subjectLocator := &SubjectAccessEvaluator{
superUser: superUser,
roleBindingLister: roleBindings,
clusterRoleBindingLister: clusterRoleBindings,
roleToRuleMapper: validation.NewDefaultRuleResolver(
roles, roleBindings, clusterRoles, clusterRoleBindings,
),
}
return subjectLocator
}
// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list.
// It is possible to have both subjects and errors returned if some rolebindings couldn't be resolved, but others could be.
func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.Attributes) ([]rbac.Subject, error) {
subjects := []rbac.Subject{{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup}}
if len(r.superUser) > 0 {
subjects = append(subjects, rbac.Subject{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: r.superUser})
}
errorlist := []error{}
if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil {
errorlist = append(errorlist, err)
} else {
for _, clusterRoleBinding := range clusterRoleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "")
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, clusterRoleBinding.Subjects...)
}
}
}
if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 {
if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil {
errorlist = append(errorlist, err)
} else {
for _, roleBinding := range roleBindings {
rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace)
if err != nil {
// if we have an error, just keep track of it and keep processing. Since rules are additive,
// missing a reference is bad, but we can continue with other rolebindings and still have a list
// that does not contain any invalid values
errorlist = append(errorlist, err)
}
if RulesAllow(requestAttributes, rules...) {
subjects = append(subjects, roleBinding.Subjects...)
}
}
}
}
dedupedSubjects := []rbac.Subject{}
for _, subject := range subjects {
found := false
for _, curr := range dedupedSubjects {
if curr == subject {
found = true
break
}
}
if !found {
dedupedSubjects = append(dedupedSubjects, subject)
}
}
return subjects, utilerrors.NewAggregate(errorlist)
}

View file

@ -0,0 +1,151 @@
/*
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 rbac
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/apis/rbac"
"k8s.io/kubernetes/pkg/apis/rbac/validation"
"k8s.io/kubernetes/pkg/auth/authorizer"
"k8s.io/kubernetes/pkg/auth/user"
)
func TestSubjectLocator(t *testing.T) {
type actionToSubjects struct {
action authorizer.Attributes
subjects []rbac.Subject
}
tests := []struct {
name string
roles []*rbac.Role
roleBindings []*rbac.RoleBinding
clusterRoles []*rbac.ClusterRole
clusterRoleBindings []*rbac.ClusterRoleBinding
superUser string
actionsToSubjects []actionToSubjects
}{
{
name: "no super user, star matches star",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToClusterRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
{Kind: rbac.UserKind, Name: "admin"},
{Kind: rbac.GroupKind, Name: "admins"},
},
},
{
// cluster role matches star in namespace
&defaultAttributes{"", "", "*", "Pods", "", "*", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
{
// empty ns
&defaultAttributes{"", "", "*", "Pods", "", "", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
},
},
{
name: "super user, local roles work",
superUser: "foo",
clusterRoles: []*rbac.ClusterRole{
newClusterRole("admin", newRule("*", "*", "*", "*")),
},
clusterRoleBindings: []*rbac.ClusterRoleBinding{
newClusterRoleBinding("admin", "User:super-admin", "Group:super-admins"),
},
roles: []*rbac.Role{
newRole("admin", "ns1", newRule("get", "*", "Pods", "*")),
},
roleBindings: []*rbac.RoleBinding{
newRoleBinding("ns1", "admin", bindToRole, "User:admin", "Group:admins"),
},
actionsToSubjects: []actionToSubjects{
{
&defaultAttributes{"", "", "get", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
{Kind: rbac.UserKind, Name: "admin"},
{Kind: rbac.GroupKind, Name: "admins"},
},
},
{
// verb matchies correctly
&defaultAttributes{"", "", "create", "Pods", "", "ns1", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
{
// binding only works in correct ns
&defaultAttributes{"", "", "get", "Pods", "", "ns2", ""},
[]rbac.Subject{
{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup},
{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: "foo"},
{Kind: rbac.UserKind, Name: "super-admin"},
{Kind: rbac.GroupKind, Name: "super-admins"},
},
},
},
},
}
for _, tt := range tests {
ruleResolver, lister := validation.NewTestRuleResolver(tt.roles, tt.roleBindings, tt.clusterRoles, tt.clusterRoleBindings)
a := SubjectAccessEvaluator{tt.superUser, lister, lister, ruleResolver}
for i, action := range tt.actionsToSubjects {
actualSubjects, err := a.AllowedSubjects(action.action)
if err != nil {
t.Errorf("case %q %d: error %v", tt.name, i, err)
}
if !reflect.DeepEqual(actualSubjects, action.subjects) {
t.Errorf("case %q %d: expected %v actual %v", tt.name, i, action.subjects, actualSubjects)
}
}
}
}

View file

@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["webhook.go"],
tags = ["automanaged"],
deps = [
"//pkg/apis/authorization/install:go_default_library",
"//pkg/apis/authorization/v1beta1:go_default_library",
"//pkg/auth/authorizer:go_default_library",
"//pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1:go_default_library",
"//pkg/runtime/schema:go_default_library",
"//pkg/util/cache:go_default_library",
"//plugin/pkg/webhook:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = [
"certs_test.go",
"webhook_test.go",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/apis/authorization/v1beta1:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/auth/authorizer:go_default_library",
"//pkg/auth/user:go_default_library",
"//pkg/client/unversioned/clientcmd/api/v1:go_default_library",
"//pkg/util/diff:go_default_library",
],
)

View 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-----`)

View 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

View file

@ -0,0 +1,228 @@
/*
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"
authorization "k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
"k8s.io/kubernetes/pkg/auth/authorizer"
authorizationclient "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/typed/authorization/v1beta1"
"k8s.io/kubernetes/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/util/cache"
"k8s.io/kubernetes/plugin/pkg/webhook"
_ "k8s.io/kubernetes/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(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
}

File diff suppressed because it is too large Load diff

18
vendor/k8s.io/kubernetes/plugin/pkg/auth/doc.go generated vendored Normal file
View 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 auth contains implementations for interfaces in the pkg/auth package
package auth // import "k8s.io/kubernetes/plugin/pkg/auth"