mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-21 17:48:01 +00:00
*: add webhook implementation of authorizer.Authorizer plugin
This commit is contained in:
parent
6a199706cb
commit
3116346161
@ -47,7 +47,7 @@ type APIServer struct {
|
|||||||
AdvertiseAddress net.IP
|
AdvertiseAddress net.IP
|
||||||
AllowPrivileged bool
|
AllowPrivileged bool
|
||||||
AuthorizationMode string
|
AuthorizationMode string
|
||||||
AuthorizationPolicyFile string
|
AuthorizationConfig apiserver.AuthorizationConfig
|
||||||
BasicAuthFile string
|
BasicAuthFile string
|
||||||
CloudConfigFile string
|
CloudConfigFile string
|
||||||
CloudProvider string
|
CloudProvider string
|
||||||
@ -214,7 +214,8 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
|
|||||||
fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup, "If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
|
fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup, "If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
|
||||||
fs.StringVar(&s.KeystoneURL, "experimental-keystone-url", s.KeystoneURL, "If passed, activates the keystone authentication plugin")
|
fs.StringVar(&s.KeystoneURL, "experimental-keystone-url", s.KeystoneURL, "If passed, activates the keystone authentication plugin")
|
||||||
fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
|
fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
|
||||||
fs.StringVar(&s.AuthorizationPolicyFile, "authorization-policy-file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
|
fs.StringVar(&s.AuthorizationConfig.PolicyFile, "authorization-policy-file", s.AuthorizationConfig.PolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
|
||||||
|
fs.StringVar(&s.AuthorizationConfig.WebhookConfigFile, "authorization-webhook-config-file", s.AuthorizationConfig.WebhookConfigFile, "File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. The API server will query the remote service to determine access on the API server's secure port.")
|
||||||
fs.StringVar(&s.AdmissionControl, "admission-control", s.AdmissionControl, "Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: "+strings.Join(admission.GetPlugins(), ", "))
|
fs.StringVar(&s.AdmissionControl, "admission-control", s.AdmissionControl, "Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: "+strings.Join(admission.GetPlugins(), ", "))
|
||||||
fs.StringVar(&s.AdmissionControlConfigFile, "admission-control-config-file", s.AdmissionControlConfigFile, "File with admission control configuration.")
|
fs.StringVar(&s.AdmissionControlConfigFile, "admission-control-config-file", s.AdmissionControlConfigFile, "File with admission control configuration.")
|
||||||
fs.StringSliceVar(&s.EtcdServerList, "etcd-servers", s.EtcdServerList, "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd-config")
|
fs.StringSliceVar(&s.EtcdServerList, "etcd-servers", s.EtcdServerList, "List of etcd servers to watch (http://ip:port), comma separated. Mutually exclusive with -etcd-config")
|
||||||
|
@ -406,7 +406,7 @@ func Run(s *options.APIServer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
authorizationModeNames := strings.Split(s.AuthorizationMode, ",")
|
authorizationModeNames := strings.Split(s.AuthorizationMode, ",")
|
||||||
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, s.AuthorizationPolicyFile)
|
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, s.AuthorizationConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Invalid Authorization Config: %v", err)
|
glog.Fatalf("Invalid Authorization Config: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer/abac"
|
"k8s.io/kubernetes/pkg/auth/authorizer/abac"
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer/union"
|
"k8s.io/kubernetes/pkg/auth/authorizer/union"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Attributes implements authorizer.Attributes interface.
|
// Attributes implements authorizer.Attributes interface.
|
||||||
@ -60,15 +61,28 @@ const (
|
|||||||
ModeAlwaysAllow string = "AlwaysAllow"
|
ModeAlwaysAllow string = "AlwaysAllow"
|
||||||
ModeAlwaysDeny string = "AlwaysDeny"
|
ModeAlwaysDeny string = "AlwaysDeny"
|
||||||
ModeABAC string = "ABAC"
|
ModeABAC string = "ABAC"
|
||||||
|
ModeWebhook string = "Webhook"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Keep this list in sync with constant list above.
|
// Keep this list in sync with constant list above.
|
||||||
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}
|
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook}
|
||||||
|
|
||||||
|
type AuthorizationConfig struct {
|
||||||
|
// Options for ModeABAC
|
||||||
|
|
||||||
|
// Path to a ABAC policy file.
|
||||||
|
PolicyFile string
|
||||||
|
|
||||||
|
// Options for ModeWebhook
|
||||||
|
|
||||||
|
// Kubeconfig file for Webhook authorization plugin.
|
||||||
|
WebhookConfigFile string
|
||||||
|
}
|
||||||
|
|
||||||
// NewAuthorizerFromAuthorizationConfig returns the right sort of union of multiple authorizer.Authorizer objects
|
// NewAuthorizerFromAuthorizationConfig returns the right sort of union of multiple authorizer.Authorizer objects
|
||||||
// based on the authorizationMode or an error. authorizationMode should be a comma separated values
|
// based on the authorizationMode or an error. authorizationMode should be a comma separated values
|
||||||
// of AuthorizationModeChoices.
|
// of AuthorizationModeChoices.
|
||||||
func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, authorizationPolicyFile string) (authorizer.Authorizer, error) {
|
func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, config AuthorizationConfig) (authorizer.Authorizer, error) {
|
||||||
|
|
||||||
if len(authorizationModes) == 0 {
|
if len(authorizationModes) == 0 {
|
||||||
return nil, errors.New("Atleast one authorization mode should be passed")
|
return nil, errors.New("Atleast one authorization mode should be passed")
|
||||||
@ -88,23 +102,35 @@ func NewAuthorizerFromAuthorizationConfig(authorizationModes []string, authoriza
|
|||||||
case ModeAlwaysDeny:
|
case ModeAlwaysDeny:
|
||||||
authorizers = append(authorizers, NewAlwaysDenyAuthorizer())
|
authorizers = append(authorizers, NewAlwaysDenyAuthorizer())
|
||||||
case ModeABAC:
|
case ModeABAC:
|
||||||
if authorizationPolicyFile == "" {
|
if config.PolicyFile == "" {
|
||||||
return nil, errors.New("ABAC's authorization policy file not passed")
|
return nil, errors.New("ABAC's authorization policy file not passed")
|
||||||
}
|
}
|
||||||
abacAuthorizer, err := abac.NewFromFile(authorizationPolicyFile)
|
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
authorizers = append(authorizers, abacAuthorizer)
|
authorizers = append(authorizers, abacAuthorizer)
|
||||||
|
case ModeWebhook:
|
||||||
|
if config.WebhookConfigFile == "" {
|
||||||
|
return nil, errors.New("Webhook's configuration file not passed")
|
||||||
|
}
|
||||||
|
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
authorizers = append(authorizers, webhookAuthorizer)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
|
return nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
|
||||||
}
|
}
|
||||||
authorizerMap[authorizationMode] = true
|
authorizerMap[authorizationMode] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authorizerMap[ModeABAC] && authorizationPolicyFile != "" {
|
if !authorizerMap[ModeABAC] && config.PolicyFile != "" {
|
||||||
return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
|
return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
|
||||||
}
|
}
|
||||||
|
if !authorizerMap[ModeWebhook] && config.WebhookConfigFile != "" {
|
||||||
|
return nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook")
|
||||||
|
}
|
||||||
|
|
||||||
return union.New(authorizers...), nil
|
return union.New(authorizers...), nil
|
||||||
}
|
}
|
||||||
|
@ -41,31 +41,75 @@ func TestNewAlwaysDenyAuthorizer(t *testing.T) {
|
|||||||
// NewAuthorizerFromAuthorizationConfig has multiple return possibilities. This test
|
// NewAuthorizerFromAuthorizationConfig has multiple return possibilities. This test
|
||||||
// validates that errors are returned only when proper.
|
// validates that errors are returned only when proper.
|
||||||
func TestNewAuthorizerFromAuthorizationConfig(t *testing.T) {
|
func TestNewAuthorizerFromAuthorizationConfig(t *testing.T) {
|
||||||
// Unknown modes should return errors
|
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{"DoesNotExist"}, ""); err == nil {
|
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig using a fake mode should have returned an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
examplePolicyFile := "../auth/authorizer/abac/example_policy_file.jsonl"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
modes []string
|
||||||
|
config AuthorizationConfig
|
||||||
|
wantErr bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Unknown modes should return errors
|
||||||
|
modes: []string{"DoesNotExist"},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "using a fake mode should have returned an error",
|
||||||
|
},
|
||||||
|
{
|
||||||
// ModeAlwaysAllow and ModeAlwaysDeny should return without authorizationPolicyFile
|
// ModeAlwaysAllow and ModeAlwaysDeny should return without authorizationPolicyFile
|
||||||
// but error if one is given
|
// but error if one is given
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{ModeAlwaysAllow, ModeAlwaysDeny}, ""); err != nil {
|
modes: []string{ModeAlwaysAllow, ModeAlwaysDeny},
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig returned an error: %s", err)
|
msg: "returned an error for valid config",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ModeABAC requires a policy file
|
||||||
|
modes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "specifying ABAC with no policy file should return an error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ModeABAC should not error if a valid policy path is provided
|
||||||
|
modes: []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC},
|
||||||
|
config: AuthorizationConfig{PolicyFile: examplePolicyFile},
|
||||||
|
msg: "errored while using a valid policy file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
// Authorization Policy file cannot be used without ModeABAC
|
||||||
|
modes: []string{ModeAlwaysAllow, ModeAlwaysDeny},
|
||||||
|
config: AuthorizationConfig{PolicyFile: examplePolicyFile},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "should have errored when Authorization Policy File is used without ModeABAC",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Atleast one authorizationMode is necessary
|
||||||
|
modes: []string{},
|
||||||
|
config: AuthorizationConfig{PolicyFile: examplePolicyFile},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "should have errored when no authorization modes are passed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// ModeWebhook requires at minimum a target.
|
||||||
|
modes: []string{ModeWebhook},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "should have errored when config was empty with ModeWebhook",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Cannot provide webhook flags without ModeWebhook
|
||||||
|
modes: []string{ModeAlwaysAllow},
|
||||||
|
config: AuthorizationConfig{WebhookConfigFile: "authz_webhook_config.yml"},
|
||||||
|
wantErr: true,
|
||||||
|
msg: "should have errored when Webhook config file is used without ModeWebhook",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModeABAC requires a policy file
|
for _, tt := range tests {
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}, ""); err == nil {
|
_, err := NewAuthorizerFromAuthorizationConfig(tt.modes, tt.config)
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig using a fake mode should have returned an error")
|
if tt.wantErr && (err == nil) {
|
||||||
}
|
t.Errorf("NewAuthorizerFromAuthorizationConfig %s", tt.msg)
|
||||||
// ModeABAC should not error if a valid policy path is provided
|
} else if !tt.wantErr && (err != nil) {
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}, "../auth/authorizer/abac/example_policy_file.jsonl"); err != nil {
|
t.Errorf("NewAuthorizerFromAuthorizationConfig %s: %v", tt.msg, err)
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig errored while using a valid policy file: %s", err)
|
}
|
||||||
}
|
|
||||||
// Authorization Policy file cannot be used without ModeABAC
|
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{ModeAlwaysAllow, ModeAlwaysDeny}, "../auth/authorizer/abac/example_policy_file.jsonl"); err == nil {
|
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig should have errored when Authorization Policy File is used without ModeABAC")
|
|
||||||
}
|
|
||||||
// Atleast one authorizationMode is necessary
|
|
||||||
if _, err := NewAuthorizerFromAuthorizationConfig([]string{}, "../auth/authorizer/abac/example_policy_file.jsonl"); err == nil {
|
|
||||||
t.Errorf("NewAuthorizerFromAuthorizationConfig should have errored when no authorization modes are passed")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
plugin/pkg/auth/authorizer/doc.go
Normal file
18
plugin/pkg/auth/authorizer/doc.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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
|
211
plugin/pkg/auth/authorizer/webhook/certs_test.go
Normal file
211
plugin/pkg/auth/authorizer/webhook/certs_test.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This file was generated using openssl by the gencerts.sh script
|
||||||
|
// and holds raw certificates for the webhook tests.
|
||||||
|
|
||||||
|
package webhook
|
||||||
|
|
||||||
|
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEA6IVXGPX5yP2Q6TAlQXIQsavzSqZ973iZvpQBGTI6M98gTSVm
|
||||||
|
eBYE3o7S8e6WTI3DCnWwqc8Md1rT92FtaQLwv+uMNXijLio5RVBqjUEbunD5In/+
|
||||||
|
T/y5sE9P3CzcWy6CEhIvORAZj6UlvgZzbRwI91+EVFR5jd8JU0e/L9Ds1jLZFyQw
|
||||||
|
Kc1ADo+Tj9O4l0WtpRlrhzTgoor4C3fAQZm0mq+llTnxCmw+lhy8t88bPG1cMwdd
|
||||||
|
DtUTbpetc++2JZ62Q3F1nqcX1EcHDidR0x3j+3357BLkXRK4MQsWLYLzeZ3X1ghW
|
||||||
|
XT062H866PcIV+MX4H58spMN5cVYk5YTneGihQIDAQABAoIBAHU7FQieq4ssXK1U
|
||||||
|
+tOeQNBzUzxl6MSd11YApPUhH7sbWdvLaXhOEbJr6+rSUbDTIGzbnXBf1XcvsgLd
|
||||||
|
eh4hv2PjzFMBObSC0VEjFDWXh/VeFB3SzlNhpfVAZ5EohQjrz+RwiqKIfXqw1vCR
|
||||||
|
rAxswBCIdd1WodpngvocCEaBXYc4MblaPhJDVtxQe8ndEakkSDlX9Z3qIaIGyXRa
|
||||||
|
NvY/yURVuXhwDDd7C2QBT6CXGWhldAg7xrRVTcIoqAUfZCgfis0H8cQOa1cGNsbW
|
||||||
|
t/oHm1fYTxMKFPhWQG0oimx+XJ07BeGgraDRLnxxNnGWTg/W33bc0ZCxCVT0Q5p9
|
||||||
|
kMMfQUECgYEA9cewTK4ZRKC4bTdwqLTh3cyMkbyN4kBHmB1mS2FV/T0l4oZThM//
|
||||||
|
OZ6KFnRCuvfuJIOa70s2bqUYky8NTQAidnnbTW2nZ/E5JdeIBs1fAfadAqiPdmkf
|
||||||
|
MhvjBF/XfLnbCuXx3jA7GmNCpunJysuLtQzwlQlZLojN231uS+3LFbkCgYEA8jCC
|
||||||
|
MgKYaDWssQbT7zfk5MxyZIH3F9N8K2RBIDSVuMo/E1LCIJ06/k+4jdv8nAWYJXcN
|
||||||
|
eyLG7l0SXqrpMBSc9+ZTJgmbo0Mw+npvJHbJvAtD/XOSPjlIqkzPAUrxuiBYxa5S
|
||||||
|
IfKZibygXKAbQMEwY7I4sTbBtIyiQmo9csxt2S0CgYEAiBi1VSCquUfOGBw09BaF
|
||||||
|
Y85aoHCqmHhDrMXK2T7i4MG1csQzBz4t8/gIOvrR4LpdUjbV2l/pmkctXoMVeGf0
|
||||||
|
rWo4t51ar8HxhTTeC/Y4/9tRgiFYn5cCQTsT8F4p8tTvqA9AaWqHr8r7I3Yd2X/w
|
||||||
|
sqahqcVtbskuRLYmF0FrzXECgYAeiR0xPwCGSxYt78Vy6OI0Ms7Ne1FzMJf8RJSt
|
||||||
|
gdPKy70uK4YMZKaWf+iuAimUZmQrfRo3B0h7r0JsqzHhfQfZfbHIHvf/mq4nNp6i
|
||||||
|
w1NmISl+YD71F3Xg+vQynodhx0hKDFOQsizHn/+8DffBr1nxh/v75AKCSCUBKLH8
|
||||||
|
sme7NQKBgDHQac2TmDSelE2uXTGxEVDQs/EpdJh7oCTLQ99Xud/DsaCOrt2s7aRX
|
||||||
|
1FEohsCaUnqwS07/iH2o6Qb/qOteufB9I7FG85nAvqmP5dI4crGNNa8Rl6fXJaR8
|
||||||
|
TUwpZmylTKEJ9zLt2PADglyDrQ2D+1WNzh966Oo9c+kZt4WJM0aF
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDCzCCAfOgAwIBAgIJAKK9m2Cfg5uhMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||||
|
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||||
|
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||||
|
DQEBAQUAA4IBDwAwggEKAoIBAQDohVcY9fnI/ZDpMCVBchCxq/NKpn3veJm+lAEZ
|
||||||
|
Mjoz3yBNJWZ4FgTejtLx7pZMjcMKdbCpzwx3WtP3YW1pAvC/64w1eKMuKjlFUGqN
|
||||||
|
QRu6cPkif/5P/LmwT0/cLNxbLoISEi85EBmPpSW+BnNtHAj3X4RUVHmN3wlTR78v
|
||||||
|
0OzWMtkXJDApzUAOj5OP07iXRa2lGWuHNOCiivgLd8BBmbSar6WVOfEKbD6WHLy3
|
||||||
|
zxs8bVwzB10O1RNul61z77YlnrZDcXWepxfURwcOJ1HTHeP7ffnsEuRdErgxCxYt
|
||||||
|
gvN5ndfWCFZdPTrYfzro9whX4xfgfnyykw3lxViTlhOd4aKFAgMBAAGjUDBOMB0G
|
||||||
|
A1UdDgQWBBSumZL6MMwmFGyhQAwl/v0lYDzdZjAfBgNVHSMEGDAWgBSumZL6MMwm
|
||||||
|
FGyhQAwl/v0lYDzdZjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAG
|
||||||
|
6k+bZxKYq4PVZHWTKA7RSjv95FMMr4RSFwKn/n8TUD44ANWYqDrEfVmxAMn3NVK9
|
||||||
|
ckA8mIRym4IGiWD9eBGgPNNtbAq8Wl/9+5qbDMerpXuRnG3wNY7RU75Rl008m52r
|
||||||
|
c2i86ZPUi2fAJZyMf5StWE21oKiDYYQqlB6xxsIj6OHhf7536vEysoztNX5FpS2n
|
||||||
|
q8wG0EhJVhG+Qyww8IlZA5Cjoh71Eqkcwb4cuLjPypxmLm0ywZ/6KgzV+IF+CT2v
|
||||||
|
TJIpMokDUKlRi9cWSqkWXFE6xbCmhrrwKYsi0X6Vvi7a0pmOnSzKCQl8jN8u4A9R
|
||||||
|
xar2YeJ6mCCzSAPM69DP
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEAon7dRV4Br10dLcf8zgs/hOHouELveFr8tuWVIFivxSdnac2k
|
||||||
|
6dM4iQ2uYS9nTXxNhyJJ/TX/MHEYc4gSXoqUbtx9jE3VA4mCKDhO7cJtCYxq0QV/
|
||||||
|
PlQCiAPjn5nUMt9ACdii7/uTFDl46bK9K6ajvKHfHoWeYaJsF54kxBq5IMj+QaB2
|
||||||
|
nc+pba00bGG09sYcHyD37QH+ugx64x+21xMYj2LB/uPoqZM0kj1GHPxAs8GqFq2P
|
||||||
|
gwkv589AlHqt2iMCTAqED2jcg4FeS2r1DeYHwGyGAPfWTdA8RZ+gZ/P0Gj91T+4B
|
||||||
|
9srR7BybUFjf1KxEcvPXBvP5r8OwOiYjS8hx/wIDAQABAoIBAQCVBQ9bfDjDX/tQ
|
||||||
|
buVS+FHKRXss8IW4tIiqGqXGQk7/2YEnMKaaoVBpsBhJnDV6hBJ9aV69TnW3MSCh
|
||||||
|
YxqlhSVW/fJNZ1uAoOyygeEwfmuMpC+ZfRcSS+z+W8K2LVbDSKXr4babqvVZSNOw
|
||||||
|
TnDZxTrH1RNPZG65T0Ed77P7/B3nB7aeB2UMuHMQNZ3KrYDTck2R2uTGp+29TplN
|
||||||
|
blS4VAg2/9KqFr7jkS3/C4jjxVd7d9mm0VdAvLcvENVXqSTYV8xDp+VLTnmtXi5f
|
||||||
|
LXcopS+zKtKqT7MM7RA2sKrmSfrQBIXW2E1kfDFtpZHajhDutdYkSTH665W1G23M
|
||||||
|
dIgy3ajhAoGBANE4AhMUVfQqXUCU0UjUDxiOy/8XcKiW/dKhRR1DOQY24J/k+UWv
|
||||||
|
PEGVcBW4tgalYkTl/AW6hsNfubZaJuw05cHIKdL3df6ug7BUiJpmIv3sjrvPRYvA
|
||||||
|
WY1UTb3EJrswGz8S2l5+2S3WFTCfK7S6N6Stfi1x6rMJBuOss7HGqdh3AoGBAMbU
|
||||||
|
WavRqGRsvJFfE5bahXbFpkGWT++BTMP+lzK31z24JjmJdwO+ABWU4/xaXayA4skH
|
||||||
|
PrzlYUcGJWIedb6W4dvz0sA59yflQzYmREkQPE+wbyor003y7mB8LpFiCnfaFhRn
|
||||||
|
hoowkyIY+xM4UeDXWWt3DhBElgfA8fYZdiNJEhy5AoGBAMwYUw3BvMffu/CQPElL
|
||||||
|
dR6DzsUeXKxZ/2pGIGIXfb1uM1pHyFQOSj3ARgMqmYeKNn73zA7akzRsYYJeF7I9
|
||||||
|
OBT96q7+8IBuRdDx5gCYunHzHppf7HwUPEf+gYgpnY7lsu6ouZWNMNfiC/HOlJhN
|
||||||
|
QJLJHFnA0y+sEqhvhSxbnLypAoGBALHCZ+kVKFegX3YYaosUEv589obsu8qE7vzL
|
||||||
|
QKI3elfTq1kFbUILPEgPNUUIBXeUQy03LP/0k2PMOt/eG6apfoQHGQSCzlT8w3pF
|
||||||
|
/AbWXRVhyAEL7X5jEntwirGv1WwRrmvPopkplGGHs/EbCRjbbzaE2i3xI7EK70f2
|
||||||
|
u4gQbAEBAoGAVR4u8g5Tx2Gunzh7tfJJ5e3xGBGS3Yq+JqUVNI6t6KIAPh0rM+aD
|
||||||
|
9tDgcwn8Vn5YU7YkqA2T8OOFsbJfrfZ7y7+oeMFukuIyxgmy9n/V/tCIrV/lR7A5
|
||||||
|
3iYhanTUbQswx19pSRgsXi7fo9Fi/dmUwyHi18uz5FdLyCTsMbf3uA8=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDCzCCAfOgAwIBAgIJAPqJyUfmRxGLMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||||
|
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||||
|
MzQ0MjhaMBsxGTAXBgNVBAMMEHdlYmhvb2tfYXV0aHpfY2EwggEiMA0GCSqGSIb3
|
||||||
|
DQEBAQUAA4IBDwAwggEKAoIBAQCift1FXgGvXR0tx/zOCz+E4ei4Qu94Wvy25ZUg
|
||||||
|
WK/FJ2dpzaTp0ziJDa5hL2dNfE2HIkn9Nf8wcRhziBJeipRu3H2MTdUDiYIoOE7t
|
||||||
|
wm0JjGrRBX8+VAKIA+OfmdQy30AJ2KLv+5MUOXjpsr0rpqO8od8ehZ5homwXniTE
|
||||||
|
GrkgyP5BoHadz6ltrTRsYbT2xhwfIPftAf66DHrjH7bXExiPYsH+4+ipkzSSPUYc
|
||||||
|
/ECzwaoWrY+DCS/nz0CUeq3aIwJMCoQPaNyDgV5LavUN5gfAbIYA99ZN0DxFn6Bn
|
||||||
|
8/QaP3VP7gH2ytHsHJtQWN/UrERy89cG8/mvw7A6JiNLyHH/AgMBAAGjUDBOMB0G
|
||||||
|
A1UdDgQWBBS6IGeGHZCylibt0GzY0dP6C0J9VjAfBgNVHSMEGDAWgBS6IGeGHZCy
|
||||||
|
libt0GzY0dP6C0J9VjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAi
|
||||||
|
A1dp75kbePFZsUNjxN6B/Pv0vSoaOjQkc4hpxKbI4VRCuPGmMRFYTlKCzoZ53OqQ
|
||||||
|
2Jmu1Zbzel/bV5vXrW0BOfUpfWYzd/usIJEuTgU8ijBIB+IHAXYwwxeKRcz3C+7+
|
||||||
|
9RBMF7gSg9pU2hrSvjhh7Q96IMJ42Z7tI3WD8SZaQLjY1NW1jrQVsg66ktdMke7x
|
||||||
|
zC8oIRIBH4W6l5s7jtZx1k305NE04pigcFLxCxOmicKd66ysI5hAZkD7y0dgwgtL
|
||||||
|
IqCQy6t7uJDydRiNRfPFr9Eg7uOu83JGw11f3bGVhJVCbzHyKddvkQsQbdaMHRgZ
|
||||||
|
zgmWLORg+ls1H1oaJiNW
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/9bhfMr0LaEKng1lR
|
||||||
|
XopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv0EUnyx7WR3V21ObZ
|
||||||
|
iQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jNz+OM/VmqsgzoKLoa
|
||||||
|
bGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvhjqo8NRqzZLg79ldY
|
||||||
|
aKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJG8eZUmjQpUMv7Jk3
|
||||||
|
qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABAoIBAQCjzeFijwzKKL4w
|
||||||
|
0B1IBhi3WeReFPG4nkt1ssQPBYrrJPKBZgHO13A1STI78wFn/OdYpajfF8hI8HT1
|
||||||
|
BiGVsu27Eb9TC60b/x6OtmeCEk+044LRbtu+9NZUb7HHHogI0l++X0KXZ0coE38L
|
||||||
|
1izwNvfrmLa+QaIgHMtAg9EnJwJ993n4L31GovWh8MGmVyJX/F92y+agNwWkNYYp
|
||||||
|
iLWFyon+HbNVL13WOOYnYEdA8Me3+Gucy1EOfWMF7mgmuO2vcfnxXd6b16VjAwtE
|
||||||
|
jGCQfzgpWGHLpgwoBgDmnPUbdNPUT3MbA9jqG2mlnBSBQveYgKrmFdDYnAjnCM4L
|
||||||
|
uF2ztBzhAoGBAOYc3sF3YjpIIMsyH9omqtfOuxO+oZkpb2vB9kgdXCDcG870M+BC
|
||||||
|
bNzV7DCSV8QAUqjKQK1r3gq62UZMLXZbG8x5UnM8/EK0X1CSqygwSWjGpYxIQEhh
|
||||||
|
O2lq69WipkNDnX1ZmrvEdHD2cxqkkXZ7bdRKRasrFJgvJa3XbiJ18KYxAoGBAMpe
|
||||||
|
/72EcX9oL3KT8tJSpvasrw17p/XkMMCxTp3IDb3krF/4k5bYF61F68/LNSy3xkos
|
||||||
|
ZrPUK/U160iuHSYCpMq4pPmlWgKq4hmUMOt+8Yy622zDlugarq9VLqvSdGHm+r6F
|
||||||
|
5fHilXB0UsTXXOuLZWLcSQ0MBgiaVCLb2AmXZhhXAoGAEjSchw/r7JKCTbE0hezj
|
||||||
|
PVm0wVYmsNhvYUYiNwhjnpHrfU8iv45h0IL4QcuCOBaSc5o0zcOn+I9Z207xldiV
|
||||||
|
dXLvzAA6MQjWNai08+QGGs0EkfmxZEiVC70S1X8dylqSHjW1oT9kuv80khoNDCOt
|
||||||
|
x8rsgiNRaMzqHTvbEczk8jECgYB2Od+wSULBSw2FI5fVdcHjFGlEODycs44j1LH4
|
||||||
|
DZqxmHl3q9IVavMSIGouQCo1kLuAM8ZgQpDXtYNaN5YB0cOSRyLiUc5vBoQGq4OU
|
||||||
|
4Nme/L8aIH315TiuZ9ZXPSEO3REZ40G9+UCSrPJ52tOHLC2z/ruSqraPqhGDN+pT
|
||||||
|
WCamCwKBgEPa+kVrPs0khQH8+sbFbU9ifj4fhPAiSwj2fKuXFro2mE205vAMHye/
|
||||||
|
SYs/mPzYzKSd7F+7Zk6oVrgFVskTiReW3phF+cIl+CdcnIenF0jW1PVgGw8znu+P
|
||||||
|
SbHSdqV+tB7AW2J7sH8TZtfMUPAK2MJ4S+1uaHK86K79ym4Rz0E2
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC/zCCAeegAwIBAgIJAN7rkfhaX8FZMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||||
|
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||||
|
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfc2VydmVyMIIBIjANBgkq
|
||||||
|
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtegsP499au5ZxlwM26rk3TnRgakchQi/
|
||||||
|
9bhfMr0LaEKng1lRXopzzGuGeZQswzbx7iiH89JzFkurZoEmZwtS4Aybit92VOSv
|
||||||
|
0EUnyx7WR3V21ObZiQO0rr0UmG84NjdzATkqF+R5Z+HN9shwgBI4PR1j/ybCt7jN
|
||||||
|
z+OM/VmqsgzoKLoabGrx7LCTPk8y5G8AoPOrIAP+9WHJsKQSRT8Lru4lYqseBxvh
|
||||||
|
jqo8NRqzZLg79ldYaKFqa2N2zr5qp94sG3/zihNDxjZvyyn9c8qvPBL0xOyayvOJ
|
||||||
|
G8eZUmjQpUMv7Jk3qFmdMgGaDJRw0Qg6+/Zt6MHNs6Rbb8hmwuMSpwIDAQABo0Aw
|
||||||
|
PjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDATAP
|
||||||
|
BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCZHB9UCl2CfylWP3db
|
||||||
|
xUamawnRoTYlsOcUh4f2tlHMY+vYiEStN+LECk62YpeaHl/nz/lk7g1Jx9aua39z
|
||||||
|
wFIHiXYhwSWOtgmzpbxYLye1yajKXbbA1T7mEZJTjewDB9i1LcB9W3EV5VJ8Y1GY
|
||||||
|
AYKuKQ4Cb1HrqLsrw/1PDm0VouWzf2ESv8CBvAv/pYLVfwgS6WsUqn9wycpLEnqQ
|
||||||
|
RK66/AoiOaxUIjEP0O1q6pi6Mag7XAfeNtx8J0VGt4cRG4rvWCbKVUyvKfUCkipN
|
||||||
|
gJu09S+KIz3x1CJLRuJX9tB+cFnnykDLQ2IKg7x44O83ikNk8+Di3iT/awCguWPE
|
||||||
|
rHh5
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDjwQAlnHoLf/lz+Fh2
|
||||||
|
DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V9Ll3aY/6xVRCenWi
|
||||||
|
UNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL0q7vw003v5TBqzC/
|
||||||
|
FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqSfbBg6B/A3OwpbIfx
|
||||||
|
09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na+ZB+gMt4+e2Y7qNz
|
||||||
|
76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABAoIBAQCJpGzJSzC2W8DM
|
||||||
|
sMqBNdCUMKZ0cwq13b7W2BimGJKyCOOi3HxUZEaYf/2Leyt+PPBm72SML7dzvDh3
|
||||||
|
qa269gKVqmkSqa2vF763qQbRuYo14msTQzA7+s3TUMbZs2UaDOE6nZIzs1QdEElp
|
||||||
|
1DvYXHz+/rD7Adj9VF+mMnouqQoy5kgJTnVZ8sOyl/9R6F67xKBIvcrtPfqVZzuG
|
||||||
|
2hGAMUnawxFUajQC7BynIeCWrk79SUmQgilyNgRdY6+rGh2uRupIxuiAukPtuag1
|
||||||
|
Li+wnNl1UGECtv9ZnnboKvg2334k5vhYScGRJbwbr7Zt3ZaNd0Z/DE9kTtnhBS7v
|
||||||
|
9qWdc7CBAoGBAPR4hz1fhHFiPmMEAGuiNms6WdyIfyonIRYas8ZDKUQGdxn/aO8a
|
||||||
|
CURktHRlm6iYT+j1cbf3RnLEN9pNr3V2EySOMc+rXUNifcP7Vl53akAQmISUfQWG
|
||||||
|
UfwaNLicbavf6m9UCiwWByAZghqDZSLiwmLHIjGcSJQiFuhZryioDydxAoGBAPED
|
||||||
|
q1Z7oNhzwRYie9OB5ylnrCH8G3yFl8egBmQrPJKIQHA9mAGg01LEJwQNoWewyAWx
|
||||||
|
jfeFtWvIgZkj49cluZgHYyF81jApaNraxtXAgIwC1n7oAIttmeklZ/V1HntknG3Y
|
||||||
|
ow2bV/NA3aPOTPYxW8oDv7U9lvwve7kIFxeWjE/5AoGASfXI3G1wUSkqvKPySJ3b
|
||||||
|
ntcZZpm49xS9csWDS+D3tAfMsoXNxkB3O0TIP0qaLAhgbJcM314k5wWr7BSCl6Ow
|
||||||
|
KOgH887hOUirycXZHF0+PMGIktulcy1u0jlPZ+aTW2MztpiTN0E2yKRO8xx7VXGK
|
||||||
|
431hP+cLIh2qFoNDdaZaZ1ECgYEArw++PWQxMefqgVxs2vXJZY7TPiA0Ct+ynqKC
|
||||||
|
4fFx3vGu9JgYuF4MAVtPB6eq7HlA4LnWZ8ssOuz6DbU/AoB5bY84FxPpNDRv4D/3
|
||||||
|
Gz3nYUuSZ72234+tsuaju2vlxzUOVs97qB+E48Di/N+VkWHKzVKpxkjFScpnsL/K
|
||||||
|
niyRIGkCgYEAriuxbOCczL/j6u2Xq1ngEsGg+RXjtOYGoJWo7B8qlVL4nF8w1Nbd
|
||||||
|
FxEmOChQgUnBdwb93qHCSq0Fidf7OfewrfJJkstWIh3zPS4umLZo7R3YblncpdfT
|
||||||
|
M197uckIWccZml2jF/c7nvK+MjwDRhkOl2a6HzMxcdBwYUJmSwmIZ4k=
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC7jCCAdagAwIBAgIJAN7rkfhaX8FaMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV
|
||||||
|
BAMMEHdlYmhvb2tfYXV0aHpfY2EwIBcNMTYwMjE2MjM0NDI4WhgPMjI4OTEyMDEy
|
||||||
|
MzQ0MjhaMB8xHTAbBgNVBAMMFHdlYmhvb2tfYXV0aHpfY2xpZW50MIIBIjANBgkq
|
||||||
|
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5ij4WXWGvbmfAYhEafKRvLEHSkUCYIDj
|
||||||
|
wQAlnHoLf/lz+Fh2DEv4lcBaycwk3+LVUGKgYOg91txYJvGD3HcmVThXZvcgJd4V
|
||||||
|
9Ll3aY/6xVRCenWiUNgVQVQITGkMn09ZkSXbZCK4wqz9oTVh0Ti5a7apOS2V07yL
|
||||||
|
0q7vw003v5TBqzC/FgRwE0bv1rKYYQ80WbDlYkkYGf216zQTwS4g/nShCZAX9eqS
|
||||||
|
fbBg6B/A3OwpbIfx09BWuwWhp5QnS4w002gGWavRFNzu8pUHUv6zMN8OKpasv+Na
|
||||||
|
+ZB+gMt4+e2Y7qNz76QL23eGwc6oWn8lQBtkDLmLIa6jbWX067U76QIDAQABoy8w
|
||||||
|
LTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAKBggrBgEFBQcDAjAN
|
||||||
|
BgkqhkiG9w0BAQsFAAOCAQEA2IZNhkVrSTAIeP2N2WzOHqbFbGyO+NA8G9Hb5fiX
|
||||||
|
e1YS2Ku3ERYNr+HLxNHCsXiSUKjjBmXMc4z0XaHJznEKEbotZftjTlTQlHi3/5vm
|
||||||
|
dIG18pmO/E5ebVXl6pU96v/hBd8N5rWp9WUKgP0y59r/JA+oNpmd10A+RyaOyrFK
|
||||||
|
rBm8Z8rvDYMrXSpOwx9BNDuhqzbdG8MYw5vO55Er3hwTXoapsMqSh5s9+OFFpUJi
|
||||||
|
2uEoQlwWiYRtQj6g4wgr4woDEbv8XxsHqGfs+GSnmRsB69xRI24lEtC+nS6Rz3Sh
|
||||||
|
YWeN0gD8PsQC1KJVv6xCGo1yXSEwytRMB23XYtAZahLdLg==
|
||||||
|
-----END CERTIFICATE-----`)
|
102
plugin/pkg/auth/authorizer/webhook/gencerts.sh
Executable file
102
plugin/pkg/auth/authorizer/webhook/gencerts.sh
Executable file
@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
#
|
||||||
|
# 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 All rights reserved.
|
||||||
|
|
||||||
|
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
|
180
plugin/pkg/auth/authorizer/webhook/webhook.go
Normal file
180
plugin/pkg/auth/authorizer/webhook/webhook.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
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 (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
||||||
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/serializer/json"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime/serializer/versioning"
|
||||||
|
|
||||||
|
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
encodeVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||||
|
decodeVersions = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||||
|
|
||||||
|
requireEnabled = []unversioned.GroupVersion{v1beta1.SchemeGroupVersion}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure Webhook implements the authorizer.Authorizer interface.
|
||||||
|
var _ authorizer.Authorizer = (*WebhookAuthorizer)(nil)
|
||||||
|
|
||||||
|
type WebhookAuthorizer struct {
|
||||||
|
restClient *client.RESTClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) (*WebhookAuthorizer, error) {
|
||||||
|
|
||||||
|
for _, groupVersion := range requireEnabled {
|
||||||
|
if !registered.IsEnabledVersion(groupVersion) {
|
||||||
|
return nil, fmt.Errorf("webhook authz plugin requires enabling extension resource: %s", groupVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
loadingRules.ExplicitPath = kubeConfigFile
|
||||||
|
loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
|
||||||
|
|
||||||
|
clientConfig, err := loader.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serializer := json.NewSerializer(json.DefaultMetaFactory, api.Scheme, runtime.ObjectTyperToTyper(api.Scheme), false)
|
||||||
|
clientConfig.ContentConfig.Codec = versioning.NewCodecForScheme(api.Scheme, serializer, encodeVersions, decodeVersions)
|
||||||
|
|
||||||
|
restClient, err := client.UnversionedRESTClientFor(clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(ericchiang): Can we ensure remote service is reachable?
|
||||||
|
|
||||||
|
return &WebhookAuthorizer{restClient}, 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) (err error) {
|
||||||
|
r := &v1beta1.SubjectAccessReview{
|
||||||
|
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||||
|
User: attr.GetUserName(),
|
||||||
|
Groups: attr.GetGroups(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if attr.IsResourceRequest() {
|
||||||
|
r.Spec.ResourceAttributes = &v1beta1.ResourceAttributes{
|
||||||
|
Namespace: attr.GetNamespace(),
|
||||||
|
Verb: attr.GetVerb(),
|
||||||
|
Group: attr.GetAPIGroup(),
|
||||||
|
Resource: attr.GetResource(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.Spec.NonResourceAttributes = &v1beta1.NonResourceAttributes{
|
||||||
|
Path: attr.GetPath(),
|
||||||
|
Verb: attr.GetVerb(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result := w.restClient.Post().Body(r).Do()
|
||||||
|
if err := result.Error(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := result.Into(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r.Status.Allowed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if r.Status.Reason != "" {
|
||||||
|
return errors.New(r.Status.Reason)
|
||||||
|
}
|
||||||
|
return errors.New("unauthorized")
|
||||||
|
}
|
473
plugin/pkg/auth/authorizer/webhook/webhook_test.go
Normal file
473
plugin/pkg/auth/authorizer/webhook/webhook_test.go
Normal file
@ -0,0 +1,473 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package webhook
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/apis/authorization/v1beta1"
|
||||||
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
|
"k8s.io/kubernetes/pkg/auth/user"
|
||||||
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewFromConfig(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
CA string
|
||||||
|
Cert string
|
||||||
|
Key string
|
||||||
|
}{
|
||||||
|
CA: filepath.Join(dir, "ca.pem"),
|
||||||
|
Cert: filepath.Join(dir, "clientcert.pem"),
|
||||||
|
Key: filepath.Join(dir, "clientkey.pem"),
|
||||||
|
}
|
||||||
|
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}{
|
||||||
|
{data.CA, caCert},
|
||||||
|
{data.Cert, clientCert},
|
||||||
|
{data.Key, clientKey},
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
msg string
|
||||||
|
configTmpl string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
msg: "a single cluster and single user",
|
||||||
|
configTmpl: `
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: {{ .CA }}
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: foobar
|
||||||
|
users:
|
||||||
|
- name: a cluster
|
||||||
|
user:
|
||||||
|
client-certificate: {{ .Cert }}
|
||||||
|
client-key: {{ .Key }}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "multiple clusters with no context",
|
||||||
|
configTmpl: `
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: {{ .CA }}
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: foobar
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: a bad certificate path
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: barfoo
|
||||||
|
users:
|
||||||
|
- name: a name
|
||||||
|
user:
|
||||||
|
client-certificate: {{ .Cert }}
|
||||||
|
client-key: {{ .Key }}
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "multiple clusters with a context",
|
||||||
|
configTmpl: `
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: a bad certificate path
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: foobar
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: {{ .CA }}
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: barfoo
|
||||||
|
users:
|
||||||
|
- name: a name
|
||||||
|
user:
|
||||||
|
client-certificate: {{ .Cert }}
|
||||||
|
client-key: {{ .Key }}
|
||||||
|
contexts:
|
||||||
|
- name: default
|
||||||
|
context:
|
||||||
|
cluster: barfoo
|
||||||
|
user: a name
|
||||||
|
current-context: default
|
||||||
|
`,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "cluster with bad certificate path specified",
|
||||||
|
configTmpl: `
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: a bad certificate path
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: foobar
|
||||||
|
- cluster:
|
||||||
|
certificate-authority: {{ .CA }}
|
||||||
|
server: https://authz.example.com
|
||||||
|
name: barfoo
|
||||||
|
users:
|
||||||
|
- name: a name
|
||||||
|
user:
|
||||||
|
client-certificate: {{ .Cert }}
|
||||||
|
client-key: {{ .Key }}
|
||||||
|
contexts:
|
||||||
|
- name: default
|
||||||
|
context:
|
||||||
|
cluster: foobar
|
||||||
|
user: a name
|
||||||
|
current-context: default
|
||||||
|
`,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
// Use a closure so defer statements trigger between loop iterations.
|
||||||
|
err := func() error {
|
||||||
|
tempfile, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p := tempfile.Name()
|
||||||
|
defer os.Remove(p)
|
||||||
|
|
||||||
|
tmpl, err := template.New("test").Parse(tt.configTmpl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse test template: %v", err)
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(tempfile, data); err != nil {
|
||||||
|
return fmt.Errorf("failed to execute test template: %v", err)
|
||||||
|
}
|
||||||
|
// Create a new authorizer
|
||||||
|
_, err = New(p)
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
if err != nil && !tt.wantErr {
|
||||||
|
t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
|
||||||
|
}
|
||||||
|
if err == nil && tt.wantErr {
|
||||||
|
t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service mocks a remote service.
|
||||||
|
type Service interface {
|
||||||
|
Review(*v1beta1.SubjectAccessReview)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTestServer wraps a Service as an httptest.Server.
|
||||||
|
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||||
|
var tlsConfig *tls.Config
|
||||||
|
if cert != nil {
|
||||||
|
cert, err := tls.X509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if caCert != nil {
|
||||||
|
rootCAs := x509.NewCertPool()
|
||||||
|
rootCAs.AppendCertsFromPEM(caCert)
|
||||||
|
if tlsConfig == nil {
|
||||||
|
tlsConfig = &tls.Config{}
|
||||||
|
}
|
||||||
|
tlsConfig.ClientCAs = rootCAs
|
||||||
|
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||||
|
}
|
||||||
|
|
||||||
|
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var review v1beta1.SubjectAccessReview
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||||
|
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Review(&review)
|
||||||
|
type status struct {
|
||||||
|
Allowed bool `json:"allowed"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
resp := struct {
|
||||||
|
APIVersion string `json:"apiVersion"`
|
||||||
|
Status status `json:"status"`
|
||||||
|
}{
|
||||||
|
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||||
|
Status: status{review.Status.Allowed, review.Status.Reason},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||||
|
server.TLS = tlsConfig
|
||||||
|
server.StartTLS()
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A service that can be set to allow all or deny all authorization requests.
|
||||||
|
type mockService struct {
|
||||||
|
allow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockService) Review(r *v1beta1.SubjectAccessReview) {
|
||||||
|
r.Status.Allowed = m.allow
|
||||||
|
}
|
||||||
|
func (m *mockService) Allow() { m.allow = true }
|
||||||
|
func (m *mockService) Deny() { m.allow = false }
|
||||||
|
|
||||||
|
// newAuthorizer creates a temporary kubeconfig file from the provided arguments and attempts to load
|
||||||
|
// a new WebhookAuthorizer from it.
|
||||||
|
func newAuthorizer(callbackURL string, clientCert, clientKey, ca []byte) (*WebhookAuthorizer, error) {
|
||||||
|
tempfile, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p := tempfile.Name()
|
||||||
|
defer os.Remove(p)
|
||||||
|
config := v1.Config{
|
||||||
|
Clusters: []v1.NamedCluster{
|
||||||
|
{
|
||||||
|
Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AuthInfos: []v1.NamedAuthInfo{
|
||||||
|
{
|
||||||
|
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return New(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTLSConfig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
test string
|
||||||
|
clientCert, clientKey, clientCA []byte
|
||||||
|
serverCert, serverKey, serverCA []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
test: "TLS setup between client and server",
|
||||||
|
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||||
|
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: "Server does not require client auth",
|
||||||
|
clientCA: caCert,
|
||||||
|
serverCert: serverCert, serverKey: serverKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: "Server does not require client auth, client provides it",
|
||||||
|
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||||
|
serverCert: serverCert, serverKey: serverKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: "Client does not trust server",
|
||||||
|
clientCert: clientCert, clientKey: clientKey,
|
||||||
|
serverCert: serverCert, serverKey: serverKey,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: "Server does not trust client",
|
||||||
|
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||||
|
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Plugin does not support insecure configurations.
|
||||||
|
test: "Server is using insecure connection",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
// Use a closure so defer statements trigger between loop iterations.
|
||||||
|
func() {
|
||||||
|
service := new(mockService)
|
||||||
|
|
||||||
|
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
wh, err := newAuthorizer(server.URL, tt.clientCert, tt.clientKey, tt.clientCA)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
attr := authorizer.AttributesRecord{User: &user.DefaultInfo{}}
|
||||||
|
|
||||||
|
// Allow all and see if we get an error.
|
||||||
|
service.Allow()
|
||||||
|
err = wh.Authorize(attr)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected error making authorization request: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: failed to authorize with AllowAll policy: %v", tt.test, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
service.Deny()
|
||||||
|
if err := wh.Authorize(attr); err == nil {
|
||||||
|
t.Errorf("%s: incorrectly authorized with DenyAll policy", tt.test)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recorderService records all access review requests.
|
||||||
|
type recorderService struct {
|
||||||
|
last v1beta1.SubjectAccessReview
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rec *recorderService) Review(r *v1beta1.SubjectAccessReview) {
|
||||||
|
rec.last = v1beta1.SubjectAccessReview{}
|
||||||
|
rec.last = *r
|
||||||
|
r.Status.Allowed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rec *recorderService) Last() (v1beta1.SubjectAccessReview, error) {
|
||||||
|
return rec.last, rec.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWebhook(t *testing.T) {
|
||||||
|
serv := new(recorderService)
|
||||||
|
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
wh, err := newAuthorizer(s.URL, clientCert, clientKey, caCert)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expTypeMeta := unversioned.TypeMeta{
|
||||||
|
APIVersion: "authorization.k8s.io/v1beta1",
|
||||||
|
Kind: "SubjectAccessReview",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
attr authorizer.Attributes
|
||||||
|
want v1beta1.SubjectAccessReview
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
attr: authorizer.AttributesRecord{User: &user.DefaultInfo{}},
|
||||||
|
want: v1beta1.SubjectAccessReview{
|
||||||
|
TypeMeta: expTypeMeta,
|
||||||
|
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||||
|
NonResourceAttributes: &v1beta1.NonResourceAttributes{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attr: authorizer.AttributesRecord{User: &user.DefaultInfo{Name: "jane"}},
|
||||||
|
want: v1beta1.SubjectAccessReview{
|
||||||
|
TypeMeta: expTypeMeta,
|
||||||
|
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||||
|
User: "jane",
|
||||||
|
NonResourceAttributes: &v1beta1.NonResourceAttributes{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attr: authorizer.AttributesRecord{
|
||||||
|
User: &user.DefaultInfo{
|
||||||
|
Name: "jane",
|
||||||
|
UID: "1",
|
||||||
|
Groups: []string{"group1", "group2"},
|
||||||
|
},
|
||||||
|
Verb: "GET",
|
||||||
|
Namespace: "kittensandponies",
|
||||||
|
APIGroup: "group3",
|
||||||
|
Resource: "pods",
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "/foo",
|
||||||
|
},
|
||||||
|
want: v1beta1.SubjectAccessReview{
|
||||||
|
TypeMeta: expTypeMeta,
|
||||||
|
Spec: v1beta1.SubjectAccessReviewSpec{
|
||||||
|
User: "jane",
|
||||||
|
Groups: []string{"group1", "group2"},
|
||||||
|
ResourceAttributes: &v1beta1.ResourceAttributes{
|
||||||
|
Verb: "GET",
|
||||||
|
Namespace: "kittensandponies",
|
||||||
|
Group: "group3",
|
||||||
|
Resource: "pods",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
if err := wh.Authorize(tt.attr); err != nil {
|
||||||
|
t.Errorf("case %d: authorization failed: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gotAttr, err := serv.Last()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("case %d: failed to deserialize webhook request: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotAttr, tt.want) {
|
||||||
|
t.Errorf("case %d: got != want:\n%s", i, util.ObjectGoPrintDiff(gotAttr, tt.want))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user