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

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