forked from barak/tarpoon
Add glide.yaml and vendor deps
This commit is contained in:
parent
db918f12ad
commit
5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions
2
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
2
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
assignees:
|
||||
- derekwaynecarr
|
||||
29
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [],
|
||||
)
|
||||
48
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
48
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admit
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("AlwaysAdmit", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysAdmit(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysAdmit is an implementation of admission.Interface which always says yes to an admit request.
|
||||
// It is useful in tests and when using kubernetes in an open manner.
|
||||
type alwaysAdmit struct{}
|
||||
|
||||
func (alwaysAdmit) Admit(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (alwaysAdmit) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysAdmit creates a new always admit admission handler
|
||||
func NewAlwaysAdmit() admission.Interface {
|
||||
return new(alwaysAdmit)
|
||||
}
|
||||
29
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.Admit(nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
75
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
75
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package alwayspullimages contains an admission controller that modifies every new Pod to force
|
||||
// the image pull policy to Always. This is useful in a multitenant cluster so that users can be
|
||||
// assured that their private images can only be used by those who have the credentials to pull
|
||||
// them. Without this admission controller, once an image has been pulled to a node, any pod from
|
||||
// any user can use it simply by knowing the image's name (assuming the Pod is scheduled onto the
|
||||
// right node), without any authorization check against the image. With this admission controller
|
||||
// enabled, images are always pulled prior to starting containers, which means valid credentials are
|
||||
// required.
|
||||
package alwayspullimages
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("AlwaysPullImages", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysPullImages(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysPullImages is an implementation of admission.Interface.
|
||||
// It looks at all new pods and overrides each container's image pull policy to Always.
|
||||
type alwaysPullImages struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
func (a *alwaysPullImages) Admit(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
pod.Spec.InitContainers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
pod.Spec.Containers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewAlwaysPullImages creates a new always pull images admission control handler
|
||||
func NewAlwaysPullImages() admission.Interface {
|
||||
return &alwaysPullImages{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
129
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
129
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package alwayspullimages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// TestAdmission verifies all create requests for pods result in every container's image pull policy
|
||||
// set to Always
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
handler := &alwaysPullImages{}
|
||||
pod := api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init1", Image: "image"},
|
||||
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "exec",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &alwaysPullImages{}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if e, a := api.PullNever, pod.Spec.Containers[0].ImagePullPolicy; e != a {
|
||||
t.Errorf("%s: image pull policy was changed to %s", tc.name, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
41
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
83
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
83
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
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 antiaffinity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("LimitPodHardAntiAffinityTopology", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewInterPodAntiAffinity(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// plugin contains the client used by the admission controller
|
||||
type plugin struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
}
|
||||
|
||||
// NewInterPodAntiAffinity creates a new instance of the LimitPodHardAntiAffinityTopology admission controller
|
||||
func NewInterPodAntiAffinity(client clientset.Interface) admission.Interface {
|
||||
return &plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// Admit will deny any pod that defines AntiAffinity topology key other than metav1.LabelHostname i.e. "kubernetes.io/hostname"
|
||||
// in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution.
|
||||
func (p *plugin) Admit(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
affinity, err := api.GetAffinityFromPodAnnotations(pod.Annotations)
|
||||
if err != nil {
|
||||
glog.V(5).Infof("Invalid Affinity detected, but we will leave handling of this to validation phase")
|
||||
return nil
|
||||
}
|
||||
if affinity != nil && affinity.PodAntiAffinity != nil {
|
||||
var podAntiAffinityTerms []api.PodAffinityTerm
|
||||
if len(affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 {
|
||||
podAntiAffinityTerms = affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||
}
|
||||
// TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution.
|
||||
//if len(affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution) != 0 {
|
||||
// podAntiAffinityTerms = append(podAntiAffinityTerms, affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution...)
|
||||
//}
|
||||
for _, v := range podAntiAffinityTerms {
|
||||
if v.TopologyKey != metav1.LabelHostname {
|
||||
return apierrors.NewForbidden(attributes.GetResource().GroupResource(), pod.Name, fmt.Errorf("affinity.PodAntiAffinity.RequiredDuringScheduling has TopologyKey %v but only key %v is allowed", v.TopologyKey, metav1.LabelHostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
299
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
299
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
/*
|
||||
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 antiaffinity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// ensures the hard PodAntiAffinity is denied if it defines TopologyKey other than kubernetes.io/hostname.
|
||||
func TestInterPodAffinityAdmission(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity(nil)
|
||||
pod := api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
tests := []struct {
|
||||
affinity map[string]string
|
||||
errorExpected bool
|
||||
}{
|
||||
// empty affinity its success.
|
||||
{
|
||||
affinity: map[string]string{},
|
||||
errorExpected: false,
|
||||
},
|
||||
// what ever topologyKey in preferredDuringSchedulingIgnoredDuringExecution, the admission should success.
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 5,
|
||||
"podAffinityTerm": {
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": [],
|
||||
"topologyKey": "az"
|
||||
}
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution,
|
||||
// plus any topologyKey in preferredDuringSchedulingIgnoredDuringExecution, then admission success.
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"preferredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"weight": 5,
|
||||
"podAffinityTerm": {
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": [],
|
||||
"topologyKey": "az"
|
||||
}
|
||||
}],
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": [],
|
||||
"topologyKey": "` + metav1.LabelHostname + `"
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission success.
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces":[],
|
||||
"topologyKey": "` + metav1.LabelHostname + `"
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// invalid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission fails.
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces":[],
|
||||
"topologyKey": " zone "
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
// invalid topologyKey in requiredDuringSchedulingRequiredDuringExecution then admission fails.
|
||||
// TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution.
|
||||
// {
|
||||
// affinity: map[string]string{
|
||||
// api.AffinityAnnotationKey: `
|
||||
// {"podAntiAffinity": {
|
||||
// "requiredDuringSchedulingRequiredDuringExecution": [{
|
||||
// "labelSelector": {
|
||||
// "matchExpressions": [{
|
||||
// "key": "security",
|
||||
// "operator": "In",
|
||||
// "values":["S2"]
|
||||
// }]
|
||||
// },
|
||||
// "namespaces":[],
|
||||
// "topologyKey": " zone "
|
||||
// }]
|
||||
// }}`,
|
||||
// },
|
||||
// errorExpected: true,
|
||||
// }
|
||||
// list of requiredDuringSchedulingIgnoredDuringExecution middle element topologyKey is not valid.
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"requiredDuringSchedulingIgnoredDuringExecution": [{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces":[],
|
||||
"topologyKey": "` + metav1.LabelHostname + `"
|
||||
},
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces":[],
|
||||
"topologyKey": " zone "
|
||||
},
|
||||
{
|
||||
"labelSelector": {
|
||||
"matchExpressions": [{
|
||||
"key": "security",
|
||||
"operator": "In",
|
||||
"values":["S2"]
|
||||
}]
|
||||
},
|
||||
"namespaces": [],
|
||||
"topologyKey": "` + metav1.LabelHostname + `"
|
||||
}]
|
||||
}}`,
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
{
|
||||
affinity: map[string]string{
|
||||
api.AffinityAnnotationKey: `
|
||||
{"podAntiAffinity": {
|
||||
"thisIsAInvalidAffinity": [{}
|
||||
}}`,
|
||||
},
|
||||
// however, we should not get error here
|
||||
errorExpected: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
pod.ObjectMeta.Annotations = test.affinity
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
|
||||
if test.errorExpected && err == nil {
|
||||
t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity)
|
||||
}
|
||||
|
||||
if !test.errorExpected && err != nil {
|
||||
t.Errorf("Unexpected error %v for AntiAffinity %+v", err, test.affinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity(nil)
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: name, Namespace: namespace},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "eviction",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &plugin{}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// LimitPodHardAntiAffinityTopology admission controller rejects any pod
|
||||
// that specifies "hard" (RequiredDuringScheduling) anti-affinity
|
||||
// with a TopologyKey other than metav1.LabelHostname.
|
||||
// Because anti-affinity is symmetric, without this admission controller,
|
||||
// a user could maliciously or accidentally specify that their pod (once it has scheduled)
|
||||
// should block other pods from scheduling into the same zone or some other large topology,
|
||||
// essentially DoSing the cluster.
|
||||
// In the future we will address this problem more fully by using quota and priority,
|
||||
// but for now this admission controller provides a simple protection,
|
||||
// on the assumption that the only legitimate use of hard pod anti-affinity
|
||||
// is to exclude other pods from the same node.
|
||||
package antiaffinity // import "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
||||
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
],
|
||||
)
|
||||
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deny
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("AlwaysDeny", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysDeny(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysDeny is an implementation of admission.Interface which always says no to an admission request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
type alwaysDeny struct{}
|
||||
|
||||
func (alwaysDeny) Admit(a admission.Attributes) (err error) {
|
||||
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
func (alwaysDeny) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysDeny creates an always deny admission handler
|
||||
func NewAlwaysDeny() admission.Interface {
|
||||
return new(alwaysDeny)
|
||||
}
|
||||
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "namespace", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
39
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/rest:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/rest:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
128
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
128
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("DenyEscalatingExec", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyEscalatingExec(client), nil
|
||||
})
|
||||
|
||||
// This is for legacy support of the DenyExecOnPrivileged admission controller. Most
|
||||
// of the time DenyEscalatingExec should be preferred.
|
||||
admission.RegisterPlugin("DenyExecOnPrivileged", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyExecOnPrivileged(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// denyExec is an implementation of admission.Interface which says no to a pod/exec on
|
||||
// a pod using host based configurations.
|
||||
type denyExec struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
|
||||
// these flags control which items will be checked to deny exec/attach
|
||||
hostIPC bool
|
||||
hostPID bool
|
||||
privileged bool
|
||||
}
|
||||
|
||||
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
|
||||
// using host based configurations.
|
||||
func NewDenyEscalatingExec(client clientset.Interface) admission.Interface {
|
||||
return &denyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
client: client,
|
||||
hostIPC: true,
|
||||
hostPID: true,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged
|
||||
// option. This is for legacy support of the DenyExecOnPrivileged admission controller. Most
|
||||
// of the time NewDenyEscalatingExec should be preferred.
|
||||
func NewDenyExecOnPrivileged(client clientset.Interface) admission.Interface {
|
||||
return &denyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
client: client,
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *denyExec) Admit(a admission.Attributes) (err error) {
|
||||
connectRequest, ok := a.GetObject().(*rest.ConnectRequest)
|
||||
if !ok {
|
||||
return errors.NewBadRequest("a connect request was received, but could not convert the request object.")
|
||||
}
|
||||
// Only handle exec or attach requests on pods
|
||||
if connectRequest.ResourcePath != "pods/exec" && connectRequest.ResourcePath != "pods/attach" {
|
||||
return nil
|
||||
}
|
||||
pod, err := d.client.Core().Pods(a.GetNamespace()).Get(connectRequest.Name)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
if d.hostPID && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
|
||||
}
|
||||
|
||||
if d.hostIPC && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
|
||||
}
|
||||
|
||||
if d.privileged && isPrivileged(pod) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a privileged container"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrivileged will return true a pod has any privileged containers
|
||||
func isPrivileged(pod *api.Pod) bool {
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.SecurityContext == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.SecurityContext == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
218
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
218
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: false,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: false,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// use the same code as NewDenyEscalatingExec, using the direct object though to allow testAdmission to
|
||||
// inject the client
|
||||
handler := &denyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: true,
|
||||
hostPID: true,
|
||||
privileged: true,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler.privileged = false
|
||||
handler.hostPID = false
|
||||
handler.hostIPC = false
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
|
||||
// run against an init container
|
||||
handler = &denyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: true,
|
||||
hostPID: true,
|
||||
privileged: true,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler.privileged = false
|
||||
handler.hostPID = false
|
||||
handler.hostIPC = false
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
}
|
||||
|
||||
func testAdmission(t *testing.T, pod *api.Pod, handler *denyExec, shouldAccept bool) {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.(core.GetAction).GetName() == pod.Name {
|
||||
return true, pod, nil
|
||||
}
|
||||
t.Errorf("Unexpected API call: %#v", action)
|
||||
return true, nil, nil
|
||||
})
|
||||
|
||||
handler.client = mockClient
|
||||
|
||||
// pods/exec
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"}
|
||||
err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
|
||||
// pods/attach
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"}
|
||||
err := handler.Admit(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test to ensure legacy admission controller works as expected.
|
||||
func TestDenyExecOnPrivileged(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: true,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: true,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// use the same code as NewDenyExecOnPrivileged, using the direct object though to allow testAdmission to
|
||||
// inject the client
|
||||
handler := &denyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: true,
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// test init containers
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
}
|
||||
|
||||
func validPod(name string) *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["gc_admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/meta:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["gc_admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
],
|
||||
)
|
||||
112
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
112
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
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 gc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/meta"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("OwnerReferencesPermissionEnforcement", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// gcPermissionsEnforcement is an implementation of admission.Interface.
|
||||
type gcPermissionsEnforcement struct {
|
||||
*admission.Handler
|
||||
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) {
|
||||
// if we aren't changing owner references, then the edit is always allowed
|
||||
if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleteAttributes := authorizer.AttributesRecord{
|
||||
User: attributes.GetUserInfo(),
|
||||
Verb: "delete",
|
||||
Namespace: attributes.GetNamespace(),
|
||||
APIGroup: attributes.GetResource().Group,
|
||||
APIVersion: attributes.GetResource().Version,
|
||||
Resource: attributes.GetResource().Resource,
|
||||
Subresource: attributes.GetSubresource(),
|
||||
Name: attributes.GetName(),
|
||||
ResourceRequest: true,
|
||||
Path: "",
|
||||
}
|
||||
allowed, reason, err := a.authorizer.Authorize(deleteAttributes)
|
||||
if allowed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
|
||||
}
|
||||
|
||||
func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
|
||||
newMeta, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
if oldObj == nil {
|
||||
return len(newMeta.GetOwnerReferences()) > 0
|
||||
}
|
||||
oldMeta, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
// compare the old and new. If they aren't the same, then we're trying to change an ownerRef
|
||||
oldOwners := oldMeta.GetOwnerReferences()
|
||||
newOwners := newMeta.GetOwnerReferences()
|
||||
if len(oldOwners) != len(newOwners) {
|
||||
return true
|
||||
}
|
||||
for i := range oldOwners {
|
||||
if !api.Semantic.DeepEqual(oldOwners[i], newOwners[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
a.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) Validate() error {
|
||||
if a.authorizer == nil {
|
||||
return fmt.Errorf("missing authorizer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
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 gc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type fakeAuthorizer struct{}
|
||||
|
||||
func (fakeAuthorizer) Authorize(a authorizer.Attributes) (bool, string, error) {
|
||||
username := a.GetUser().GetName()
|
||||
|
||||
if username == "non-deleter" {
|
||||
if a.GetVerb() == "delete" {
|
||||
return false, "", nil
|
||||
}
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
if username == "non-pod-deleter" {
|
||||
if a.GetVerb() == "delete" && a.GetResource() == "pods" {
|
||||
return false, "", nil
|
||||
}
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
func TestGCAdmission(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
resource schema.GroupVersionResource
|
||||
oldObj runtime.Object
|
||||
newObj runtime.Object
|
||||
|
||||
expectedAllowed bool
|
||||
}{
|
||||
{
|
||||
name: "super-user, create, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
|
||||
{
|
||||
name: "super-user, update, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, no objectref change two",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}, {Name: "second"}}}},
|
||||
expectedAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: false,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: api.ObjectMeta{OwnerReferences: []api.OwnerReference{{Name: "first"}}}},
|
||||
expectedAllowed: true,
|
||||
},
|
||||
}
|
||||
gcAdmit := &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
authorizer: fakeAuthorizer{},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
operation := admission.Create
|
||||
if tc.oldObj != nil {
|
||||
operation = admission.Update
|
||||
}
|
||||
user := &user.DefaultInfo{Name: tc.username}
|
||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, api.NamespaceDefault, "foo", tc.resource, "", operation, user)
|
||||
|
||||
err := gcAdmit.Admit(attributes)
|
||||
switch {
|
||||
case err != nil && !tc.expectedAllowed:
|
||||
case err != nil && tc.expectedAllowed:
|
||||
t.Errorf("%v: unexpected err: %v", tc.name, err)
|
||||
case err == nil && !tc.expectedAllowed:
|
||||
t.Errorf("%v: missing err", tc.name)
|
||||
case err == nil && tc.expectedAllowed:
|
||||
}
|
||||
}
|
||||
}
|
||||
54
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/apis/imagepolicy/v1alpha1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/util/cache:go_default_library",
|
||||
"//pkg/util/yaml:go_default_library",
|
||||
"//plugin/pkg/webhook:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"certs_test.go",
|
||||
"config_test.go",
|
||||
],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//pkg/apis/imagepolicy/v1alpha1:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
239
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
239
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/*
|
||||
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 imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/apis/imagepolicy/v1alpha1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/util/yaml"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/util/cache"
|
||||
"k8s.io/kubernetes/plugin/pkg/webhook"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{v1alpha1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("ImagePolicyWebhook", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
newImagePolicyWebhook, err := NewImagePolicyWebhook(client, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImagePolicyWebhook, nil
|
||||
})
|
||||
}
|
||||
|
||||
// imagePolicyWebhook is an implementation of admission.Interface.
|
||||
type imagePolicyWebhook struct {
|
||||
*admission.Handler
|
||||
webhook *webhook.GenericWebhook
|
||||
responseCache *cache.LRUExpireCache
|
||||
allowTTL time.Duration
|
||||
denyTTL time.Duration
|
||||
retryBackoff time.Duration
|
||||
defaultAllow bool
|
||||
}
|
||||
|
||||
func (a *imagePolicyWebhook) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration {
|
||||
if status.Allowed {
|
||||
return a.allowTTL
|
||||
}
|
||||
return a.denyTTL
|
||||
}
|
||||
|
||||
// Filter out annotations that don't match *.image-policy.k8s.io/*
|
||||
func (a *imagePolicyWebhook) filterAnnotations(allAnnotations map[string]string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range allAnnotations {
|
||||
if strings.Contains(k, ".image-policy.k8s.io/") {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// Function to call on webhook failure; behavior determined by defaultAllow flag
|
||||
func (a *imagePolicyWebhook) webhookError(attributes admission.Attributes, err error) error {
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error contacting webhook backend: %s", err)
|
||||
if a.defaultAllow {
|
||||
glog.V(2).Infof("resource allowed in spite of webhook backend failure")
|
||||
return nil
|
||||
}
|
||||
glog.V(2).Infof("resource not allowed due to webhook backend failure ")
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *imagePolicyWebhook) Admit(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
allowedResources := map[schema.GroupResource]bool{
|
||||
api.Resource("pods"): true,
|
||||
}
|
||||
|
||||
if len(attributes.GetSubresource()) != 0 || !allowedResources[attributes.GetResource().GroupResource()] {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
// Build list of ImageReviewContainerSpec
|
||||
var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec
|
||||
containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||
containers = append(containers, pod.Spec.Containers...)
|
||||
containers = append(containers, pod.Spec.InitContainers...)
|
||||
for _, c := range containers {
|
||||
imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{
|
||||
Image: c.Image,
|
||||
})
|
||||
}
|
||||
imageReview := v1alpha1.ImageReview{
|
||||
Spec: v1alpha1.ImageReviewSpec{
|
||||
Containers: imageReviewContainerSpecs,
|
||||
Annotations: a.filterAnnotations(pod.Annotations),
|
||||
Namespace: attributes.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if err := a.admitPod(attributes, &imageReview); err != nil {
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *imagePolicyWebhook) admitPod(attributes admission.Attributes, review *v1alpha1.ImageReview) error {
|
||||
cacheKey, err := json.Marshal(review.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry, ok := a.responseCache.Get(string(cacheKey)); ok {
|
||||
review.Status = entry.(v1alpha1.ImageReviewStatus)
|
||||
} else {
|
||||
result := a.webhook.WithExponentialBackoff(func() restclient.Result {
|
||||
return a.webhook.RestClient.Post().Body(review).Do()
|
||||
})
|
||||
|
||||
if err := result.Error(); err != nil {
|
||||
return a.webhookError(attributes, err)
|
||||
}
|
||||
var statusCode int
|
||||
if result.StatusCode(&statusCode); statusCode < 200 || statusCode >= 300 {
|
||||
return a.webhookError(attributes, fmt.Errorf("Error contacting webhook: %d", statusCode))
|
||||
}
|
||||
|
||||
if err := result.Into(review); err != nil {
|
||||
return a.webhookError(attributes, err)
|
||||
}
|
||||
|
||||
a.responseCache.Add(string(cacheKey), review.Status, a.statusTTL(review.Status))
|
||||
}
|
||||
|
||||
if !review.Status.Allowed {
|
||||
if len(review.Status.Reason) > 0 {
|
||||
return fmt.Errorf("image policy webook backend denied one or more images: %s", review.Status.Reason)
|
||||
}
|
||||
return errors.New("one or more images rejected by webhook backend")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImagePolicyWebhook a new imagePolicyWebhook from the provided config file.
|
||||
// The config file is specified by --admission-controller-config-file and has the
|
||||
// following format for a webhook:
|
||||
//
|
||||
// {
|
||||
// "imagePolicy": {
|
||||
// "kubeConfigFile": "path/to/kubeconfig/for/backend",
|
||||
// "allowTTL": 30, # time in s to cache approval
|
||||
// "denyTTL": 30, # time in s to cache denial
|
||||
// "retryBackoff": 500, # time in ms to wait between retries
|
||||
// "defaultAllow": true # determines behavior if the webhook backend fails
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The config file may be json or yaml.
|
||||
//
|
||||
// The kubeconfig property refers to another file in the kubeconfig format which
|
||||
// specifies how to connect to the webhook backend.
|
||||
//
|
||||
// The kubeconfig'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-imagepolicy-service
|
||||
// cluster:
|
||||
// certificate-authority: /path/to/ca.pem # CA for verifying the remote service.
|
||||
// server: https://images.example.com/policy # 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 NewImagePolicyWebhook(client clientset.Interface, configFile io.Reader) (admission.Interface, error) {
|
||||
var config AdmissionConfig
|
||||
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
|
||||
err := d.Decode(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whConfig := config.ImagePolicyWebhook
|
||||
if err := normalizeWebhookConfig(&whConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gw, err := webhook.NewGenericWebhook(whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &imagePolicyWebhook{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
webhook: gw,
|
||||
responseCache: cache.NewLRUExpireCache(1024),
|
||||
allowTTL: whConfig.AllowTTL,
|
||||
denyTTL: whConfig.DenyTTL,
|
||||
defaultAllow: whConfig.DefaultAllow,
|
||||
}, nil
|
||||
}
|
||||
946
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
946
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the imagepolicy webhook tests.
|
||||
|
||||
package imagepolicy
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAoKjaP9PtRAGRNCx8z+0LTGt2eEduqElcPrm8EvlBwn3dnLFo
|
||||
55x+Tejb6ysQsyy1BKI0dRdX4tNSAgFFFaIVcsOo9kGtPq7QsSd4VWViNE3L5zJA
|
||||
+0X2ztHBkPlQXwDrtArsNKxwcpyHP9sXE05BN36XBjAz2XkusTkFrdJ/PzjZhlb4
|
||||
9i9gTZ0bJbexQ1+dfZX2WpY70JypYnKrbV1dLj5ORb65SC8IWZcG/ouqLWAN+lT+
|
||||
eug8P6PjoOQWs3qsl0bSAtAdiYcwXKtPiBEWPJe24ACywyE+8jVzmIJqAm0U1V8k
|
||||
GTHzjmSRwzgX/VN5JMri/nxNIW5UsbhHzYHfjQIDAQABAoIBAQCIeAWz1Bwl+ULT
|
||||
U7rNkChZyKrAbsUDdBVEPtcQMuR2Bh5Z/KUEoHz1RwiP0WwFFsPI5NO0ZpjD1wdB
|
||||
Jrz9LEoVyzfZvl4f8bTZ1pIzz8PEdBTxFVH3Xy3P7oMC15Q6rviIXgLYl2WJJYcJ
|
||||
adxHDOD+96vnmMhiQbq01aAKT9TA6PvXXDusfadMQ+il+mEbeZz4aNYBk9u+34Co
|
||||
aQTNwlLft5anW2820IMJdJR/bFjyX71cPID1rIjw4VOQZExIpIEnuHPiulyE4EvJ
|
||||
hvvVKAm0dRjHg39cz0eAQ6PntX3DUvjNfcLLrj7sQxLco1cnAKZxhpZ8ajtvynr5
|
||||
pF2d5xYBAoGBAM8y/e5+raHTLHEKZUc0vekUey3fc4aRqptyAKTS0ZvOYBXg4Vhl
|
||||
mOK7066IEqwF4UHGmQqW6D5HstqPGx0uN0d9IyImUqDp0JotdFSZMEMQkYLyFD+r
|
||||
J7O2nOO6E4SOxXO9/q9iSB+G/qgl6LS3O9+58uHTYEbUommiDZ6a18qBAoGBAMZ/
|
||||
xSGMa3b6vrU3rUTEh+xBh6YRVNYAxWwpGg2sO0k2brT3SxSMCrx1wvNGY+k7XNx0
|
||||
JJfZQDC/wlR0rcVTnPCi/cE9FTUlh23xXCPRlxwc4vLly+7yU95LhAO+N9XAwsrs
|
||||
OIi4lR57jxoLNO2ofoAVMvllkE5Eo5W6lOPR2xcNAoGAV1Tv0OFV//pJJhAypfOm
|
||||
BCLc1HX1dIfbOA+yE8bEEH7I4w/ZC3AvI4n1a//wls8Xpai2gs8ebnm7+gENdZww
|
||||
MpKdB1zNwQMsKH/2I146CFpoap/sRvW2EzpqIFYiueGPefxf575uFdPJbEgmMF13
|
||||
ABKZO/PjBZfEKO/j+7DaOYECgYBYX+Zqa1QlIrnpgKJZ7Y3+d6ZnH2w/4xQCdcIt
|
||||
uDKlA+ECHN+GhFr7UQq8uOgenNlZJTRtjsHvclCYvWHoarOCx25mrEVW5iCHqF+3
|
||||
asb2Mz4vmnPTLHx+iex6piPBvRJ8ufLpnBR3/9bUZ4znCo9XgxiwxLEcx551OR60
|
||||
12fNuQKBgC1fkqgtDDxQzrabSmmiqXthcPXxFdsYqnSNlFgba0uaAp9LREztSrX8
|
||||
QhwSoSwHVmjBvR6SybLYdsZ9Efj/w7XBejOOcS44MOoHYYFdsP7W47Ao5QFqvDoI
|
||||
oqyQ1R73cF9WX6obRQwH4P3DvcsBebOjvjMX9mljKtpJMc9KqrGc
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJAJlL10mfdZraMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgqNo/0+1EAZE0LHzP7QtM
|
||||
a3Z4R26oSVw+ubwS+UHCfd2csWjnnH5N6NvrKxCzLLUEojR1F1fi01ICAUUVohVy
|
||||
w6j2Qa0+rtCxJ3hVZWI0TcvnMkD7RfbO0cGQ+VBfAOu0Cuw0rHBynIc/2xcTTkE3
|
||||
fpcGMDPZeS6xOQWt0n8/ONmGVvj2L2BNnRslt7FDX519lfZaljvQnKlicqttXV0u
|
||||
Pk5FvrlILwhZlwb+i6otYA36VP566Dw/o+Og5BazeqyXRtIC0B2JhzBcq0+IERY8
|
||||
l7bgALLDIT7yNXOYgmoCbRTVXyQZMfOOZJHDOBf9U3kkyuL+fE0hblSxuEfNgd+N
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBSx2m5pJoFpdGDmOzSVl29jkheQFTAfBgNVHSME
|
||||
GDAWgBSx2m5pJoFpdGDmOzSVl29jkheQFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBe6tZzmOQKt8fTsnDDKvEjSwK2Pb91R5tkwmIhdpTjmAgC+Zkk
|
||||
kSihR9sZIxdRC4wlbuorRl8BjhX5I8Kr3FWdDhOrIhicp7CIrxPiFh6+ZLSOj3o9
|
||||
pQ6SriIopjXCHvl5XjzKxLg/uQpzui/YUtfqffCRB4EccOsjlyUanK5rjMLBMLCn
|
||||
2LadiRB2Q/cC9fYigczETACDjq5vzp6I9eqwpCTmv/+4bFncW+VBD4touaJc8FKf
|
||||
ljW5xekKRh4uzP85X7rEgrFen/my5Fs/cylkFvYIiZwgn6NLgW3BNi+m31XIfU0S
|
||||
xIbgh4UH0dwc6Zk8WUwFud4GXj6OyGneMGKB
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAnKpC89q4/H+Xg91xI+GLhkrpJrO4n3nw0+/EQUoF9qwLtEDk
|
||||
mJp6ymUulwJgfvJwHOsUYqQB6jMKfXyqeSR24ssjjF9LTKhaQMZOGcW5Mshi04Ie
|
||||
USX93wDZwbWwqihVSqWaMpmf3JByeldnXNtc29Ik6NwqZcNWW5kEsSszheLhOU4i
|
||||
ZcRUlovwMYhHX37vQCQ1aygMaIMgBOb/vogSNxumqPKS4WdWsjss6LmEPnm350e+
|
||||
+9cb6RAfrDlOaj32VbLEp500SfBpZeCuyc5v81X12HT4V0qmsqIZ79tIgrqAaPxE
|
||||
D/HJXPpH64EAr23bR9dMPLXTh6w2yYWu+NGYywIDAQABAoIBAQCE/CZPN1gVxf0I
|
||||
i12x9o/oVAhruN08Sld6oCm4viwnws1AmmExhNg8m/0bZIIi4Ir4kThBrzSM5/y8
|
||||
nqlaofBk/cjULEQP80yBdZPwXp2hlOYG4on3mkdRGDjALQmktw4HimFFGJDRuq/i
|
||||
V/U+plrBojWAkPtQXKsen9qSxbg7qhI6KZyUQKExIHhsCfmE1ZzGx+/bgLVJEagi
|
||||
7zzZdAj2BzdoCk8yySAAsZG+pNSnd8gs5EzzRJ1RXanwxPSeEG/guX9YhLgLhhFu
|
||||
XzXngJDKVVhz4F2TfxtqIvZYvTMNh0R1OE0OUO2P88M837KKk5BHvW9oqYKZTUFV
|
||||
MC9k5No5AoGBAMtUBp8UcYZy+yetOAK2iGaEYwuWx8vwjY0c1POWun2Hny0nYxTQ
|
||||
WxXXqKaJydxZ+DlD3XuRKmMlKZQsp+bzuL5ukWN/ipO5tgQQfuKOZqVwvL19GkFi
|
||||
+Qr70G/TvYT/rv6A4s6XqbG4xt+7c2gf/XSghyoIyq1uwOcNNtrMdM/tAoGBAMU/
|
||||
tYc4d+vAl7hd8TwhFiZiC3N84C1HwsPVj38uqQI/j8boB21Bhpw6HHzq+VdVPfvp
|
||||
zk5e8AiQdSpitM7pBVmLpoRdTQjdlUDFRUi4TdJwfp5P7dXM8D6swNQ9f9w180na
|
||||
5ewu16PSC+sh19wAl04KwOmiDqZujJrBgWnFcESXAoGBALGofoybAUK3zqlxWcJN
|
||||
GUtyG1Sx72tLiXMmIQ+hwNsUGEoM4y75isy//ZVeSammVxQ6Lxjb00yD2RumFSLg
|
||||
C6kg1Ro6A6xmFRriCuwL/rZJljB/UeSWBQLK2eoL+clu2sl3djWLIPOvft1YXVM6
|
||||
uGwiI1fgDK+TWSvJSQfOo7ZVAoGBAK+A6DvQeqNBUb2xmJsvtU2hnx661Zx0ZU9q
|
||||
DavUEHz3oS4R9cm4q9UFv6NGT2Tta6FhfzcsMdbs8dMs0EPqAeCS6S6M9aYVwl9H
|
||||
J0Z09olvnrmt1KiPGJQrkcdGkSWWu0nTgxCK/UO9+OzVyALwY7AE0XEPyIk9g82O
|
||||
r181VZcxAoGANY2QGYrNtfa++o2B0O4qskKxhYEeCnZPptmjVO0oHOx2YSDQXK3K
|
||||
B0evCQ7ylvMnobNLjp9bqD14a0M86QjRlpSg1vHUhBsETZICc+E0UgV28CdWgYtt
|
||||
urARDE9ZpLVSRfPVAitC1I76pZwevsbQ9TeS2p0cWQpYYKmBtGpkdug=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJANQEJyMW4HFZMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcqkLz2rj8f5eD3XEj4YuG
|
||||
Sukms7ifefDT78RBSgX2rAu0QOSYmnrKZS6XAmB+8nAc6xRipAHqMwp9fKp5JHbi
|
||||
yyOMX0tMqFpAxk4ZxbkyyGLTgh5RJf3fANnBtbCqKFVKpZoymZ/ckHJ6V2dc21zb
|
||||
0iTo3Cplw1ZbmQSxKzOF4uE5TiJlxFSWi/AxiEdffu9AJDVrKAxogyAE5v++iBI3
|
||||
G6ao8pLhZ1ayOyzouYQ+ebfnR7771xvpEB+sOU5qPfZVssSnnTRJ8Gll4K7Jzm/z
|
||||
VfXYdPhXSqayohnv20iCuoBo/EQP8clc+kfrgQCvbdtH10w8tdOHrDbJha740ZjL
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBRjFVG818hHK+HSEhdz+gPwSKa4kzAfBgNVHSME
|
||||
GDAWgBRjFVG818hHK+HSEhdz+gPwSKa4kzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBBCl0UJq0iLy/dvym79mnoPZ1KPhS2WnQB5ZLzJwL26ePkr8j8
|
||||
G/1AOVPu73hovJx51b+T7ZhTgtmAEwqpRHBxRQ0+Yf973YOVJYp4QFGWDnueurzv
|
||||
bCsnZEPkQtccHzZxT3fUsM6Ejy99j0WBNmvfAj1X7yNaN5EZw6kvuaDDda3I7WNM
|
||||
0eGy8aoAcPJZkYfZb39VDq/qJn+bVsAJdUaXt/FkDZBJl6XzoGjC/webjRJOpkgN
|
||||
vgjJDhhQ8LlHFiq+lXIiK4Y55RBWG3iXGTM8W3fjZYTNvH7FlGyuRD4Y4hyaYXTP
|
||||
+PoFWuDZM89EAyICr0yyTc8mkdrAEM/Lj9GO
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAyhmjG7BJCGwuf1FyHJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTH
|
||||
riPOe9d1ahH7bvsZycnzh7/pABdTDUdStiR8/1KUYt8PjjosrmmYyupqNPq+wkBD
|
||||
EmKa+4voR2EBgXbIGghx8e++KmmNnSCNk6B8m2EJR0fn9zPnoY3uHNogKjCICt19
|
||||
g+uipuwZco7yTu3e40LwpIVmA8SsrM0S/CaZqSmtIClSwv7YDvreUd6FuI/GT0cj
|
||||
NMPRuSdfohBxGz6R7Cml7qP4AYKajjl+08mRYv3o+hVclXUltcRmTnJanYGmGS3k
|
||||
C7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS0PhjmQIDAQABAoIBAFOicmZ1+HM82a0k
|
||||
llWSV5xPUzUmU6TT4bJlZnzJd0R7i+6H8250MPH9AwEHOgb+cPiZ02cdGx5HiL4Z
|
||||
AviPdw7uLKwR5U0VdAIlfu6SPat5DNI0Z81G8x4gEtrfIRFjh4GGdykI7qh8j/cz
|
||||
ToOGSaq/aGiQMEWTvEqWArD7742lVHE4/1bM3GuKV8shy31zfw0d9RCCy1GdBR75
|
||||
zZ1w4zKL55DM3PC73Ndy2IcrViVXVAgfqD0xxKwQW1qoENgThueALj3PkU1XaKxI
|
||||
nOdztt1fBFpcSHyFBkJ1sexumnssMRXSVcJ/0D5F2T4QPUnWBM0oSzoyioAab4RP
|
||||
8XrZwAECgYEA/eFjNgCeHztXgS3YRC/RddLOtobrerYKN7vA64ou5VUCqEQ9rfQE
|
||||
MbmKdZdiFVNJI0JrPq8Gx39ME9g2OLTVVqdtlm6JYjy5CHdUXHIHObo9oz7Uueos
|
||||
TdeCf0LFvEUNXvbGIP5KqcdVi+wekauHMqXGQYTNa6bar/FE99MdyAECgYEAy8mU
|
||||
tCjm4QsuKsdku5bDHGv56ZN9DkWd7Lcjie5otElwH9bKfIQ2lUYyoUAIa0rEJ9Ya
|
||||
7vuAZ2bX7od9s8Jkci91ONDWxdy361SRZcbpuqgQKKVRuzGlfamufyW4sStbXY1k
|
||||
+zeQxyWGJHhhLWpapzca89RELGZSkbIMVVIT25kCgYEA7EUYboZuoYQ5cGf476RM
|
||||
28kfRXEUrvPBWJLr/IhyEk1mFrDDciM40AnrWHpU9qG23BCQ/BopRforFADQnT91
|
||||
l5pje29NfdYjIUTkhtA79zZi7IyprofHSX453TOIECl3QxyH0Oa3F4ACFiDdZhXq
|
||||
0XDDq+/quLfkp37y/2xDOAECgYEAmi55g5UumTWMSHFzlToLhIVtH3unMhUZ1u74
|
||||
xHLMZRrq6ivoJy0g3u+tfrKjrAl1P26OEiHWlGULGj0Ireh1dq7RUZsv46OKw1HI
|
||||
b+h/Den5z8bEf4ygWOL4UtqHUgQrrCw+KpNvxjxtsUoiu+mrjLf0fGYs7iq8bd73
|
||||
1dWzkIECgYEAi6P/LzMC6orbyONmwlscqO1Ili8ZBkUjJ/wThkiNMMA3pyKmb68W
|
||||
yt56Yh0rs+WnuVUN90cG87k+CY35dQ7FAOVUJi9LWGA3Oq9fGkoOB7f4dzaUu/rB
|
||||
dtit2KPCxiKpZsxqSf4+S8AXYF48abNPLYK3DCCSqAah09gYOrqYlW4=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAMvo2rkGpEUQMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfc2Vy
|
||||
dmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyhmjG7BJCGwuf1Fy
|
||||
HJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTHriPOe9d1ahH7bvsZycnzh7/pABdTDUdS
|
||||
tiR8/1KUYt8PjjosrmmYyupqNPq+wkBDEmKa+4voR2EBgXbIGghx8e++KmmNnSCN
|
||||
k6B8m2EJR0fn9zPnoY3uHNogKjCICt19g+uipuwZco7yTu3e40LwpIVmA8SsrM0S
|
||||
/CaZqSmtIClSwv7YDvreUd6FuI/GT0cjNMPRuSdfohBxGz6R7Cml7qP4AYKajjl+
|
||||
08mRYv3o+hVclXUltcRmTnJanYGmGS3kC7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS
|
||||
0PhjmQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCF
|
||||
xaS/KIijKDLbaL/P7AxhnAta8jYSEzL66WTaYV4GeRhLtX/vPUV9gzPWnkNr0TBM
|
||||
lS+Q0KDxh17rJ/MrWwrMSwsgKZahTR+7mSHiXrIlHcnHXXSvhnoXu8VDu8goqOEI
|
||||
5yRHt6plzmFZEwVi/hSmIAuQjmyjOk2dc/ZKI0fMExKhnVms8AoztjAMbt3TFMTK
|
||||
Kk7bVGPblFsXiVPhRlzbLbh5i/PvHHf+12ACrVxoxOOQUmuXy1DPxmkk7jP3FIsE
|
||||
+rnyWnfmGS5sW8oMkj2nFYIh3LehADsMS9s7JVlJk/loNJDA9Yn2fev/vRKck8RZ
|
||||
siw54G4e+6nKpY5BAY1M
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3IOqCz88jTQpsGIBFTdjbqBg+0NFeym3OEl8zLfzkLQuZieO
|
||||
3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWIY8KU7c2SPfErlhP86VFoD0RKHJxwRVh0
|
||||
y70WyK8+CzzwrrPpWydgtAwbm9F+0v/zdcCL0TEL2/MYgCc97mSGwtTRaW4bqq6V
|
||||
MWMHBcOu44dHq8+CF8ixxk0WSBl2oocXnF7QdEA15iuOM5hacLB0fyH4T3NM54lO
|
||||
rOSXUMUuysougSrMcCPv3esFlv4TVUkldwu73jWx+Wja0gNXlnmgU2lqFdM+PsVT
|
||||
DPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkNcx9QDQIDAQABAoIBADblRCC2pmFUmghB
|
||||
7ZkVh9hTbrE+Zv6pPOZzTPE93hGo+WAO+v6GNBLuIEte87DhF2QTmovp4VfsFeXK
|
||||
oECNgTvOEFkBP+OFqFGBJZGfY3/J5h0tTy4lLZXaImzzx8sGGNLLc8R+uyTIO3VV
|
||||
qIso2uXB+vzPgMrueflt5yp7hoJjI0c+qEktUg5n+WJFAFteI9LCngN+xwRWVEgp
|
||||
rjKVPcT9zio8tLJOhcSPA7q6lORUkwbPWHyNDpamvldnqjhgp5Ceq5f/qfoWPzvM
|
||||
H5o72Ax2WduxST+P+hCOqZReUmTaGzAKb5rJwdEpmbnDZ3kSR08aT/40m/EG1SvQ
|
||||
pi0b3QECgYEA/mRGIjaYPQr+tw3Sz8g76t3PYfrglro60HdLBn2IUpj2sEpazNId
|
||||
2aPFPb58whL+VPmUfXbpPH+wW/+wWpRw4MraFkJanbOjDiEGXK5ZoUQIDZJWUSwf
|
||||
oCge5uacU69weC67UyPYmK1e+A/gaFw1Dz729jLxtB3rGWKxEGbWEc0CgYEA3eiP
|
||||
hv0GxbdEEbSfQoSPKbBHGI9spaqAIcqL+dSsx3m6Ckqx0El/xi9mQkITgqs2gyqI
|
||||
o2T/3yDli9oF4+3Plz0wrZ11auOWX+nhKfACtF679I1PL0UOavXF0FVgOfwOIqdG
|
||||
jp4QQV7USkbTP9ZOHo90Y8G4rmTEdMZ/VsH490ECgYEA8u/bsiyk8haf7Tx8SAWW
|
||||
gtLUi2NEO20ZYZ+qvEYBe6+sVeqMD/HQo9ksMazKA6ST0Z6O2cpHLolaaGEjjz0X
|
||||
FvVhk8RGOTglzQZoxvWRjtojPqKzX81dXlsyN5ufSqPOKlemeN1QqW1XtlmjGsaD
|
||||
vU2KFs/L1xCDRbjkEx/B6zkCgYBmqeE9InKvpknnpxjHPWy+bL93rWMmgesltv9r
|
||||
ZelJoBdiC4yYQGjM18EHhmpgWbWumU79yQxXvnB0czmmaa9Q2Q5cRCy+duxrE1kI
|
||||
ffHCYNG0ImwwAlLZSTtrVxRdvy8K+Ti7YoVCuQyeEIZLUmpx2QyP2mAGzrfVDsB6
|
||||
8uKsAQKBgQDO+PmADra91NKJP1iVuvOK8iEy/Z14L03uKtF3X9u8vLdzQZa/Q/P9
|
||||
hXOX9ovFwSBQOOfgb+/+QRuPL4xxi1J8CFwrSWCEeFgrDijl9DS6aNY6BWHDA8p6
|
||||
8V7Adb04cnenj8QjYYN8/mqsQlHSoAIxeAlUoJpq+pk7O8PAfbjgMw==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC+jCCAeKgAwIBAgIJAMvo2rkGpEURMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2xp
|
||||
ZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IOqCz88jTQpsGIB
|
||||
FTdjbqBg+0NFeym3OEl8zLfzkLQuZieO3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWI
|
||||
Y8KU7c2SPfErlhP86VFoD0RKHJxwRVh0y70WyK8+CzzwrrPpWydgtAwbm9F+0v/z
|
||||
dcCL0TEL2/MYgCc97mSGwtTRaW4bqq6VMWMHBcOu44dHq8+CF8ixxk0WSBl2oocX
|
||||
nF7QdEA15iuOM5hacLB0fyH4T3NM54lOrOSXUMUuysougSrMcCPv3esFlv4TVUkl
|
||||
dwu73jWx+Wja0gNXlnmgU2lqFdM+PsVTDPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkN
|
||||
cx9QDQIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkHIhrPfRROhzLg2hRZz5/7Kw
|
||||
3V0/Y0XS91YU3rew+c2k++bLp1INzpWxfB6gbSC6bTOgn/seIDvxwJ2g5DRdOxU/
|
||||
Elcpqg1hTCVfpmra9PCniMzZuP7lsz8sJKj6FgE6ElJ1S74FW/CYz/jA+76LLot4
|
||||
JwGkCJHzyLgFPBEOjJ/mLYSM/SDzHU5E+NHXVaKz4MjM3JwycN/juqi4ikAcZEBW
|
||||
1HmpcHKBedAwlCM90zlvG2SL4sFRp/clMbntRdmh5L+/1F6aP82PO3iuvXtXP48d
|
||||
NtjboxP3IV2eY5iUle8BOQ9CnFQs4wsF1LxTMNACypQyFinMsHrCpwrB3i4VvA==
|
||||
-----END CERTIFICATE-----`)
|
||||
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
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 imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryBackoff = time.Duration(500) * time.Millisecond
|
||||
minRetryBackoff = time.Duration(1)
|
||||
maxRetryBackoff = time.Duration(5) * time.Minute
|
||||
defaultAllowTTL = time.Duration(5) * time.Minute
|
||||
defaultDenyTTL = time.Duration(30) * time.Second
|
||||
minAllowTTL = time.Duration(1) * time.Second
|
||||
maxAllowTTL = time.Duration(30) * time.Minute
|
||||
minDenyTTL = time.Duration(1) * time.Second
|
||||
maxDenyTTL = time.Duration(30) * time.Minute
|
||||
useDefault = time.Duration(0) //sentinel for using default TTL
|
||||
disableTTL = time.Duration(-1) //sentinel for disabling a TTL
|
||||
)
|
||||
|
||||
// imagePolicyWebhookConfig holds config data for imagePolicyWebhook
|
||||
type imagePolicyWebhookConfig struct {
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
AllowTTL time.Duration `json:"allowTTL"`
|
||||
DenyTTL time.Duration `json:"denyTTL"`
|
||||
RetryBackoff time.Duration `json:"retryBackoff"`
|
||||
DefaultAllow bool `json:"defaultAllow"`
|
||||
}
|
||||
|
||||
// AdmissionConfig holds config data for admission controllers
|
||||
type AdmissionConfig struct {
|
||||
ImagePolicyWebhook imagePolicyWebhookConfig `json:"imagePolicy"`
|
||||
}
|
||||
|
||||
func normalizeWebhookConfig(config *imagePolicyWebhookConfig) (err error) {
|
||||
config.RetryBackoff, err = normalizeConfigDuration("backoff", time.Millisecond, config.RetryBackoff, minRetryBackoff, maxRetryBackoff, defaultRetryBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.AllowTTL, err = normalizeConfigDuration("allow cache", time.Second, config.AllowTTL, minAllowTTL, maxAllowTTL, defaultAllowTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DenyTTL, err = normalizeConfigDuration("deny cache", time.Second, config.DenyTTL, minDenyTTL, maxDenyTTL, defaultDenyTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeConfigDuration(name string, scale, value, min, max, defaultValue time.Duration) (time.Duration, error) {
|
||||
// disable with -1 sentinel
|
||||
if value == disableTTL {
|
||||
glog.V(2).Infof("image policy webhook %s disabled", name)
|
||||
return time.Duration(0), nil
|
||||
}
|
||||
|
||||
// use defualt with 0 sentinel
|
||||
if value == useDefault {
|
||||
glog.V(2).Infof("image policy webhook %s using default value", name)
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
// convert to s; unmarshalling gives ns
|
||||
value *= scale
|
||||
|
||||
// check value is within range
|
||||
if value <= min || value > max {
|
||||
return value, fmt.Errorf("valid value is between %v and %v, got %v", min, max, value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
105
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
105
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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 imagepolicy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfigNormalization(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
config imagePolicyWebhookConfig
|
||||
normalizedConfig imagePolicyWebhookConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "config within normal ranges",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second,
|
||||
RetryBackoff: ((minRetryBackoff + maxRetryBackoff) / 2) / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second * time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second * time.Second,
|
||||
RetryBackoff: (minRetryBackoff + maxRetryBackoff) / 2,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config below normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL - time.Duration(1),
|
||||
DenyTTL: minDenyTTL - time.Duration(1),
|
||||
RetryBackoff: minRetryBackoff - time.Duration(1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config above normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(1) + maxAllowTTL,
|
||||
DenyTTL: time.Duration(1) + maxDenyTTL,
|
||||
RetryBackoff: time.Duration(1) + maxRetryBackoff,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config wants default values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: useDefault,
|
||||
DenyTTL: useDefault,
|
||||
RetryBackoff: useDefault,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: defaultAllowTTL,
|
||||
DenyTTL: defaultDenyTTL,
|
||||
RetryBackoff: defaultRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config wants disabled values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: disableTTL,
|
||||
DenyTTL: disableTTL,
|
||||
RetryBackoff: disableTTL,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(0),
|
||||
DenyTTL: time.Duration(0),
|
||||
RetryBackoff: time.Duration(0),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := normalizeWebhookConfig(&tt.config)
|
||||
if err == nil && tt.wantErr == true {
|
||||
t.Errorf("%s: expected error from normalization and didn't have one", tt.test)
|
||||
}
|
||||
if err != nil && tt.wantErr == false {
|
||||
t.Errorf("%s: unexpected error from normalization: %v", tt.test, err)
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(tt.config, tt.normalizedConfig) {
|
||||
t.Errorf("%s: expected config to be normalized. got: %v expected: %v", tt.test, tt.config, tt.normalizedConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal 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 imagepolicy checks a webhook for image admission
|
||||
package imagepolicy // import "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
||||
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 The Kubernetes Authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook authz plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=webhook_imagepolicy_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_imagepolicy_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook_imagepolicy_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_imagepolicy_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 imagepolicy webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package imagepolicy" >> $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
|
||||
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"data_source.go",
|
||||
"gcm.go",
|
||||
"hawkular.go",
|
||||
"influxdb.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd:go_default_library",
|
||||
"//vendor:cloud.google.com/go/compute/metadata",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/hawkular/hawkular-client-go/metrics",
|
||||
"//vendor:github.com/influxdata/influxdb/client",
|
||||
"//vendor:golang.org/x/oauth2",
|
||||
"//vendor:golang.org/x/oauth2/google",
|
||||
"//vendor:google.golang.org/api/cloudmonitoring/v2beta2",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"hawkular_test.go",
|
||||
],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/require",
|
||||
],
|
||||
)
|
||||
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission.go
generated
vendored
Normal file
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
)
|
||||
|
||||
var (
|
||||
source = flag.String("ir-data-source", "influxdb", "Data source used by InitialResources. Supported options: influxdb, gcm.")
|
||||
percentile = flag.Int64("ir-percentile", 90, "Which percentile of samples should InitialResources use when estimating resources. For experiment purposes.")
|
||||
nsOnly = flag.Bool("ir-namespace-only", false, "Whether the estimation should be made only based on data from the same namespace.")
|
||||
)
|
||||
|
||||
const (
|
||||
initialResourcesAnnotation = "kubernetes.io/initial-resources"
|
||||
samplesThreshold = 30
|
||||
week = 7 * 24 * time.Hour
|
||||
month = 30 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// WARNING: this feature is experimental and will definitely change.
|
||||
func init() {
|
||||
admission.RegisterPlugin("InitialResources", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
s, err := newDataSource(*source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newInitialResources(s, *percentile, *nsOnly), nil
|
||||
})
|
||||
}
|
||||
|
||||
type initialResources struct {
|
||||
*admission.Handler
|
||||
source dataSource
|
||||
percentile int64
|
||||
nsOnly bool
|
||||
}
|
||||
|
||||
func newInitialResources(source dataSource, percentile int64, nsOnly bool) admission.Interface {
|
||||
return &initialResources{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
source: source,
|
||||
percentile: percentile,
|
||||
nsOnly: nsOnly,
|
||||
}
|
||||
}
|
||||
|
||||
func (ir initialResources) Admit(a admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
ir.estimateAndFillResourcesIfNotSet(pod)
|
||||
return nil
|
||||
}
|
||||
|
||||
// The method veryfies whether resources should be set for the given pod and
|
||||
// if there is estimation available the method fills Request field.
|
||||
func (ir initialResources) estimateAndFillResourcesIfNotSet(pod *api.Pod) {
|
||||
var annotations []string
|
||||
for i := range pod.Spec.InitContainers {
|
||||
annotations = append(annotations, ir.estimateContainer(pod, &pod.Spec.InitContainers[i], "init container")...)
|
||||
}
|
||||
for i := range pod.Spec.Containers {
|
||||
annotations = append(annotations, ir.estimateContainer(pod, &pod.Spec.Containers[i], "container")...)
|
||||
}
|
||||
if len(annotations) > 0 {
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
val := "Initial Resources plugin set: " + strings.Join(annotations, "; ")
|
||||
pod.ObjectMeta.Annotations[initialResourcesAnnotation] = val
|
||||
}
|
||||
}
|
||||
|
||||
func (ir initialResources) estimateContainer(pod *api.Pod, c *api.Container, message string) []string {
|
||||
var annotations []string
|
||||
req := c.Resources.Requests
|
||||
lim := c.Resources.Limits
|
||||
var cpu, mem *resource.Quantity
|
||||
var err error
|
||||
if _, ok := req[api.ResourceCPU]; !ok {
|
||||
if _, ok2 := lim[api.ResourceCPU]; !ok2 {
|
||||
cpu, err = ir.getEstimation(api.ResourceCPU, c, pod.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
glog.Errorf("Error while trying to estimate resources: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := req[api.ResourceMemory]; !ok {
|
||||
if _, ok2 := lim[api.ResourceMemory]; !ok2 {
|
||||
mem, err = ir.getEstimation(api.ResourceMemory, c, pod.ObjectMeta.Namespace)
|
||||
if err != nil {
|
||||
glog.Errorf("Error while trying to estimate resources: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If Requests doesn't exits and an estimation was made, create Requests.
|
||||
if req == nil && (cpu != nil || mem != nil) {
|
||||
c.Resources.Requests = api.ResourceList{}
|
||||
req = c.Resources.Requests
|
||||
}
|
||||
setRes := []string{}
|
||||
if cpu != nil {
|
||||
glog.Infof("CPU estimation for %s %v in pod %v/%v is %v", message, c.Name, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, cpu.String())
|
||||
setRes = append(setRes, string(api.ResourceCPU))
|
||||
req[api.ResourceCPU] = *cpu
|
||||
}
|
||||
if mem != nil {
|
||||
glog.Infof("Memory estimation for %s %v in pod %v/%v is %v", message, c.Name, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, mem.String())
|
||||
setRes = append(setRes, string(api.ResourceMemory))
|
||||
req[api.ResourceMemory] = *mem
|
||||
}
|
||||
if len(setRes) > 0 {
|
||||
sort.Strings(setRes)
|
||||
a := strings.Join(setRes, ", ") + fmt.Sprintf(" request for %s %s", message, c.Name)
|
||||
annotations = append(annotations, a)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
func (ir initialResources) getEstimation(kind api.ResourceName, c *api.Container, ns string) (*resource.Quantity, error) {
|
||||
end := time.Now()
|
||||
start := end.Add(-week)
|
||||
var usage, samples int64
|
||||
var err error
|
||||
|
||||
// Historical data from last 7 days for the same image:tag within the same namespace.
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, ns, true, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if samples < samplesThreshold {
|
||||
// Historical data from last 30 days for the same image:tag within the same namespace.
|
||||
start := end.Add(-month)
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, ns, true, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If we are allowed to estimate only based on data from the same namespace.
|
||||
if ir.nsOnly {
|
||||
if samples < samplesThreshold {
|
||||
// Historical data from last 30 days for the same image within the same namespace.
|
||||
start := end.Add(-month)
|
||||
image := strings.Split(c.Image, ":")[0]
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, image, ns, false, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if samples < samplesThreshold {
|
||||
// Historical data from last 7 days for the same image:tag within all namespaces.
|
||||
start := end.Add(-week)
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, "", true, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if samples < samplesThreshold {
|
||||
// Historical data from last 30 days for the same image:tag within all namespaces.
|
||||
start := end.Add(-month)
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, "", true, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if samples < samplesThreshold {
|
||||
// Historical data from last 30 days for the same image within all namespaces.
|
||||
start := end.Add(-month)
|
||||
image := strings.Split(c.Image, ":")[0]
|
||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, image, "", false, start, end); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if samples > 0 && kind == api.ResourceCPU {
|
||||
return resource.NewMilliQuantity(usage, resource.DecimalSI), nil
|
||||
}
|
||||
if samples > 0 && kind == api.ResourceMemory {
|
||||
return resource.NewQuantity(usage, resource.DecimalSI), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
258
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission_test.go
generated
vendored
Normal file
258
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
)
|
||||
|
||||
type fakeSource struct {
|
||||
f func(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error)
|
||||
}
|
||||
|
||||
func (s *fakeSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (usage int64, samples int64, err error) {
|
||||
return s.f(kind, perc, image, namespace, exactMatch, start, end)
|
||||
}
|
||||
|
||||
func parseReq(cpu, mem string) api.ResourceList {
|
||||
if cpu == "" && mem == "" {
|
||||
return nil
|
||||
}
|
||||
req := api.ResourceList{}
|
||||
if cpu != "" {
|
||||
req[api.ResourceCPU] = resource.MustParse(cpu)
|
||||
}
|
||||
if mem != "" {
|
||||
req[api.ResourceMemory] = resource.MustParse(mem)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func addContainer(pod *api.Pod, name, image string, request api.ResourceList) {
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
|
||||
Name: name,
|
||||
Image: image,
|
||||
Resources: api.ResourceRequirements{Requests: request},
|
||||
})
|
||||
}
|
||||
|
||||
func createPod(name string, image string, request api.ResourceList) *api.Pod {
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: name, Namespace: "test-ns"},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
pod.Spec.Containers = []api.Container{}
|
||||
addContainer(pod, "i0", image, request)
|
||||
pod.Spec.InitContainers = pod.Spec.Containers
|
||||
pod.Spec.Containers = []api.Container{}
|
||||
addContainer(pod, "c0", image, request)
|
||||
return pod
|
||||
}
|
||||
|
||||
func getPods() []*api.Pod {
|
||||
return []*api.Pod{
|
||||
createPod("p0", "image:v0", parseReq("", "")),
|
||||
createPod("p1", "image:v1", parseReq("", "300")),
|
||||
createPod("p2", "image:v2", parseReq("300m", "")),
|
||||
createPod("p3", "image:v3", parseReq("300m", "300")),
|
||||
}
|
||||
}
|
||||
|
||||
func verifyContainer(t *testing.T, c *api.Container, cpu, mem int64) {
|
||||
req := c.Resources.Requests
|
||||
if req.Cpu().MilliValue() != cpu {
|
||||
t.Errorf("Wrong CPU request for container %v. Expected %v, got %v.", c.Name, cpu, req.Cpu().MilliValue())
|
||||
}
|
||||
if req.Memory().Value() != mem {
|
||||
t.Errorf("Wrong memory request for container %v. Expected %v, got %v.", c.Name, mem, req.Memory().Value())
|
||||
}
|
||||
}
|
||||
|
||||
func verifyPod(t *testing.T, pod *api.Pod, cpu, mem int64) {
|
||||
verifyContainer(t, &pod.Spec.Containers[0], cpu, mem)
|
||||
verifyContainer(t, &pod.Spec.InitContainers[0], cpu, mem)
|
||||
}
|
||||
|
||||
func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
|
||||
a, ok := pod.ObjectMeta.Annotations[initialResourcesAnnotation]
|
||||
if !ok {
|
||||
t.Errorf("No annotation but expected %v", expected)
|
||||
}
|
||||
if a != expected {
|
||||
t.Errorf("Wrong annotation set by Initial Resources: got %v, expected %v", a, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoAnnotation(t *testing.T, pod *api.Pod) {
|
||||
if a, ok := pod.ObjectMeta.Annotations[initialResourcesAnnotation]; ok {
|
||||
t.Errorf("Expected no annotation but got %v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) {
|
||||
for i := range pods {
|
||||
p := pods[i]
|
||||
if err := ir.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "test", p.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performTest(t *testing.T, ir admission.Interface) {
|
||||
pods := getPods()
|
||||
admit(t, ir, pods)
|
||||
|
||||
verifyPod(t, pods[0], 100, 100)
|
||||
verifyPod(t, pods[1], 100, 300)
|
||||
verifyPod(t, pods[2], 300, 100)
|
||||
verifyPod(t, pods[3], 300, 300)
|
||||
|
||||
verifyAnnotation(t, pods[0], "Initial Resources plugin set: cpu, memory request for init container i0; cpu, memory request for container c0")
|
||||
verifyAnnotation(t, pods[1], "Initial Resources plugin set: cpu request for init container i0")
|
||||
verifyAnnotation(t, pods[2], "Initial Resources plugin set: memory request for init container i0")
|
||||
expectNoAnnotation(t, pods[3])
|
||||
}
|
||||
|
||||
func TestEstimationBasedOnTheSameImageSameNamespace7d(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
if exactMatch && end.Sub(start) == week && ns == "test-ns" {
|
||||
return 100, 120, nil
|
||||
}
|
||||
return 200, 120, nil
|
||||
}
|
||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
||||
|
||||
}
|
||||
|
||||
func TestEstimationBasedOnTheSameImageSameNamespace30d(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
if exactMatch && end.Sub(start) == week && ns == "test-ns" {
|
||||
return 200, 20, nil
|
||||
}
|
||||
if exactMatch && end.Sub(start) == month && ns == "test-ns" {
|
||||
return 100, 120, nil
|
||||
}
|
||||
return 200, 120, nil
|
||||
}
|
||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
||||
}
|
||||
|
||||
func TestEstimationBasedOnTheSameImageAllNamespaces7d(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
if exactMatch && ns == "test-ns" {
|
||||
return 200, 20, nil
|
||||
}
|
||||
if exactMatch && end.Sub(start) == week && ns == "" {
|
||||
return 100, 120, nil
|
||||
}
|
||||
return 200, 120, nil
|
||||
}
|
||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
||||
}
|
||||
|
||||
func TestEstimationBasedOnTheSameImageAllNamespaces30d(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
if exactMatch && ns == "test-ns" {
|
||||
return 200, 20, nil
|
||||
}
|
||||
if exactMatch && end.Sub(start) == week && ns == "" {
|
||||
return 200, 20, nil
|
||||
}
|
||||
if exactMatch && end.Sub(start) == month && ns == "" {
|
||||
return 100, 120, nil
|
||||
}
|
||||
return 200, 120, nil
|
||||
}
|
||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
||||
}
|
||||
|
||||
func TestEstimationBasedOnOtherImages(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, image, ns string, exactMatch bool, _, _ time.Time) (int64, int64, error) {
|
||||
if image == "image" && !exactMatch && ns == "" {
|
||||
return 100, 5, nil
|
||||
}
|
||||
return 200, 20, nil
|
||||
}
|
||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
||||
}
|
||||
|
||||
func TestNoData(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, _ bool, _, _ time.Time) (int64, int64, error) {
|
||||
return 200, 0, nil
|
||||
}
|
||||
ir := newInitialResources(&fakeSource{f: f}, 90, false)
|
||||
|
||||
pods := []*api.Pod{
|
||||
createPod("p0", "image:v0", parseReq("", "")),
|
||||
}
|
||||
admit(t, ir, pods)
|
||||
|
||||
if pods[0].Spec.Containers[0].Resources.Requests != nil {
|
||||
t.Errorf("Unexpected resource estimation")
|
||||
}
|
||||
|
||||
expectNoAnnotation(t, pods[0])
|
||||
}
|
||||
|
||||
func TestManyContainers(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, _, _ time.Time) (int64, int64, error) {
|
||||
if exactMatch {
|
||||
return 100, 120, nil
|
||||
}
|
||||
return 200, 30, nil
|
||||
}
|
||||
ir := newInitialResources(&fakeSource{f: f}, 90, false)
|
||||
|
||||
pod := createPod("p", "image:v0", parseReq("", ""))
|
||||
addContainer(pod, "c1", "image:v1", parseReq("", "300"))
|
||||
addContainer(pod, "c2", "image:v2", parseReq("300m", ""))
|
||||
addContainer(pod, "c3", "image:v3", parseReq("300m", "300"))
|
||||
admit(t, ir, []*api.Pod{pod})
|
||||
|
||||
verifyContainer(t, &pod.Spec.Containers[0], 100, 100)
|
||||
verifyContainer(t, &pod.Spec.Containers[1], 100, 300)
|
||||
verifyContainer(t, &pod.Spec.Containers[2], 300, 100)
|
||||
verifyContainer(t, &pod.Spec.Containers[3], 300, 300)
|
||||
|
||||
verifyAnnotation(t, pod, "Initial Resources plugin set: cpu, memory request for init container i0; cpu, memory request for container c0; cpu request for container c1; memory request for container c2")
|
||||
}
|
||||
|
||||
func TestNamespaceAware(t *testing.T) {
|
||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
if ns == "test-ns" {
|
||||
return 200, 0, nil
|
||||
}
|
||||
return 200, 120, nil
|
||||
}
|
||||
ir := newInitialResources(&fakeSource{f: f}, 90, true)
|
||||
|
||||
pods := []*api.Pod{
|
||||
createPod("p0", "image:v0", parseReq("", "")),
|
||||
}
|
||||
admit(t, ir, pods)
|
||||
|
||||
if pods[0].Spec.Containers[0].Resources.Requests != nil {
|
||||
t.Errorf("Unexpected resource estimation")
|
||||
}
|
||||
|
||||
expectNoAnnotation(t, pods[0])
|
||||
}
|
||||
57
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
var (
|
||||
influxdbHost = flag.String("ir-influxdb-host", "localhost:8080/api/v1/proxy/namespaces/kube-system/services/monitoring-influxdb:api", "Address of InfluxDB which contains metrics required by InitialResources")
|
||||
user = flag.String("ir-user", "root", "User used for connecting to InfluxDB")
|
||||
// TODO: figure out how to better pass password here
|
||||
password = flag.String("ir-password", "root", "Password used for connecting to InfluxDB")
|
||||
db = flag.String("ir-dbname", "k8s", "InfluxDB database name which contains metrics required by InitialResources")
|
||||
hawkularConfig = flag.String("ir-hawkular", "", "Hawkular configuration URL")
|
||||
)
|
||||
|
||||
// WARNING: If you are planning to add another implementation of dataSource interface please bear in mind,
|
||||
// that dataSource will be moved to Heapster some time in the future and possibly rewritten.
|
||||
type dataSource interface {
|
||||
// Returns <perc>th of sample values which represent usage of <kind> for containers running <image>,
|
||||
// within time range (start, end), number of samples considered and error if occurred.
|
||||
// If <exactMatch> then take only samples that concern the same image (both name and take are the same),
|
||||
// otherwise consider also samples with the same image a possibly different tag.
|
||||
GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (usage int64, samples int64, err error)
|
||||
}
|
||||
|
||||
func newDataSource(kind string) (dataSource, error) {
|
||||
if kind == "influxdb" {
|
||||
return newInfluxdbSource(*influxdbHost, *user, *password, *db)
|
||||
}
|
||||
if kind == "gcm" {
|
||||
return newGcmSource()
|
||||
}
|
||||
if kind == "hawkular" {
|
||||
return newHawkularSource(*hawkularConfig)
|
||||
}
|
||||
return nil, fmt.Errorf("unknown data source %v", kind)
|
||||
}
|
||||
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm.go
generated
vendored
Normal file
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm.go
generated
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
gce "cloud.google.com/go/compute/metadata"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
gcm "google.golang.org/api/cloudmonitoring/v2beta2"
|
||||
)
|
||||
|
||||
const (
|
||||
kubePrefix = "custom.cloudmonitoring.googleapis.com/kubernetes.io/"
|
||||
cpuMetricName = kubePrefix + "cpu/usage_rate"
|
||||
memMetricName = kubePrefix + "memory/usage"
|
||||
labelImage = kubePrefix + "label/container_base_image"
|
||||
labelNs = kubePrefix + "label/pod_namespace"
|
||||
)
|
||||
|
||||
type gcmSource struct {
|
||||
project string
|
||||
gcmService *gcm.Service
|
||||
}
|
||||
|
||||
func newGcmSource() (dataSource, error) {
|
||||
// Detect project ID
|
||||
projectId, err := gce.ProjectID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create Google Cloud Monitoring service.
|
||||
client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
|
||||
s, err := gcm.New(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gcmSource{
|
||||
project: projectId,
|
||||
gcmService: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *gcmSource) query(metric, oldest, youngest string, labels []string, pageToken string) (*gcm.ListTimeseriesResponse, error) {
|
||||
req := s.gcmService.Timeseries.List(s.project, metric, youngest, nil).
|
||||
Oldest(oldest).
|
||||
Aggregator("mean").
|
||||
Window("1m")
|
||||
for _, l := range labels {
|
||||
req = req.Labels(l)
|
||||
}
|
||||
if pageToken != "" {
|
||||
req = req.PageToken(pageToken)
|
||||
}
|
||||
return req.Do()
|
||||
}
|
||||
|
||||
func retrieveRawSamples(res *gcm.ListTimeseriesResponse, output *[]int) {
|
||||
for _, ts := range res.Timeseries {
|
||||
for _, p := range ts.Points {
|
||||
*output = append(*output, int(*p.DoubleValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *gcmSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
var metric string
|
||||
if kind == api.ResourceCPU {
|
||||
metric = cpuMetricName
|
||||
} else if kind == api.ResourceMemory {
|
||||
metric = memMetricName
|
||||
}
|
||||
|
||||
var labels []string
|
||||
if exactMatch {
|
||||
labels = append(labels, labelImage+"=="+image)
|
||||
} else {
|
||||
labels = append(labels, labelImage+"=~"+image+".*")
|
||||
}
|
||||
if namespace != "" {
|
||||
labels = append(labels, labelNs+"=="+namespace)
|
||||
}
|
||||
|
||||
oldest := start.Format(time.RFC3339)
|
||||
youngest := end.Format(time.RFC3339)
|
||||
|
||||
rawSamples := make([]int, 0)
|
||||
pageToken := ""
|
||||
for {
|
||||
res, err := s.query(metric, oldest, youngest, labels, pageToken)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
retrieveRawSamples(res, &rawSamples)
|
||||
|
||||
pageToken = res.NextPageToken
|
||||
if pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
count := len(rawSamples)
|
||||
if count == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
sort.Ints(rawSamples)
|
||||
usageIndex := int64(math.Ceil(float64(count)*9/10)) - 1
|
||||
usage := rawSamples[usageIndex]
|
||||
|
||||
return int64(usage), int64(count), nil
|
||||
}
|
||||
226
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular.go
generated
vendored
Normal file
226
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular.go
generated
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/hawkular/hawkular-client-go/metrics"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
)
|
||||
|
||||
type hawkularSource struct {
|
||||
client *metrics.Client
|
||||
uri *url.URL
|
||||
useNamespace bool
|
||||
modifiers []metrics.Modifier
|
||||
}
|
||||
|
||||
const (
|
||||
containerImageTag string = "container_base_image"
|
||||
descriptorTag string = "descriptor_name"
|
||||
separator string = "/"
|
||||
|
||||
defaultServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
||||
)
|
||||
|
||||
// heapsterName gets the equivalent MetricDescriptor.Name used in the Heapster
|
||||
func heapsterName(kind api.ResourceName) string {
|
||||
switch kind {
|
||||
case api.ResourceCPU:
|
||||
return "cpu/usage"
|
||||
case api.ResourceMemory:
|
||||
return "memory/usage"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// tagQuery creates tagFilter query for Hawkular
|
||||
func tagQuery(kind api.ResourceName, image string, exactMatch bool) map[string]string {
|
||||
q := make(map[string]string)
|
||||
|
||||
// Add here the descriptor_tag..
|
||||
q[descriptorTag] = heapsterName(kind)
|
||||
|
||||
if exactMatch {
|
||||
q[containerImageTag] = image
|
||||
} else {
|
||||
split := strings.Index(image, "@")
|
||||
if split < 0 {
|
||||
split = strings.Index(image, ":")
|
||||
}
|
||||
q[containerImageTag] = fmt.Sprintf("%s:*", image[:split])
|
||||
}
|
||||
|
||||
return q
|
||||
}
|
||||
|
||||
// dataSource API
|
||||
|
||||
func (hs *hawkularSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
q := tagQuery(kind, image, exactMatch)
|
||||
|
||||
m := make([]metrics.Modifier, len(hs.modifiers), 2+len(hs.modifiers))
|
||||
copy(m, hs.modifiers)
|
||||
|
||||
if namespace != api.NamespaceAll {
|
||||
m = append(m, metrics.Tenant(namespace))
|
||||
}
|
||||
|
||||
p, err := metrics.ConvertToFloat64(perc)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
m = append(m, metrics.Filters(metrics.TagsFilter(q), metrics.BucketsFilter(1), metrics.StartTimeFilter(start), metrics.EndTimeFilter(end), metrics.PercentilesFilter([]float64{p})))
|
||||
|
||||
bp, err := hs.client.ReadBuckets(metrics.Counter, m...)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if len(bp) > 0 && len(bp[0].Percentiles) > 0 {
|
||||
return int64(bp[0].Percentiles[0].Value), int64(bp[0].Samples), nil
|
||||
}
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
// newHawkularSource creates a new Hawkular Source. The uri follows the scheme from Heapster
|
||||
func newHawkularSource(uri string) (dataSource, error) {
|
||||
u, err := url.Parse(uri)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d := &hawkularSource{
|
||||
uri: u,
|
||||
}
|
||||
if err = d.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// init initializes the Hawkular dataSource. Almost equal to the Heapster initialization
|
||||
func (hs *hawkularSource) init() error {
|
||||
hs.modifiers = make([]metrics.Modifier, 0)
|
||||
p := metrics.Parameters{
|
||||
Tenant: "heapster", // This data is stored by the heapster - for no-namespace hits
|
||||
Url: hs.uri.String(),
|
||||
}
|
||||
|
||||
opts := hs.uri.Query()
|
||||
|
||||
if v, found := opts["tenant"]; found {
|
||||
p.Tenant = v[0]
|
||||
}
|
||||
|
||||
if v, found := opts["useServiceAccount"]; found {
|
||||
if b, _ := strconv.ParseBool(v[0]); b {
|
||||
accountFile := defaultServiceAccountFile
|
||||
if file, f := opts["serviceAccountFile"]; f {
|
||||
accountFile = file[0]
|
||||
}
|
||||
|
||||
// If a readable service account token exists, then use it
|
||||
if contents, err := ioutil.ReadFile(accountFile); err == nil {
|
||||
p.Token = string(contents)
|
||||
} else {
|
||||
glog.Errorf("Could not read contents of %s, no token authentication is used\n", defaultServiceAccountFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication / Authorization parameters
|
||||
tC := &tls.Config{}
|
||||
|
||||
if v, found := opts["auth"]; found {
|
||||
if _, f := opts["caCert"]; f {
|
||||
return fmt.Errorf("both auth and caCert files provided, combination is not supported")
|
||||
}
|
||||
if len(v[0]) > 0 {
|
||||
// Authfile
|
||||
kubeConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&clientcmd.ClientConfigLoadingRules{
|
||||
ExplicitPath: v[0]},
|
||||
&clientcmd.ConfigOverrides{}).ClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tC, err = restclient.TLSConfigFor(kubeConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if u, found := opts["user"]; found {
|
||||
if _, wrong := opts["useServiceAccount"]; wrong {
|
||||
return fmt.Errorf("if user and password are used, serviceAccount cannot be used")
|
||||
}
|
||||
if p, f := opts["pass"]; f {
|
||||
hs.modifiers = append(hs.modifiers, func(req *http.Request) error {
|
||||
req.SetBasicAuth(u[0], p[0])
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if v, found := opts["caCert"]; found {
|
||||
caCert, err := ioutil.ReadFile(v[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
tC.RootCAs = caCertPool
|
||||
}
|
||||
|
||||
if v, found := opts["insecure"]; found {
|
||||
insecure, err := strconv.ParseBool(v[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tC.InsecureSkipVerify = insecure
|
||||
}
|
||||
|
||||
p.TLSConfig = tC
|
||||
|
||||
c, err := metrics.NewHawkularClient(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.client = c
|
||||
|
||||
glog.Infof("Initialised Hawkular Source with parameters %v", p)
|
||||
return nil
|
||||
}
|
||||
105
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular_test.go
generated
vendored
Normal file
105
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
assert "github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
testImageName string = "hawkular/hawkular-metrics"
|
||||
testImageVersion string = "latest"
|
||||
testImageSHA string = "b727ece3780cdd30e9a86226e520f26bcc396071ed7a86b7ef6684bb93a9f717"
|
||||
testPartialMatch string = "hawkular/hawkular-metrics:*"
|
||||
)
|
||||
|
||||
func testImageWithVersion() string {
|
||||
return fmt.Sprintf("%s:%s", testImageName, testImageVersion)
|
||||
}
|
||||
|
||||
func testImageWithReference() string {
|
||||
return fmt.Sprintf("%s@sha256:%s", testImageName, testImageSHA)
|
||||
}
|
||||
|
||||
func TestTaqQuery(t *testing.T) {
|
||||
kind := api.ResourceCPU
|
||||
tQ := tagQuery(kind, testImageWithVersion(), false)
|
||||
|
||||
assert.Equal(t, 2, len(tQ))
|
||||
assert.Equal(t, testPartialMatch, tQ[containerImageTag])
|
||||
assert.Equal(t, "cpu/usage", tQ[descriptorTag])
|
||||
|
||||
tQe := tagQuery(kind, testImageWithVersion(), true)
|
||||
assert.Equal(t, 2, len(tQe))
|
||||
assert.Equal(t, testImageWithVersion(), tQe[containerImageTag])
|
||||
assert.Equal(t, "cpu/usage", tQe[descriptorTag])
|
||||
|
||||
tQr := tagQuery(kind, testImageWithReference(), false)
|
||||
assert.Equal(t, 2, len(tQe))
|
||||
assert.Equal(t, testPartialMatch, tQr[containerImageTag])
|
||||
assert.Equal(t, "cpu/usage", tQr[descriptorTag])
|
||||
|
||||
tQre := tagQuery(kind, testImageWithReference(), true)
|
||||
assert.Equal(t, 2, len(tQe))
|
||||
assert.Equal(t, testImageWithReference(), tQre[containerImageTag])
|
||||
assert.Equal(t, "cpu/usage", tQre[descriptorTag])
|
||||
}
|
||||
|
||||
func TestGetUsagePercentile(t *testing.T) {
|
||||
tenant := "16a8884e4c155457ee38a8901df6b536"
|
||||
reqs := make(map[string]string)
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, tenant, r.Header.Get("Hawkular-Tenant"))
|
||||
assert.Equal(t, "Basic", r.Header.Get("Authorization")[:5])
|
||||
|
||||
if strings.Contains(r.RequestURI, "counters/data") {
|
||||
assert.True(t, strings.Contains(r.RequestURI, url.QueryEscape(testImageWithVersion())))
|
||||
assert.True(t, strings.Contains(r.RequestURI, "cpu%2Fusage"))
|
||||
assert.True(t, strings.Contains(r.RequestURI, "percentiles=90"))
|
||||
|
||||
reqs["counters/data"] = r.RequestURI
|
||||
fmt.Fprintf(w, ` [{"start":1444620095882,"end":1444648895882,"min":1.45,"avg":1.45,"median":1.45,"max":1.45,"percentile95th":1.45,"samples":123456,"percentiles":[{"value":7896.54,"quantile":0.9},{"value":1.45,"quantile":0.99}],"empty":false}]`)
|
||||
} else {
|
||||
reqs["unknown"] = r.RequestURI
|
||||
}
|
||||
}))
|
||||
|
||||
paramUri := fmt.Sprintf("%s?user=test&pass=yep", s.URL)
|
||||
|
||||
hSource, err := newHawkularSource(paramUri)
|
||||
assert.NoError(t, err)
|
||||
|
||||
usage, samples, err := hSource.GetUsagePercentile(api.ResourceCPU, 90, testImageWithVersion(), "16a8884e4c155457ee38a8901df6b536", true, time.Now(), time.Now())
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, len(reqs))
|
||||
assert.Equal(t, "", reqs["unknown"])
|
||||
|
||||
assert.Equal(t, int64(123456), int64(samples))
|
||||
assert.Equal(t, int64(7896), usage) // float64 -> int64
|
||||
}
|
||||
73
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb.go
generated
vendored
Normal file
73
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package initialresources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
influxdb "github.com/influxdata/influxdb/client"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
const (
|
||||
cpuSeriesName = "autoscaling.cpu.usage.2m"
|
||||
memSeriesName = "autoscaling.memory.usage.2m"
|
||||
cpuContinuousQuery = "select derivative(value) as value from \"cpu/usage_ns_cumulative\" where pod_id <> '' group by pod_id, pod_namespace, container_name, container_base_image, time(2m) into " + cpuSeriesName
|
||||
memContinuousQuery = "select mean(value) as value from \"memory/usage_bytes_gauge\" where pod_id <> '' group by pod_id, pod_namespace, container_name, container_base_image, time(2m) into " + memSeriesName
|
||||
timeFormat = "2006-01-02 15:04:05"
|
||||
)
|
||||
|
||||
// TODO(piosz): rewrite this once we will migrate into InfluxDB v0.9.
|
||||
type influxdbSource struct{}
|
||||
|
||||
func newInfluxdbSource(host, user, password, db string) (dataSource, error) {
|
||||
return &influxdbSource{}, nil
|
||||
}
|
||||
|
||||
func (s *influxdbSource) query(query string) ([]*influxdb.Response, error) {
|
||||
// TODO(piosz): add support again
|
||||
return nil, fmt.Errorf("temporary not supported; see #18826 for more details")
|
||||
}
|
||||
|
||||
func (s *influxdbSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
||||
var series string
|
||||
if kind == api.ResourceCPU {
|
||||
series = cpuSeriesName
|
||||
} else if kind == api.ResourceMemory {
|
||||
series = memSeriesName
|
||||
}
|
||||
|
||||
var imgPattern string
|
||||
if exactMatch {
|
||||
imgPattern = "='" + image + "'"
|
||||
} else {
|
||||
// Escape character "/" in image pattern.
|
||||
imgPattern = "=~/^" + strings.Replace(image, "/", "\\/", -1) + "/"
|
||||
}
|
||||
var namespaceCond string
|
||||
if namespace != "" {
|
||||
namespaceCond = " and pod_namespace='" + namespace + "'"
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("select percentile(value, %v), count(pod_id) from %v where container_base_image%v%v and time > '%v' and time < '%v'", perc, series, imgPattern, namespaceCond, start.UTC().Format(timeFormat), end.UTC().Format(timeFormat))
|
||||
if _, err := s.query(query); err != nil {
|
||||
return 0, 0, fmt.Errorf("error while trying to query InfluxDB: %v", err)
|
||||
}
|
||||
return 0, 0, nil
|
||||
}
|
||||
52
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
52
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"interfaces.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/meta:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/labels:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/errors:go_default_library",
|
||||
"//vendor:github.com/hashicorp/golang-lru",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
531
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
531
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
662
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
662
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package limitranger
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type LimitRangerActions interface {
|
||||
// Limit is a pluggable function to enforce limits on the object.
|
||||
Limit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
||||
// SupportsAttributes is a pluggable function to allow overridding what resources the limitranger
|
||||
// supports.
|
||||
SupportsAttributes(attr admission.Attributes) bool
|
||||
// SupportsLimit is a pluggable function to allow ignoring limits that should not be applied
|
||||
// for any reason.
|
||||
SupportsLimit(limitRange *api.LimitRange) bool
|
||||
}
|
||||
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal 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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
100
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
100
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package autoprovision
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("NamespaceAutoProvision", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewProvision(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// provision is an implementation of admission.Interface.
|
||||
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
|
||||
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
|
||||
type provision struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
namespaceInformer cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
var _ = admission.WantsInformerFactory(&provision{})
|
||||
|
||||
func (p *provision) Admit(a admission.Attributes) (err error) {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
// we need to wait for our caches to warm
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
_, exists, err := p.namespaceInformer.GetStore().Get(namespace)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
_, err = p.client.Core().Namespaces().Create(namespace)
|
||||
if err != nil && !errors.IsAlreadyExists(err) {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewProvision creates a new namespace provision admission control handler
|
||||
func NewProvision(c clientset.Interface) admission.Interface {
|
||||
return &provision{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *provision) SetInformerFactory(f informers.SharedInformerFactory) {
|
||||
p.namespaceInformer = f.InternalNamespaces().Informer()
|
||||
p.SetReadyFunc(p.namespaceInformer.HasSynced)
|
||||
}
|
||||
|
||||
func (p *provision) Validate() error {
|
||||
if p.namespaceInformer == nil {
|
||||
return fmt.Errorf("missing namespaceInformer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package autoprovision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(nil, c, 5*time.Minute)
|
||||
handler := NewProvision(c)
|
||||
plugins := []admission.Interface{handler}
|
||||
pluginInitializer := admission.NewPluginInitializer(f, nil)
|
||||
pluginInitializer.Initialize(plugins)
|
||||
err := admission.Validate(plugins)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// hasCreateNamespaceAction returns true if it has the create namespace action
|
||||
func hasCreateNamespaceAction(mockClient *fake.Clientset) bool {
|
||||
for _, action := range mockClient.Actions() {
|
||||
if action.GetVerb() == "create" && action.GetResource().Resource == "namespaces" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestAdmission verifies a namespace is created on create requests for namespace managed resources
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgnoreAdmission validates that a request is ignored if its not a create
|
||||
func TestIgnoreAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
chainHandler := admission.NewChainHandler(handler)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = chainHandler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionWithLatentCache(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("create", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.NewAlreadyExists(api.Resource("namespaces"), namespace)
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
||||
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
107
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
107
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exists
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("NamespaceExists", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewExists(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// exists is an implementation of admission.Interface.
|
||||
// It rejects all incoming requests in a namespace context if the namespace does not exist.
|
||||
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
|
||||
type exists struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
namespaceInformer cache.SharedIndexInformer
|
||||
}
|
||||
|
||||
var _ = admission.WantsInformerFactory(&exists{})
|
||||
|
||||
func (e *exists) Admit(a admission.Attributes) (err error) {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !e.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
_, exists, err := e.namespaceInformer.GetStore().Get(namespace)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
|
||||
_, err = e.client.Core().Namespaces().Get(a.GetNamespace())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewExists creates a new namespace exists admission control handler
|
||||
func NewExists(c clientset.Interface) admission.Interface {
|
||||
return &exists{
|
||||
client: c,
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *exists) SetInformerFactory(f informers.SharedInformerFactory) {
|
||||
e.namespaceInformer = f.InternalNamespaces().Informer()
|
||||
e.SetReadyFunc(e.namespaceInformer.HasSynced)
|
||||
}
|
||||
|
||||
func (e *exists) Validate() error {
|
||||
if e.namespaceInformer == nil {
|
||||
return fmt.Errorf("missing namespaceInformer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(nil, c, 5*time.Minute)
|
||||
handler := NewExists(c)
|
||||
plugins := []admission.Interface{handler}
|
||||
pluginInitializer := admission.NewPluginInitializer(f, nil)
|
||||
pluginInitializer.Initialize(plugins)
|
||||
err := admission.Validate(plugins)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies pod is admitted only if namespace exists.
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist.
|
||||
func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
actions := ""
|
||||
for _, action := range mockClient.Actions() {
|
||||
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
|
||||
}
|
||||
t.Errorf("expected error returned from admission handler: %v", actions)
|
||||
}
|
||||
}
|
||||
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/BUILD
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/util/cache:go_default_library",
|
||||
"//pkg/util/clock:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/clock:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
193
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/admission.go
generated
vendored
Normal file
193
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
utilcache "k8s.io/kubernetes/pkg/util/cache"
|
||||
"k8s.io/kubernetes/pkg/util/clock"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
// Name of admission plug-in
|
||||
PluginName = "NamespaceLifecycle"
|
||||
// how long a namespace stays in the force live lookup cache before expiration.
|
||||
forceLiveLookupTTL = 30 * time.Second
|
||||
// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
|
||||
// this accomplishes two things:
|
||||
// 1. It allows a watch-fed cache time to observe a namespace creation event
|
||||
// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
|
||||
// so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
|
||||
missingNamespaceWait = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewLifecycle(client, sets.NewString(api.NamespaceDefault, api.NamespaceSystem))
|
||||
})
|
||||
}
|
||||
|
||||
// lifecycle is an implementation of admission.Interface.
|
||||
// It enforces life-cycle constraints around a Namespace depending on its Phase
|
||||
type lifecycle struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
immortalNamespaces sets.String
|
||||
namespaceInformer cache.SharedIndexInformer
|
||||
// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
|
||||
// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
|
||||
forceLiveLookupCache *utilcache.LRUExpireCache
|
||||
}
|
||||
|
||||
type forceLiveLookupEntry struct {
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
var _ = admission.WantsInformerFactory(&lifecycle{})
|
||||
|
||||
func makeNamespaceKey(namespace string) *api.Namespace {
|
||||
return &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: namespace,
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lifecycle) Admit(a admission.Attributes) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == api.Kind("Namespace") && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
}
|
||||
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
// if a namespace is deleted, we want to prevent all further creates into it
|
||||
// while it is undergoing termination. to reduce incidences where the cache
|
||||
// is slow to update, we add the namespace into a force live lookup list to ensure
|
||||
// we are not looking at stale state.
|
||||
if a.GetOperation() == admission.Delete {
|
||||
l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !l.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
var (
|
||||
namespaceObj interface{}
|
||||
exists bool
|
||||
err error
|
||||
)
|
||||
|
||||
key := makeNamespaceKey(a.GetNamespace())
|
||||
namespaceObj, exists, err = l.namespaceInformer.GetStore().Get(key)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if !exists && a.GetOperation() == admission.Create {
|
||||
// give the cache time to observe the namespace before rejecting a create.
|
||||
// this helps when creating a namespace and immediately creating objects within it.
|
||||
time.Sleep(missingNamespaceWait)
|
||||
namespaceObj, exists, err = l.namespaceInformer.GetStore().Get(key)
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
if exists {
|
||||
glog.V(4).Infof("found %s in cache after waiting", a.GetNamespace())
|
||||
}
|
||||
}
|
||||
|
||||
// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
|
||||
forceLiveLookup := false
|
||||
if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
|
||||
// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
|
||||
forceLiveLookup = exists && namespaceObj.(*api.Namespace).Status.Phase == api.NamespaceActive
|
||||
}
|
||||
|
||||
// refuse to operate on non-existent namespaces
|
||||
if !exists || forceLiveLookup {
|
||||
// as a last resort, make a call directly to storage
|
||||
namespaceObj, err = l.client.Core().Namespaces().Get(a.GetNamespace())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
glog.V(4).Infof("found %s via storage lookup", a.GetNamespace())
|
||||
}
|
||||
|
||||
// ensure that we're not trying to create objects in terminating namespaces
|
||||
if a.GetOperation() == admission.Create {
|
||||
namespace := namespaceObj.(*api.Namespace)
|
||||
if namespace.Status.Phase != api.NamespaceTerminating {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: This should probably not be a 403
|
||||
return admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated.", a.GetNamespace()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewLifecycle creates a new namespace lifecycle admission control handler
|
||||
func NewLifecycle(c clientset.Interface, immortalNamespaces sets.String) (admission.Interface, error) {
|
||||
return newLifecycleWithClock(c, immortalNamespaces, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newLifecycleWithClock(c clientset.Interface, immortalNamespaces sets.String, clock utilcache.Clock) (admission.Interface, error) {
|
||||
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
|
||||
return &lifecycle{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
client: c,
|
||||
immortalNamespaces: immortalNamespaces,
|
||||
forceLiveLookupCache: forceLiveLookupCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *lifecycle) SetInformerFactory(f informers.SharedInformerFactory) {
|
||||
l.namespaceInformer = f.InternalNamespaces().Informer()
|
||||
l.SetReadyFunc(l.namespaceInformer.HasSynced)
|
||||
}
|
||||
|
||||
func (l *lifecycle) Validate() error {
|
||||
if l.namespaceInformer == nil {
|
||||
return fmt.Errorf("missing namespaceInformer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
258
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/admission_test.go
generated
vendored
Normal file
258
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package lifecycle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/clock"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns a configured handler for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
return newHandlerForTestWithClock(c, clock.RealClock{})
|
||||
}
|
||||
|
||||
// newHandlerForTestWithClock returns a configured handler for testing.
|
||||
func newHandlerForTestWithClock(c clientset.Interface, cacheClock clock.Clock) (admission.Interface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(nil, c, 5*time.Minute)
|
||||
handler, err := newLifecycleWithClock(c, sets.NewString(api.NamespaceDefault, api.NamespaceSystem), cacheClock)
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
plugins := []admission.Interface{handler}
|
||||
pluginInitializer := admission.NewPluginInitializer(f, nil)
|
||||
pluginInitializer.Initialize(plugins)
|
||||
err = admission.Validate(plugins)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces with the specified phase.
|
||||
func newMockClientForTest(namespaces map[string]api.NamespacePhase) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
index := 0
|
||||
for name, phase := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: name,
|
||||
ResourceVersion: fmt.Sprintf("%d", index),
|
||||
},
|
||||
Status: api.NamespaceStatus{
|
||||
Phase: phase,
|
||||
},
|
||||
})
|
||||
index++
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist.
|
||||
func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]api.NamespacePhase{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
actions := ""
|
||||
for _, action := range mockClient.Actions() {
|
||||
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
|
||||
}
|
||||
t.Errorf("expected error returned from admission handler: %v", actions)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceActive verifies a resource is admitted when the namespace is active.
|
||||
func TestAdmissionNamespaceActive(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]api.NamespacePhase{
|
||||
namespace: api.NamespaceActive,
|
||||
})
|
||||
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceTerminating verifies a resource is not created when the namespace is active.
|
||||
func TestAdmissionNamespaceTerminating(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest(map[string]api.NamespacePhase{
|
||||
namespace: api.NamespaceTerminating,
|
||||
})
|
||||
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace when it is terminating")
|
||||
}
|
||||
|
||||
// verify update operations in the namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
|
||||
// verify delete operations in the namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
|
||||
// verify delete of namespace default can never proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Namespace").WithVersion("version"), "", api.NamespaceDefault, api.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error that this namespace can never be deleted")
|
||||
}
|
||||
|
||||
// verify delete of namespace other than default can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Namespace").WithVersion("version"), "", "other", api.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Did not expect an error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceForceLiveLookup verifies live lookups are done after deleting a namespace
|
||||
func TestAdmissionNamespaceForceLiveLookup(t *testing.T) {
|
||||
namespace := "test"
|
||||
getCalls := int64(0)
|
||||
phases := map[string]api.NamespacePhase{namespace: api.NamespaceActive}
|
||||
mockClient := newMockClientForTest(phases)
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
getCalls++
|
||||
return true, &api.Namespace{ObjectMeta: api.ObjectMeta{Name: namespace}, Status: api.NamespaceStatus{Phase: phases[namespace]}}, nil
|
||||
})
|
||||
|
||||
fakeClock := clock.NewFakeClock(time.Now())
|
||||
|
||||
handler, informerFactory, err := newHandlerForTestWithClock(mockClient, fakeClock)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
// verify create operations in the namespace is allowed
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error rejecting creates in an active namespace")
|
||||
}
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookups of the namespace, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// verify delete of namespace can proceed
|
||||
err = handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Namespace").WithVersion("version"), "", namespace, api.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected namespace deletion to be allowed")
|
||||
}
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookups of the namespace, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// simulate the phase changing
|
||||
phases[namespace] = api.NamespaceTerminating
|
||||
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace right after deleting it")
|
||||
}
|
||||
if getCalls != 1 {
|
||||
t.Errorf("Expected a live lookup of the namespace at t=0, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// Ensure the live lookup is still forced up to forceLiveLookupTTL
|
||||
fakeClock.Step(forceLiveLookupTTL)
|
||||
|
||||
// verify create operations in the namespace cause an error
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error rejecting creates in a namespace right after deleting it")
|
||||
}
|
||||
if getCalls != 1 {
|
||||
t.Errorf("Expected a live lookup of the namespace at t=forceLiveLookupTTL, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
|
||||
// Ensure the live lookup expires
|
||||
fakeClock.Step(time.Millisecond)
|
||||
|
||||
// verify create operations in the namespace don't force a live lookup after the timeout
|
||||
handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if getCalls != 0 {
|
||||
t.Errorf("Expected no live lookup of the namespace at t=forceLiveLookupTTL+1ms, got %d", getCalls)
|
||||
}
|
||||
getCalls = 0
|
||||
}
|
||||
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/cloudprovider:go_default_library",
|
||||
"//pkg/cloudprovider/providers/aws:go_default_library",
|
||||
"//pkg/cloudprovider/providers/gce:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/cloudprovider/providers/aws:go_default_library",
|
||||
"//pkg/types:go_default_library",
|
||||
],
|
||||
)
|
||||
193
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go
generated
vendored
Normal file
193
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
vol "k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("PersistentVolumeLabel", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
persistentVolumeLabelAdmission := NewPersistentVolumeLabel()
|
||||
return persistentVolumeLabelAdmission, nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = admission.Interface(&persistentVolumeLabel{})
|
||||
|
||||
type persistentVolumeLabel struct {
|
||||
*admission.Handler
|
||||
|
||||
mutex sync.Mutex
|
||||
ebsVolumes aws.Volumes
|
||||
gceCloudProvider *gce.GCECloud
|
||||
}
|
||||
|
||||
// NewPersistentVolumeLabel returns an admission.Interface implementation which adds labels to PersistentVolume CREATE requests,
|
||||
// based on the labels provided by the underlying cloud provider.
|
||||
//
|
||||
// As a side effect, the cloud provider may block invalid or non-existent volumes.
|
||||
func NewPersistentVolumeLabel() *persistentVolumeLabel {
|
||||
return &persistentVolumeLabel{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) Admit(a admission.Attributes) (err error) {
|
||||
if a.GetResource().GroupResource() != api.Resource("persistentvolumes") {
|
||||
return nil
|
||||
}
|
||||
obj := a.GetObject()
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
volume, ok := obj.(*api.PersistentVolume)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var volumeLabels map[string]string
|
||||
if volume.Spec.AWSElasticBlockStore != nil {
|
||||
labels, err := l.findAWSEBSLabels(volume)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("error querying AWS EBS volume %s: %v", volume.Spec.AWSElasticBlockStore.VolumeID, err))
|
||||
}
|
||||
volumeLabels = labels
|
||||
}
|
||||
if volume.Spec.GCEPersistentDisk != nil {
|
||||
labels, err := l.findGCEPDLabels(volume)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("error querying GCE PD volume %s: %v", volume.Spec.GCEPersistentDisk.PDName, err))
|
||||
}
|
||||
volumeLabels = labels
|
||||
}
|
||||
|
||||
if len(volumeLabels) != 0 {
|
||||
if volume.Labels == nil {
|
||||
volume.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range volumeLabels {
|
||||
// We (silently) replace labels if they are provided.
|
||||
// This should be OK because they are in the kubernetes.io namespace
|
||||
// i.e. we own them
|
||||
volume.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findAWSEBSLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.AWSElasticBlockStore.VolumeID == vol.ProvisionedVolumeName {
|
||||
return nil, nil
|
||||
}
|
||||
ebsVolumes, err := l.getEBSVolumes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ebsVolumes == nil {
|
||||
return nil, fmt.Errorf("unable to build AWS cloud provider for EBS")
|
||||
}
|
||||
|
||||
// TODO: GetVolumeLabels is actually a method on the Volumes interface
|
||||
// If that gets standardized we can refactor to reduce code duplication
|
||||
spec := aws.KubernetesVolumeID(volume.Spec.AWSElasticBlockStore.VolumeID)
|
||||
labels, err := ebsVolumes.GetVolumeLabels(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// getEBSVolumes returns the AWS Volumes interface for ebs
|
||||
func (l *persistentVolumeLabel) getEBSVolumes() (aws.Volumes, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.ebsVolumes == nil {
|
||||
cloudProvider, err := cloudprovider.GetCloudProvider("aws", nil)
|
||||
if err != nil || cloudProvider == nil {
|
||||
return nil, err
|
||||
}
|
||||
awsCloudProvider, ok := cloudProvider.(*aws.Cloud)
|
||||
if !ok {
|
||||
// GetCloudProvider has gone very wrong
|
||||
return nil, fmt.Errorf("error retrieving AWS cloud provider")
|
||||
}
|
||||
l.ebsVolumes = awsCloudProvider
|
||||
}
|
||||
return l.ebsVolumes, nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findGCEPDLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.GCEPersistentDisk.PDName == vol.ProvisionedVolumeName {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
provider, err := l.getGCECloudProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf("unable to build GCE cloud provider for PD")
|
||||
}
|
||||
|
||||
// If the zone is already labeled, honor the hint
|
||||
zone := volume.Labels[metav1.LabelZoneFailureDomain]
|
||||
|
||||
labels, err := provider.GetAutoLabelsForPD(volume.Spec.GCEPersistentDisk.PDName, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// getGCECloudProvider returns the GCE cloud provider, for use for querying volume labels
|
||||
func (l *persistentVolumeLabel) getGCECloudProvider() (*gce.GCECloud, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.gceCloudProvider == nil {
|
||||
cloudProvider, err := cloudprovider.GetCloudProvider("gce", nil)
|
||||
if err != nil || cloudProvider == nil {
|
||||
return nil, err
|
||||
}
|
||||
gceCloudProvider, ok := cloudProvider.(*gce.GCECloud)
|
||||
if !ok {
|
||||
// GetCloudProvider has gone very wrong
|
||||
return nil, fmt.Errorf("error retrieving GCE cloud provider")
|
||||
}
|
||||
l.gceCloudProvider = gceCloudProvider
|
||||
}
|
||||
return l.gceCloudProvider, nil
|
||||
}
|
||||
167
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission_test.go
generated
vendored
Normal file
167
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package label
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
"k8s.io/kubernetes/pkg/types"
|
||||
)
|
||||
|
||||
type mockVolumes struct {
|
||||
volumeLabels map[string]string
|
||||
volumeLabelsError error
|
||||
}
|
||||
|
||||
var _ aws.Volumes = &mockVolumes{}
|
||||
|
||||
func (v *mockVolumes) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
|
||||
return v.volumeLabels, v.volumeLabelsError
|
||||
}
|
||||
|
||||
func (c *mockVolumes) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DiskIsAttached(volumeName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DisksAreAttached(diskNames []aws.KubernetesVolumeID, nodeName types.NodeName) (map[aws.KubernetesVolumeID]bool, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func mockVolumeFailure(err error) *mockVolumes {
|
||||
return &mockVolumes{volumeLabelsError: err}
|
||||
}
|
||||
|
||||
func mockVolumeLabels(labels map[string]string) *mockVolumes {
|
||||
return &mockVolumes{volumeLabels: labels}
|
||||
}
|
||||
|
||||
// TestAdmission
|
||||
func TestAdmission(t *testing.T) {
|
||||
pvHandler := NewPersistentVolumeLabel()
|
||||
handler := admission.NewChainHandler(pvHandler)
|
||||
ignoredPV := api.PersistentVolume{
|
||||
ObjectMeta: api.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
awsPV := api.PersistentVolume{
|
||||
ObjectMeta: api.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Non-cloud PVs are ignored
|
||||
err := handler.Admit(admission.NewAttributesRecord(&ignoredPV, nil, api.Kind("PersistentVolume").WithVersion("version"), ignoredPV.Namespace, ignoredPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler (on ignored pv): %v", err)
|
||||
}
|
||||
|
||||
// We only add labels on creation
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler (when deleting aws pv): %v", err)
|
||||
}
|
||||
|
||||
// Errors from the cloudprovider block creation of the volume
|
||||
pvHandler.ebsVolumes = mockVolumeFailure(fmt.Errorf("invalid volume"))
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when aws pv info fails")
|
||||
}
|
||||
|
||||
// Don't add labels if the cloudprovider doesn't return any
|
||||
labels := make(map[string]string)
|
||||
pvHandler.ebsVolumes = mockVolumeLabels(labels)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if len(awsPV.ObjectMeta.Labels) != 0 {
|
||||
t.Errorf("Unexpected number of labels")
|
||||
}
|
||||
|
||||
// Don't panic if the cloudprovider returns nil, nil
|
||||
pvHandler.ebsVolumes = mockVolumeFailure(nil)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when cloud provider returns empty labels")
|
||||
}
|
||||
|
||||
// Labels from the cloudprovider should be applied to the volume
|
||||
labels = make(map[string]string)
|
||||
labels["a"] = "1"
|
||||
labels["b"] = "2"
|
||||
pvHandler.ebsVolumes = mockVolumeLabels(labels)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
|
||||
t.Errorf("Expected label a to be added when creating aws pv")
|
||||
}
|
||||
|
||||
// User-provided labels should be honored, but cloudprovider labels replace them when they overlap
|
||||
awsPV.ObjectMeta.Labels = make(map[string]string)
|
||||
awsPV.ObjectMeta.Labels["a"] = "not1"
|
||||
awsPV.ObjectMeta.Labels["c"] = "3"
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
|
||||
t.Errorf("Expected cloudprovider labels to replace user labels when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["c"] != "3" {
|
||||
t.Errorf("Expected (non-conflicting) user provided labels to be honored when creating aws pv")
|
||||
}
|
||||
|
||||
}
|
||||
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// labels created persistent volumes with zone information
|
||||
// as provided by the cloud provider
|
||||
package label // import "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label"
|
||||
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/BUILD
generated
vendored
Normal 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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/labels:go_default_library",
|
||||
"//pkg/util/yaml:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/controller/informers:go_default_library",
|
||||
"//pkg/labels:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
],
|
||||
)
|
||||
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission.go
generated
vendored
Normal file
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
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 podnodeselector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/util/yaml"
|
||||
)
|
||||
|
||||
// The annotation key scheduler.alpha.kubernetes.io/node-selector is for assigning
|
||||
// node selectors labels to namespaces
|
||||
var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"}
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("PodNodeSelector", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
pluginConfig := readConfig(config)
|
||||
plugin := NewPodNodeSelector(client, pluginConfig.PodNodeSelectorPluginConfig)
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// podNodeSelector is an implementation of admission.Interface.
|
||||
type podNodeSelector struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
namespaceInformer cache.SharedIndexInformer
|
||||
// global default node selector and namespace whitelists in a cluster.
|
||||
clusterNodeSelectors map[string]string
|
||||
}
|
||||
|
||||
type pluginConfig struct {
|
||||
PodNodeSelectorPluginConfig map[string]string
|
||||
}
|
||||
|
||||
// readConfig reads default value of clusterDefaultNodeSelector
|
||||
// from the file provided with --admission-control-config-file
|
||||
// If the file is not supplied, it defaults to ""
|
||||
// The format in a file:
|
||||
// podNodeSelectorPluginConfig:
|
||||
// clusterDefaultNodeSelector: <node-selectors-labels>
|
||||
// namespace1: <node-selectors-labels>
|
||||
// namespace2: <node-selectors-labels>
|
||||
func readConfig(config io.Reader) *pluginConfig {
|
||||
defaultConfig := &pluginConfig{}
|
||||
if config == nil || reflect.ValueOf(config).IsNil() {
|
||||
return defaultConfig
|
||||
}
|
||||
d := yaml.NewYAMLOrJSONDecoder(config, 4096)
|
||||
for {
|
||||
if err := d.Decode(defaultConfig); err != nil {
|
||||
if err != io.EOF {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return defaultConfig
|
||||
}
|
||||
|
||||
// Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
|
||||
func (p *podNodeSelector) Admit(a admission.Attributes) error {
|
||||
resource := a.GetResource().GroupResource()
|
||||
if resource != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
if a.GetSubresource() != "" {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return nil
|
||||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
pod, ok := obj.(*api.Pod)
|
||||
if !ok {
|
||||
glog.Errorf("expected pod but got %s", a.GetKind().Kind)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
name := pod.Name
|
||||
nsName := a.GetNamespace()
|
||||
var namespace *api.Namespace
|
||||
|
||||
namespaceObj, exists, err := p.namespaceInformer.GetStore().Get(&api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: nsName,
|
||||
Namespace: "",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
if exists {
|
||||
namespace = namespaceObj.(*api.Namespace)
|
||||
} else {
|
||||
namespace, err = p.defaultGetNamespace(nsName)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
namespaceNodeSelector, err := p.getNodeSelectorMap(namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
|
||||
}
|
||||
|
||||
whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Merge pod node selector = namespace node selector + current pod node selector
|
||||
podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
|
||||
|
||||
// whitelist verification
|
||||
if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) {
|
||||
return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
|
||||
}
|
||||
|
||||
// Updated pod node selector = namespace node selector + current pod node selector
|
||||
pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPodNodeSelector(client clientset.Interface, clusterNodeSelectors map[string]string) *podNodeSelector {
|
||||
return &podNodeSelector{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
client: client,
|
||||
clusterNodeSelectors: clusterNodeSelectors,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) SetInformerFactory(f informers.SharedInformerFactory) {
|
||||
p.namespaceInformer = f.InternalNamespaces().Informer()
|
||||
p.SetReadyFunc(p.namespaceInformer.HasSynced)
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) Validate() error {
|
||||
if p.namespaceInformer == nil {
|
||||
return fmt.Errorf("missing namespaceInformer")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) defaultGetNamespace(name string) (*api.Namespace, error) {
|
||||
namespace, err := p.client.Core().Namespaces().Get(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("namespace %s does not exist", name)
|
||||
}
|
||||
return namespace, nil
|
||||
}
|
||||
|
||||
func (p *podNodeSelector) getNodeSelectorMap(namespace *api.Namespace) (labels.Set, error) {
|
||||
selector := labels.Set{}
|
||||
labelsMap := labels.Set{}
|
||||
var err error
|
||||
found := false
|
||||
if len(namespace.ObjectMeta.Annotations) > 0 {
|
||||
for _, annotation := range NamespaceNodeSelectors {
|
||||
if ns, ok := namespace.ObjectMeta.Annotations[annotation]; ok {
|
||||
labelsMap, err = labels.ConvertSelectorToLabelsMap(ns)
|
||||
if err != nil {
|
||||
return labels.Set{}, err
|
||||
}
|
||||
|
||||
if labels.Conflicts(selector, labelsMap) {
|
||||
nsName := namespace.ObjectMeta.Name
|
||||
return labels.Set{}, fmt.Errorf("%s annotations' node label selectors conflict", nsName)
|
||||
}
|
||||
selector = labels.Merge(selector, labelsMap)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
selector, err = labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors["clusterDefaultNodeSelector"])
|
||||
if err != nil {
|
||||
return labels.Set{}, err
|
||||
}
|
||||
}
|
||||
return selector, nil
|
||||
}
|
||||
188
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission_test.go
generated
vendored
Normal file
188
vendor/k8s.io/kubernetes/plugin/pkg/admission/podnodeselector/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
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 podnodeselector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
"k8s.io/kubernetes/pkg/controller/informers"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
// TestPodAdmission verifies various scenarios involving pod/namespace/global node label selectors
|
||||
func TestPodAdmission(t *testing.T) {
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "testNamespace",
|
||||
Namespace: "",
|
||||
},
|
||||
}
|
||||
|
||||
mockClient := &fake.Clientset{}
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "testPod", Namespace: "testNamespace"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
defaultNodeSelector string
|
||||
namespaceNodeSelector string
|
||||
whitelist string
|
||||
podNodeSelector map[string]string
|
||||
mergedNodeSelector labels.Set
|
||||
ignoreTestNamespaceNodeSelector bool
|
||||
admit bool
|
||||
testName string
|
||||
}{
|
||||
{
|
||||
defaultNodeSelector: "",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{},
|
||||
ignoreTestNamespaceNodeSelector: true,
|
||||
admit: true,
|
||||
testName: "No node selectors",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "false"},
|
||||
ignoreTestNamespaceNodeSelector: true,
|
||||
admit: true,
|
||||
testName: "Default node selector and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "",
|
||||
namespaceNodeSelector: " infra = false ",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "false"},
|
||||
admit: true,
|
||||
testName: "TestNamespace node selector with whitespaces and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{"infra": "true"},
|
||||
admit: true,
|
||||
testName: "Default and namespace node selector, no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "",
|
||||
podNodeSelector: map[string]string{},
|
||||
mergedNodeSelector: labels.Set{},
|
||||
admit: true,
|
||||
testName: "Empty namespace node selector and no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "infra = false",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{"env": "test"},
|
||||
mergedNodeSelector: labels.Set{"infra": "true", "env": "test"},
|
||||
admit: true,
|
||||
testName: "TestNamespace and pod node selector, no conflicts",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env = test",
|
||||
namespaceNodeSelector: "infra=true",
|
||||
podNodeSelector: map[string]string{"infra": "false"},
|
||||
admit: false,
|
||||
testName: "Conflicting pod and namespace node selector, one label",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = test",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
admit: false,
|
||||
testName: "Conflicting pod and namespace node selector, multiple labels",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = dev",
|
||||
whitelist: "env=dev, infra=false, color=blue",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
mergedNodeSelector: labels.Set{"infra": "false", "env": "dev", "color": "blue"},
|
||||
admit: true,
|
||||
testName: "Merged pod node selectors satisfy the whitelist",
|
||||
},
|
||||
{
|
||||
defaultNodeSelector: "env=dev",
|
||||
namespaceNodeSelector: "infra=false, env = dev",
|
||||
whitelist: "env=dev, infra=true, color=blue",
|
||||
podNodeSelector: map[string]string{"env": "dev", "color": "blue"},
|
||||
admit: false,
|
||||
testName: "Merged pod node selectors conflict with the whitelist",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if !test.ignoreTestNamespaceNodeSelector {
|
||||
namespace.ObjectMeta.Annotations = map[string]string{"scheduler.alpha.kubernetes.io/node-selector": test.namespaceNodeSelector}
|
||||
handler.namespaceInformer.GetStore().Update(namespace)
|
||||
}
|
||||
handler.clusterNodeSelectors = make(map[string]string)
|
||||
handler.clusterNodeSelectors["clusterDefaultNodeSelector"] = test.defaultNodeSelector
|
||||
handler.clusterNodeSelectors[namespace.Name] = test.whitelist
|
||||
pod.Spec = api.PodSpec{NodeSelector: test.podNodeSelector}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "testNamespace", namespace.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if test.admit && err != nil {
|
||||
t.Errorf("Test: %s, expected no error but got: %s", test.testName, err)
|
||||
} else if !test.admit && err == nil {
|
||||
t.Errorf("Test: %s, expected an error", test.testName)
|
||||
}
|
||||
|
||||
if test.admit && !labels.Equals(test.mergedNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
|
||||
t.Errorf("Test: %s, expected: %s but got: %s", test.testName, test.mergedNodeSelector, pod.Spec.NodeSelector)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
for op, shouldHandle := range map[admission.Operation]bool{
|
||||
admission.Create: true,
|
||||
admission.Update: false,
|
||||
admission.Connect: false,
|
||||
admission.Delete: false,
|
||||
} {
|
||||
nodeEnvionment := NewPodNodeSelector(nil, nil)
|
||||
if e, a := shouldHandle, nodeEnvionment.Handles(op); e != a {
|
||||
t.Errorf("%v: shouldHandle=%t, handles=%t", op, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (*podNodeSelector, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(nil, c, 5*time.Minute)
|
||||
handler := NewPodNodeSelector(c, nil)
|
||||
plugins := []admission.Interface{handler}
|
||||
pluginInitializer := admission.NewPluginInitializer(f, nil)
|
||||
pluginInitializer.Initialize(plugins)
|
||||
err := admission.Validate(plugins)
|
||||
return handler, f, err
|
||||
}
|
||||
65
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/BUILD
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"controller.go",
|
||||
"doc.go",
|
||||
"resource_access.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/meta:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/quota:go_default_library",
|
||||
"//pkg/quota/install:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/storage/etcd:go_default_library",
|
||||
"//pkg/util/runtime:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
"//pkg/util/workqueue:go_default_library",
|
||||
"//pkg/util/workqueue/prometheus:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/hashicorp/golang-lru",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/quota:go_default_library",
|
||||
"//pkg/quota/evaluator/core:go_default_library",
|
||||
"//pkg/quota/generic:go_default_library",
|
||||
"//pkg/quota/install:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//vendor:github.com/hashicorp/golang-lru",
|
||||
],
|
||||
)
|
||||
79
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/admission.go
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package resourcequota
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/quota"
|
||||
"k8s.io/kubernetes/pkg/quota/install"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("ResourceQuota",
|
||||
func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
// NOTE: we do not provide informers to the registry because admission level decisions
|
||||
// does not require us to open watches for all items tracked by quota.
|
||||
registry := install.NewRegistry(nil, nil)
|
||||
return NewResourceQuota(client, registry, 5, make(chan struct{}))
|
||||
})
|
||||
}
|
||||
|
||||
// quotaAdmission implements an admission controller that can enforce quota constraints
|
||||
type quotaAdmission struct {
|
||||
*admission.Handler
|
||||
|
||||
evaluator Evaluator
|
||||
}
|
||||
|
||||
type liveLookupEntry struct {
|
||||
expiry time.Time
|
||||
items []*api.ResourceQuota
|
||||
}
|
||||
|
||||
// NewResourceQuota configures an admission controller that can enforce quota constraints
|
||||
// using the provided registry. The registry must have the capability to handle group/kinds that
|
||||
// are persisted by the server this admission controller is intercepting
|
||||
func NewResourceQuota(client clientset.Interface, registry quota.Registry, numEvaluators int, stopCh <-chan struct{}) (admission.Interface, error) {
|
||||
quotaAccessor, err := newQuotaAccessor(client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go quotaAccessor.Run(stopCh)
|
||||
|
||||
evaluator := NewQuotaEvaluator(quotaAccessor, registry, nil, numEvaluators, stopCh)
|
||||
|
||||
return "aAdmission{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
evaluator: evaluator,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Admit makes admission decisions while enforcing quota
|
||||
func (q *quotaAdmission) Admit(a admission.Attributes) (err error) {
|
||||
// ignore all operations that correspond to sub-resource actions
|
||||
if a.GetSubresource() != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return q.evaluator.Evaluate(a)
|
||||
}
|
||||
1052
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/admission_test.go
generated
vendored
Normal file
1052
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/admission_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
556
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/controller.go
generated
vendored
Normal file
556
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/controller.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// resourcequota enforces all incoming requests against any applied quota
|
||||
// in the namespace context of the request
|
||||
package resourcequota // import "k8s.io/kubernetes/plugin/pkg/admission/resourcequota"
|
||||
184
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/resource_access.go
generated
vendored
Normal file
184
vendor/k8s.io/kubernetes/plugin/pkg/admission/resourcequota/resource_access.go
generated
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
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 resourcequota
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/storage/etcd"
|
||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// QuotaAccessor abstracts the get/set logic from the rest of the Evaluator. This could be a test stub, a straight passthrough,
|
||||
// or most commonly a series of deconflicting caches.
|
||||
type QuotaAccessor interface {
|
||||
// UpdateQuotaStatus is called to persist final status. This method should write to persistent storage.
|
||||
// An error indicates that write didn't complete successfully.
|
||||
UpdateQuotaStatus(newQuota *api.ResourceQuota) error
|
||||
|
||||
// GetQuotas gets all possible quotas for a given namespace
|
||||
GetQuotas(namespace string) ([]api.ResourceQuota, error)
|
||||
}
|
||||
|
||||
type quotaAccessor struct {
|
||||
client clientset.Interface
|
||||
|
||||
// indexer that holds quota objects by namespace
|
||||
indexer cache.Indexer
|
||||
reflector *cache.Reflector
|
||||
|
||||
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
|
||||
// This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
|
||||
// We track the lookup result here so that for repeated requests, we don't look it up very often.
|
||||
liveLookupCache *lru.Cache
|
||||
liveTTL time.Duration
|
||||
// updatedQuotas holds a cache of quotas that we've updated. This is used to pull the "really latest" during back to
|
||||
// back quota evaluations that touch the same quota doc. This only works because we can compare etcd resourceVersions
|
||||
// for the same resource as integers. Before this change: 22 updates with 12 conflicts. after this change: 15 updates with 0 conflicts
|
||||
updatedQuotas *lru.Cache
|
||||
}
|
||||
|
||||
// newQuotaAccessor creates an object that conforms to the QuotaAccessor interface to be used to retrieve quota objects.
|
||||
func newQuotaAccessor(client clientset.Interface) (*quotaAccessor, error) {
|
||||
liveLookupCache, err := lru.New(100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updatedCache, err := lru.New(100)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lw := &cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return client.Core().ResourceQuotas(api.NamespaceAll).List(internalOptions)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return client.Core().ResourceQuotas(api.NamespaceAll).Watch(internalOptions)
|
||||
},
|
||||
}
|
||||
indexer, reflector := cache.NewNamespaceKeyedIndexerAndReflector(lw, &api.ResourceQuota{}, 0)
|
||||
|
||||
return "aAccessor{
|
||||
client: client,
|
||||
indexer: indexer,
|
||||
reflector: reflector,
|
||||
liveLookupCache: liveLookupCache,
|
||||
liveTTL: time.Duration(30 * time.Second),
|
||||
updatedQuotas: updatedCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Run begins watching and syncing.
|
||||
func (e *quotaAccessor) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
e.reflector.RunUntil(stopCh)
|
||||
|
||||
<-stopCh
|
||||
glog.Infof("Shutting down quota accessor")
|
||||
}
|
||||
|
||||
func (e *quotaAccessor) UpdateQuotaStatus(newQuota *api.ResourceQuota) error {
|
||||
updatedQuota, err := e.client.Core().ResourceQuotas(newQuota.Namespace).UpdateStatus(newQuota)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key := newQuota.Namespace + "/" + newQuota.Name
|
||||
e.updatedQuotas.Add(key, updatedQuota)
|
||||
return nil
|
||||
}
|
||||
|
||||
var etcdVersioner = etcd.APIObjectVersioner{}
|
||||
|
||||
// checkCache compares the passed quota against the value in the look-aside cache and returns the newer
|
||||
// if the cache is out of date, it deletes the stale entry. This only works because of etcd resourceVersions
|
||||
// being monotonically increasing integers
|
||||
func (e *quotaAccessor) checkCache(quota *api.ResourceQuota) *api.ResourceQuota {
|
||||
key := quota.Namespace + "/" + quota.Name
|
||||
uncastCachedQuota, ok := e.updatedQuotas.Get(key)
|
||||
if !ok {
|
||||
return quota
|
||||
}
|
||||
cachedQuota := uncastCachedQuota.(*api.ResourceQuota)
|
||||
|
||||
if etcdVersioner.CompareResourceVersion(quota, cachedQuota) >= 0 {
|
||||
e.updatedQuotas.Remove(key)
|
||||
return quota
|
||||
}
|
||||
return cachedQuota
|
||||
}
|
||||
|
||||
func (e *quotaAccessor) GetQuotas(namespace string) ([]api.ResourceQuota, error) {
|
||||
// determine if there are any quotas in this namespace
|
||||
// if there are no quotas, we don't need to do anything
|
||||
items, err := e.indexer.Index("namespace", &api.ResourceQuota{ObjectMeta: api.ObjectMeta{Namespace: namespace, Name: ""}})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error resolving quota.")
|
||||
}
|
||||
|
||||
// if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it.
|
||||
if len(items) == 0 {
|
||||
lruItemObj, ok := e.liveLookupCache.Get(namespace)
|
||||
if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
|
||||
// TODO: If there are multiple operations at the same time and cache has just expired,
|
||||
// this may cause multiple List operations being issued at the same time.
|
||||
// If there is already in-flight List() for a given namespace, we should wait until
|
||||
// it is finished and cache is updated instead of doing the same, also to avoid
|
||||
// throttling - see #22422 for details.
|
||||
liveList, err := e.client.Core().ResourceQuotas(namespace).List(api.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newEntry := liveLookupEntry{expiry: time.Now().Add(e.liveTTL)}
|
||||
for i := range liveList.Items {
|
||||
newEntry.items = append(newEntry.items, &liveList.Items[i])
|
||||
}
|
||||
e.liveLookupCache.Add(namespace, newEntry)
|
||||
lruItemObj = newEntry
|
||||
}
|
||||
lruEntry := lruItemObj.(liveLookupEntry)
|
||||
for i := range lruEntry.items {
|
||||
items = append(items, lruEntry.items[i])
|
||||
}
|
||||
}
|
||||
|
||||
resourceQuotas := []api.ResourceQuota{}
|
||||
for i := range items {
|
||||
quota := items[i].(*api.ResourceQuota)
|
||||
quota = e.checkCache(quota)
|
||||
// always make a copy. We're going to muck around with this and we should never mutate the originals
|
||||
resourceQuotas = append(resourceQuotas, *quota)
|
||||
}
|
||||
|
||||
return resourceQuotas, nil
|
||||
}
|
||||
17
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/BUILD
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
||||
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// security contains admission plugins specific to cluster security.
|
||||
package security // import "k8s.io/kubernetes/plugin/pkg/admission/security"
|
||||
61
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/BUILD
generated
vendored
Normal file
61
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy/util:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/maps:go_default_library",
|
||||
"//pkg/util/validation/field:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/auth/authorizer:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/security/apparmor:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy/seccomp:go_default_library",
|
||||
"//pkg/security/podsecuritypolicy/util:go_default_library",
|
||||
"//pkg/util/diff:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
)
|
||||
371
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/admission.go
generated
vendored
Normal file
371
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
psp "k8s.io/kubernetes/pkg/security/podsecuritypolicy"
|
||||
psputil "k8s.io/kubernetes/pkg/security/podsecuritypolicy/util"
|
||||
sc "k8s.io/kubernetes/pkg/securitycontext"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/util/maps"
|
||||
"k8s.io/kubernetes/pkg/util/validation/field"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginName = "PodSecurityPolicy"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
plugin := NewPlugin(client, psp.NewSimpleStrategyFactory(), getMatchingPolicies, true)
|
||||
plugin.Run()
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// PSPMatchFn allows plugging in how PSPs are matched against user information.
|
||||
type PSPMatchFn func(store cache.Store, user user.Info, sa user.Info, authz authorizer.Authorizer) ([]*extensions.PodSecurityPolicy, error)
|
||||
|
||||
// podSecurityPolicyPlugin holds state for and implements the admission plugin.
|
||||
type podSecurityPolicyPlugin struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
strategyFactory psp.StrategyFactory
|
||||
pspMatcher PSPMatchFn
|
||||
failOnNoPolicies bool
|
||||
authz authorizer.Authorizer
|
||||
|
||||
reflector *cache.Reflector
|
||||
stopChan chan struct{}
|
||||
store cache.Store
|
||||
}
|
||||
|
||||
// SetAuthorizer sets the authorizer.
|
||||
func (plugin *podSecurityPolicyPlugin) SetAuthorizer(authz authorizer.Authorizer) {
|
||||
plugin.authz = authz
|
||||
}
|
||||
|
||||
// Validate ensures an authorizer is set.
|
||||
func (plugin *podSecurityPolicyPlugin) Validate() error {
|
||||
if plugin.authz == nil {
|
||||
return fmt.Errorf("%s requires an authorizer", PluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ admission.Interface = &podSecurityPolicyPlugin{}
|
||||
var _ admission.WantsAuthorizer = &podSecurityPolicyPlugin{}
|
||||
|
||||
// NewPlugin creates a new PSP admission plugin.
|
||||
func NewPlugin(kclient clientset.Interface, strategyFactory psp.StrategyFactory, pspMatcher PSPMatchFn, failOnNoPolicies bool) *podSecurityPolicyPlugin {
|
||||
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||
reflector := cache.NewReflector(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return kclient.Extensions().PodSecurityPolicies().List(internalOptions)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return kclient.Extensions().PodSecurityPolicies().Watch(internalOptions)
|
||||
},
|
||||
},
|
||||
&extensions.PodSecurityPolicy{},
|
||||
store,
|
||||
0,
|
||||
)
|
||||
|
||||
return &podSecurityPolicyPlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
client: kclient,
|
||||
strategyFactory: strategyFactory,
|
||||
pspMatcher: pspMatcher,
|
||||
failOnNoPolicies: failOnNoPolicies,
|
||||
|
||||
store: store,
|
||||
reflector: reflector,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *podSecurityPolicyPlugin) Run() {
|
||||
if a.stopChan == nil {
|
||||
a.stopChan = make(chan struct{})
|
||||
}
|
||||
a.reflector.RunUntil(a.stopChan)
|
||||
}
|
||||
func (a *podSecurityPolicyPlugin) Stop() {
|
||||
if a.stopChan != nil {
|
||||
close(a.stopChan)
|
||||
a.stopChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Admit determines if the pod should be admitted based on the requested security context
|
||||
// and the available PSPs.
|
||||
//
|
||||
// 1. Find available PSPs.
|
||||
// 2. Create the providers, includes setting pre-allocated values if necessary.
|
||||
// 3. Try to generate and validate a PSP with providers. If we find one then admit the pod
|
||||
// with the validated PSP. If we don't find any reject the pod and give all errors from the
|
||||
// failed attempts.
|
||||
func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error {
|
||||
if a.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.GetSubresource()) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
// if we can't convert then we don't handle this object so just return
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get all constraints that are usable by the user
|
||||
glog.V(4).Infof("getting pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName)
|
||||
var saInfo user.Info
|
||||
if len(pod.Spec.ServiceAccountName) > 0 {
|
||||
saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "")
|
||||
}
|
||||
|
||||
matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo, c.authz)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
// if we have no policies and want to succeed then return. Otherwise we'll end up with no
|
||||
// providers and fail with "unable to validate against any pod security policy" below.
|
||||
if len(matchedPolicies) == 0 && !c.failOnNoPolicies {
|
||||
return nil
|
||||
}
|
||||
|
||||
providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace)
|
||||
logProviders(pod, providers, errs)
|
||||
|
||||
if len(providers) == 0 {
|
||||
return admission.NewForbidden(a, fmt.Errorf("no providers available to validate pod request"))
|
||||
}
|
||||
|
||||
// all containers in a single pod must validate under a single provider or we will reject the request
|
||||
validationErrs := field.ErrorList{}
|
||||
for _, provider := range providers {
|
||||
if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetPSPName()))); len(errs) > 0 {
|
||||
validationErrs = append(validationErrs, errs...)
|
||||
continue
|
||||
}
|
||||
|
||||
// the entire pod validated, annotate and accept the pod
|
||||
glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetPSPName())
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName()
|
||||
return nil
|
||||
}
|
||||
|
||||
// we didn't validate against any provider, reject the pod and give the errors for each attempt
|
||||
glog.V(4).Infof("unable to validate pod %s (generate: %s) against any pod security policy: %v", pod.Name, pod.GenerateName, validationErrs)
|
||||
return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs))
|
||||
}
|
||||
|
||||
// assignSecurityContext creates a security context for each container in the pod
|
||||
// and validates that the sc falls within the psp constraints. All containers must validate against
|
||||
// the same psp or is not considered valid.
|
||||
func assignSecurityContext(provider psp.Provider, pod *api.Pod, fldPath *field.Path) field.ErrorList {
|
||||
generatedSCs := make([]*api.SecurityContext, len(pod.Spec.Containers))
|
||||
var generatedInitSCs []*api.SecurityContext
|
||||
|
||||
errs := field.ErrorList{}
|
||||
|
||||
psc, pscAnnotations, err := provider.CreatePodSecurityContext(pod)
|
||||
if err != nil {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "securityContext"), pod.Spec.SecurityContext, err.Error()))
|
||||
}
|
||||
|
||||
// save the original PSC and validate the generated PSC. Leave the generated PSC
|
||||
// set for container generation/validation. We will reset to original post container
|
||||
// validation.
|
||||
originalPSC := pod.Spec.SecurityContext
|
||||
pod.Spec.SecurityContext = psc
|
||||
originalAnnotations := maps.CopySS(pod.Annotations)
|
||||
pod.Annotations = pscAnnotations
|
||||
errs = append(errs, provider.ValidatePodSecurityContext(pod, field.NewPath("spec", "securityContext"))...)
|
||||
|
||||
// Note: this is not changing the original container, we will set container SCs later so long
|
||||
// as all containers validated under the same PSP.
|
||||
for i, containerCopy := range pod.Spec.InitContainers {
|
||||
// We will determine the effective security context for the container and validate against that
|
||||
// since that is how the sc provider will eventually apply settings in the runtime.
|
||||
// This results in an SC that is based on the Pod's PSC with the set fields from the container
|
||||
// overriding pod level settings.
|
||||
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
|
||||
|
||||
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
|
||||
if err != nil {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "initContainers").Index(i).Child("securityContext"), "", err.Error()))
|
||||
continue
|
||||
}
|
||||
generatedInitSCs = append(generatedInitSCs, sc)
|
||||
|
||||
containerCopy.SecurityContext = sc
|
||||
pod.Annotations = scAnnotations
|
||||
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "initContainers").Index(i).Child("securityContext"))...)
|
||||
}
|
||||
|
||||
// Note: this is not changing the original container, we will set container SCs later so long
|
||||
// as all containers validated under the same PSP.
|
||||
for i, containerCopy := range pod.Spec.Containers {
|
||||
// We will determine the effective security context for the container and validate against that
|
||||
// since that is how the sc provider will eventually apply settings in the runtime.
|
||||
// This results in an SC that is based on the Pod's PSC with the set fields from the container
|
||||
// overriding pod level settings.
|
||||
containerCopy.SecurityContext = sc.InternalDetermineEffectiveSecurityContext(pod, &containerCopy)
|
||||
|
||||
sc, scAnnotations, err := provider.CreateContainerSecurityContext(pod, &containerCopy)
|
||||
if err != nil {
|
||||
errs = append(errs, field.Invalid(field.NewPath("spec", "containers").Index(i).Child("securityContext"), "", err.Error()))
|
||||
continue
|
||||
}
|
||||
generatedSCs[i] = sc
|
||||
|
||||
containerCopy.SecurityContext = sc
|
||||
pod.Annotations = scAnnotations
|
||||
errs = append(errs, provider.ValidateContainerSecurityContext(pod, &containerCopy, field.NewPath("spec", "containers").Index(i).Child("securityContext"))...)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
// ensure psc is not mutated if there are errors
|
||||
pod.Spec.SecurityContext = originalPSC
|
||||
pod.Annotations = originalAnnotations
|
||||
return errs
|
||||
}
|
||||
|
||||
// if we've reached this code then we've generated and validated an SC for every container in the
|
||||
// pod so let's apply what we generated. Note: the psc is already applied.
|
||||
for i, sc := range generatedInitSCs {
|
||||
pod.Spec.InitContainers[i].SecurityContext = sc
|
||||
}
|
||||
for i, sc := range generatedSCs {
|
||||
pod.Spec.Containers[i].SecurityContext = sc
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// createProvidersFromPolicies creates providers from the constraints supplied.
|
||||
func (c *podSecurityPolicyPlugin) createProvidersFromPolicies(psps []*extensions.PodSecurityPolicy, namespace string) ([]psp.Provider, []error) {
|
||||
var (
|
||||
// collected providers
|
||||
providers []psp.Provider
|
||||
// collected errors to return
|
||||
errs []error
|
||||
)
|
||||
|
||||
for _, constraint := range psps {
|
||||
provider, err := psp.NewSimpleProvider(constraint, namespace, c.strategyFactory)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("error creating provider for PSP %s: %v", constraint.Name, err))
|
||||
continue
|
||||
}
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
return providers, errs
|
||||
}
|
||||
|
||||
// getMatchingPolicies returns policies from the store. For now this returns everything
|
||||
// in the future it can filter based on UserInfo and permissions.
|
||||
//
|
||||
// TODO: this will likely need optimization since the initial implementation will
|
||||
// always query for authorization. Needs scale testing and possibly checking against
|
||||
// a cache.
|
||||
func getMatchingPolicies(store cache.Store, user user.Info, sa user.Info, authz authorizer.Authorizer) ([]*extensions.PodSecurityPolicy, error) {
|
||||
matchedPolicies := make([]*extensions.PodSecurityPolicy, 0)
|
||||
|
||||
for _, c := range store.List() {
|
||||
constraint, ok := c.(*extensions.PodSecurityPolicy)
|
||||
if !ok {
|
||||
return nil, errors.NewInternalError(fmt.Errorf("error converting object from store to a pod security policy: %v", c))
|
||||
}
|
||||
|
||||
if authorizedForPolicy(user, constraint, authz) || authorizedForPolicy(sa, constraint, authz) {
|
||||
matchedPolicies = append(matchedPolicies, constraint)
|
||||
}
|
||||
}
|
||||
|
||||
return matchedPolicies, nil
|
||||
}
|
||||
|
||||
// authorizedForPolicy returns true if info is authorized to perform a "get" on policy.
|
||||
func authorizedForPolicy(info user.Info, policy *extensions.PodSecurityPolicy, authz authorizer.Authorizer) bool {
|
||||
// if no info exists then the API is being hit via the unsecured port. In this case
|
||||
// authorize the request.
|
||||
if info == nil {
|
||||
return true
|
||||
}
|
||||
attr := buildAttributes(info, policy)
|
||||
allowed, _, _ := authz.Authorize(attr)
|
||||
return allowed
|
||||
}
|
||||
|
||||
// buildAttributes builds an attributes record for a SAR based on the user info and policy.
|
||||
func buildAttributes(info user.Info, policy *extensions.PodSecurityPolicy) authorizer.Attributes {
|
||||
// TODO consider checking against the namespace that the pod is being
|
||||
// created in to allow per-namespace PSP definitions.
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: info,
|
||||
Verb: "use",
|
||||
Name: policy.Name,
|
||||
APIGroup: extensions.GroupName,
|
||||
Resource: "podsecuritypolicies",
|
||||
ResourceRequest: true,
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// logProviders logs what providers were found for the pod as well as any errors that were encountered
|
||||
// while creating providers.
|
||||
func logProviders(pod *api.Pod, providers []psp.Provider, providerCreationErrs []error) {
|
||||
names := make([]string, len(providers))
|
||||
for i, p := range providers {
|
||||
names[i] = p.GetPSPName()
|
||||
}
|
||||
glog.V(4).Infof("validating pod %s (generate: %s) against providers %s", pod.Name, pod.GenerateName, strings.Join(names, ","))
|
||||
|
||||
for _, err := range providerCreationErrs {
|
||||
glog.V(4).Infof("provider creation error: %v", err)
|
||||
}
|
||||
}
|
||||
1739
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go
generated
vendored
Normal file
1739
vendor/k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy/admission_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/BUILD
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
],
|
||||
)
|
||||
99
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/admission.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package scdeny
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("SecurityContextDeny", func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewSecurityContextDeny(client), nil
|
||||
})
|
||||
}
|
||||
|
||||
// plugin contains the client used by the SecurityContextDeny admission controller
|
||||
type plugin struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
}
|
||||
|
||||
// NewSecurityContextDeny creates a new instance of the SecurityContextDeny admission controller
|
||||
func NewSecurityContextDeny(client clientset.Interface) admission.Interface {
|
||||
return &plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// Admit will deny any pod that defines SELinuxOptions or RunAsUser.
|
||||
func (p *plugin) Admit(a admission.Attributes) (err error) {
|
||||
if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SupplementalGroups != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SupplementalGroups is forbidden"))
|
||||
}
|
||||
if pod.Spec.SecurityContext != nil {
|
||||
if pod.Spec.SecurityContext.SELinuxOptions != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.SELinuxOptions is forbidden"))
|
||||
}
|
||||
if pod.Spec.SecurityContext.RunAsUser != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.RunAsUser is forbidden"))
|
||||
}
|
||||
}
|
||||
|
||||
if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.FSGroup != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.FSGroup is forbidden"))
|
||||
}
|
||||
|
||||
for _, v := range pod.Spec.InitContainers {
|
||||
if v.SecurityContext != nil {
|
||||
if v.SecurityContext.SELinuxOptions != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden"))
|
||||
}
|
||||
if v.SecurityContext.RunAsUser != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range pod.Spec.Containers {
|
||||
if v.SecurityContext != nil {
|
||||
if v.SecurityContext.SELinuxOptions != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden"))
|
||||
}
|
||||
if v.SecurityContext.RunAsUser != nil {
|
||||
return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
179
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/admission_test.go
generated
vendored
Normal file
179
vendor/k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package scdeny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// ensures the SecurityContext is denied if it defines anything more than Caps or Privileged
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewSecurityContextDeny(nil)
|
||||
|
||||
var runAsUser int64 = 1
|
||||
priv := true
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
sc *api.SecurityContext
|
||||
podSc *api.PodSecurityContext
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
},
|
||||
{
|
||||
name: "empty container.SecurityContext",
|
||||
sc: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
name: "empty pod.Spec.SecurityContext",
|
||||
podSc: &api.PodSecurityContext{},
|
||||
},
|
||||
{
|
||||
name: "valid container.SecurityContext",
|
||||
sc: &api.SecurityContext{Privileged: &priv, Capabilities: &api.Capabilities{}},
|
||||
},
|
||||
{
|
||||
name: "valid pod.Spec.SecurityContext",
|
||||
podSc: &api.PodSecurityContext{},
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.RunAsUser",
|
||||
sc: &api.SecurityContext{RunAsUser: &runAsUser},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.SELinuxOptions",
|
||||
sc: &api.SecurityContext{SELinuxOptions: &api.SELinuxOptions{}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "pod.Spec.SecurityContext.RunAsUser",
|
||||
podSc: &api.PodSecurityContext{RunAsUser: &runAsUser},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "pod.Spec.SecurityContext.SELinuxOptions",
|
||||
podSc: &api.PodSecurityContext{SELinuxOptions: &api.SELinuxOptions{}},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
p := pod()
|
||||
p.Spec.SecurityContext = tc.podSc
|
||||
p.Spec.Containers[0].SecurityContext = tc.sc
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil && !tc.expectError {
|
||||
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
||||
} else if err == nil && tc.expectError {
|
||||
t.Errorf("%v: expected error", tc.name)
|
||||
}
|
||||
|
||||
// verify init containers are also checked
|
||||
p = pod()
|
||||
p.Spec.SecurityContext = tc.podSc
|
||||
p.Spec.Containers[0].SecurityContext = tc.sc
|
||||
p.Spec.InitContainers = p.Spec.Containers
|
||||
p.Spec.Containers = nil
|
||||
|
||||
err = handler.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil && !tc.expectError {
|
||||
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
||||
} else if err == nil && tc.expectError {
|
||||
t.Errorf("%v: expected error", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodSecurityContextAdmission(t *testing.T) {
|
||||
handler := NewSecurityContextDeny(nil)
|
||||
pod := api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
fsGroup := int64(1001)
|
||||
|
||||
tests := []struct {
|
||||
securityContext api.PodSecurityContext
|
||||
errorExpected bool
|
||||
}{
|
||||
{
|
||||
securityContext: api.PodSecurityContext{},
|
||||
errorExpected: false,
|
||||
},
|
||||
{
|
||||
securityContext: api.PodSecurityContext{
|
||||
SupplementalGroups: []int64{1234},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
{
|
||||
securityContext: api.PodSecurityContext{
|
||||
FSGroup: &fsGroup,
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
pod.Spec.SecurityContext = &test.securityContext
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
|
||||
if test.errorExpected && err == nil {
|
||||
t.Errorf("Expected error for security context %+v but did not get an error", test.securityContext)
|
||||
}
|
||||
|
||||
if !test.errorExpected && err != nil {
|
||||
t.Errorf("Unexpected error %v for security context %+v", err, test.securityContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewSecurityContextDeny(nil)
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func pod() *api.Pod {
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/BUILD
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
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 = [
|
||||
"admission.go",
|
||||
"doc.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/runtime/schema:go_default_library",
|
||||
"//pkg/serviceaccount:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/types:go_default_library",
|
||||
],
|
||||
)
|
||||
463
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission.go
generated
vendored
Normal file
463
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package serviceaccount
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
kubelet "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
// DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account
|
||||
const DefaultServiceAccountName = "default"
|
||||
|
||||
// EnforceMountableSecretsAnnotation is a default annotation that indicates that a service account should enforce mountable secrets.
|
||||
// The value must be true to have this annotation take effect
|
||||
const EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
|
||||
|
||||
// DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to.
|
||||
// The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount
|
||||
const DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
|
||||
// PluginName is the name of this admission plugin
|
||||
const PluginName = "ServiceAccount"
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
serviceAccountAdmission := NewServiceAccount(client)
|
||||
serviceAccountAdmission.Run()
|
||||
return serviceAccountAdmission, nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = admission.Interface(&serviceAccount{})
|
||||
|
||||
type serviceAccount struct {
|
||||
*admission.Handler
|
||||
|
||||
// LimitSecretReferences rejects pods that reference secrets their service accounts do not reference
|
||||
LimitSecretReferences bool
|
||||
// RequireAPIToken determines whether pod creation attempts are rejected if no API token exists for the pod's service account
|
||||
RequireAPIToken bool
|
||||
// MountServiceAccountToken creates Volume and VolumeMounts for the first referenced ServiceAccountToken for the pod's service account
|
||||
MountServiceAccountToken bool
|
||||
|
||||
client clientset.Interface
|
||||
|
||||
serviceAccounts cache.Indexer
|
||||
secrets cache.Indexer
|
||||
|
||||
stopChan chan struct{}
|
||||
serviceAccountsReflector *cache.Reflector
|
||||
secretsReflector *cache.Reflector
|
||||
}
|
||||
|
||||
// NewServiceAccount returns an admission.Interface implementation which limits admission of Pod CREATE requests based on the pod's ServiceAccount:
|
||||
// 1. If the pod does not specify a ServiceAccount, it sets the pod's ServiceAccount to "default"
|
||||
// 2. It ensures the ServiceAccount referenced by the pod exists
|
||||
// 3. If LimitSecretReferences is true, it rejects the pod if the pod references Secret objects which the pod's ServiceAccount does not reference
|
||||
// 4. If the pod does not contain any ImagePullSecrets, the ImagePullSecrets of the service account are added.
|
||||
// 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers
|
||||
func NewServiceAccount(cl clientset.Interface) *serviceAccount {
|
||||
serviceAccountsIndexer, serviceAccountsReflector := cache.NewNamespaceKeyedIndexerAndReflector(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return cl.Core().ServiceAccounts(api.NamespaceAll).List(internalOptions)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return cl.Core().ServiceAccounts(api.NamespaceAll).Watch(internalOptions)
|
||||
},
|
||||
},
|
||||
&api.ServiceAccount{},
|
||||
0,
|
||||
)
|
||||
|
||||
tokenSelector := fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(api.SecretTypeServiceAccountToken)})
|
||||
secretsIndexer, secretsReflector := cache.NewNamespaceKeyedIndexerAndReflector(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
internalOptions.FieldSelector = tokenSelector
|
||||
return cl.Core().Secrets(api.NamespaceAll).List(internalOptions)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
internalOptions.FieldSelector = tokenSelector
|
||||
return cl.Core().Secrets(api.NamespaceAll).Watch(internalOptions)
|
||||
},
|
||||
},
|
||||
&api.Secret{},
|
||||
0,
|
||||
)
|
||||
|
||||
return &serviceAccount{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
// TODO: enable this once we've swept secret usage to account for adding secret references to service accounts
|
||||
LimitSecretReferences: false,
|
||||
// Auto mount service account API token secrets
|
||||
MountServiceAccountToken: true,
|
||||
// Reject pod creation until a service account token is available
|
||||
RequireAPIToken: true,
|
||||
|
||||
client: cl,
|
||||
serviceAccounts: serviceAccountsIndexer,
|
||||
serviceAccountsReflector: serviceAccountsReflector,
|
||||
secrets: secretsIndexer,
|
||||
secretsReflector: secretsReflector,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceAccount) Run() {
|
||||
if s.stopChan == nil {
|
||||
s.stopChan = make(chan struct{})
|
||||
s.serviceAccountsReflector.RunUntil(s.stopChan)
|
||||
s.secretsReflector.RunUntil(s.stopChan)
|
||||
}
|
||||
}
|
||||
func (s *serviceAccount) Stop() {
|
||||
if s.stopChan != nil {
|
||||
close(s.stopChan)
|
||||
s.stopChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
|
||||
if a.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
obj := a.GetObject()
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
pod, ok := obj.(*api.Pod)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Don't modify the spec of mirror pods.
|
||||
// That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
|
||||
// That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
|
||||
if _, isMirrorPod := pod.Annotations[kubelet.ConfigMirrorAnnotationKey]; isMirrorPod {
|
||||
if len(pod.Spec.ServiceAccountName) != 0 {
|
||||
return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
|
||||
}
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
if volume.VolumeSource.Secret != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the default service account if needed
|
||||
if len(pod.Spec.ServiceAccountName) == 0 {
|
||||
pod.Spec.ServiceAccountName = DefaultServiceAccountName
|
||||
}
|
||||
|
||||
// Ensure the referenced service account exists
|
||||
serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
|
||||
}
|
||||
if serviceAccount == nil {
|
||||
// TODO: convert to a ServerTimeout error (or other error that sends a Retry-After header)
|
||||
return admission.NewForbidden(a, fmt.Errorf("service account %s/%s was not found, retry after the service account is created", a.GetNamespace(), pod.Spec.ServiceAccountName))
|
||||
}
|
||||
|
||||
if s.enforceMountableSecrets(serviceAccount) {
|
||||
if err := s.limitSecretReferences(serviceAccount, pod); err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
}
|
||||
|
||||
if s.MountServiceAccountToken {
|
||||
if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
|
||||
if _, ok := err.(errors.APIStatus); ok {
|
||||
return err
|
||||
}
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(pod.Spec.ImagePullSecrets) == 0 {
|
||||
pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
|
||||
copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// enforceMountableSecrets indicates whether mountable secrets should be enforced for a particular service account
|
||||
// A global setting of true will override any flag set on the individual service account
|
||||
func (s *serviceAccount) enforceMountableSecrets(serviceAccount *api.ServiceAccount) bool {
|
||||
if s.LimitSecretReferences {
|
||||
return true
|
||||
}
|
||||
|
||||
if value, ok := serviceAccount.Annotations[EnforceMountableSecretsAnnotation]; ok {
|
||||
enforceMountableSecretCheck, _ := strconv.ParseBool(value)
|
||||
return enforceMountableSecretCheck
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// getServiceAccount returns the ServiceAccount for the given namespace and name if it exists
|
||||
func (s *serviceAccount) getServiceAccount(namespace string, name string) (*api.ServiceAccount, error) {
|
||||
key := &api.ServiceAccount{ObjectMeta: api.ObjectMeta{Namespace: namespace}}
|
||||
index, err := s.serviceAccounts.Index("namespace", key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, obj := range index {
|
||||
serviceAccount := obj.(*api.ServiceAccount)
|
||||
if serviceAccount.Name == name {
|
||||
return serviceAccount, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find in cache, attempt to look up directly
|
||||
numAttempts := 1
|
||||
if name == DefaultServiceAccountName {
|
||||
// If this is the default serviceaccount, attempt more times, since it should be auto-created by the controller
|
||||
numAttempts = 10
|
||||
}
|
||||
retryInterval := time.Duration(rand.Int63n(100)+int64(100)) * time.Millisecond
|
||||
for i := 0; i < numAttempts; i++ {
|
||||
if i != 0 {
|
||||
time.Sleep(retryInterval)
|
||||
}
|
||||
serviceAccount, err := s.client.Core().ServiceAccounts(namespace).Get(name)
|
||||
if err == nil {
|
||||
return serviceAccount, nil
|
||||
}
|
||||
if !errors.IsNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getReferencedServiceAccountToken returns the name of the first referenced secret which is a ServiceAccountToken for the service account
|
||||
func (s *serviceAccount) getReferencedServiceAccountToken(serviceAccount *api.ServiceAccount) (string, error) {
|
||||
if len(serviceAccount.Secrets) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
tokens, err := s.getServiceAccountTokens(serviceAccount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
references := sets.NewString()
|
||||
for _, secret := range serviceAccount.Secrets {
|
||||
references.Insert(secret.Name)
|
||||
}
|
||||
for _, token := range tokens {
|
||||
if references.Has(token.Name) {
|
||||
return token.Name, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// getServiceAccountTokens returns all ServiceAccountToken secrets for the given ServiceAccount
|
||||
func (s *serviceAccount) getServiceAccountTokens(serviceAccount *api.ServiceAccount) ([]*api.Secret, error) {
|
||||
key := &api.Secret{ObjectMeta: api.ObjectMeta{Namespace: serviceAccount.Namespace}}
|
||||
index, err := s.secrets.Index("namespace", key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokens := []*api.Secret{}
|
||||
for _, obj := range index {
|
||||
token := obj.(*api.Secret)
|
||||
|
||||
if serviceaccount.InternalIsServiceAccountToken(token, serviceAccount) {
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
func (s *serviceAccount) limitSecretReferences(serviceAccount *api.ServiceAccount, pod *api.Pod) error {
|
||||
// Ensure all secrets the pod references are allowed by the service account
|
||||
mountableSecrets := sets.NewString()
|
||||
for _, s := range serviceAccount.Secrets {
|
||||
mountableSecrets.Insert(s.Name)
|
||||
}
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
source := volume.VolumeSource
|
||||
if source.Secret == nil {
|
||||
continue
|
||||
}
|
||||
secretName := source.Secret.SecretName
|
||||
if !mountableSecrets.Has(secretName) {
|
||||
return fmt.Errorf("volume with secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", secretName, serviceAccount.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
for _, env := range container.Env {
|
||||
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
|
||||
if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
|
||||
return fmt.Errorf("init container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for _, env := range container.Env {
|
||||
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
|
||||
if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
|
||||
return fmt.Errorf("container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// limit pull secret references as well
|
||||
pullSecrets := sets.NewString()
|
||||
for _, s := range serviceAccount.ImagePullSecrets {
|
||||
pullSecrets.Insert(s.Name)
|
||||
}
|
||||
for i, pullSecretRef := range pod.Spec.ImagePullSecrets {
|
||||
if !pullSecrets.Has(pullSecretRef.Name) {
|
||||
return fmt.Errorf(`imagePullSecrets[%d].name="%s" is not allowed because service account %s does not reference that imagePullSecret`, i, pullSecretRef.Name, serviceAccount.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceAccount) mountServiceAccountToken(serviceAccount *api.ServiceAccount, pod *api.Pod) error {
|
||||
// Find the name of a referenced ServiceAccountToken secret we can mount
|
||||
serviceAccountToken, err := s.getReferencedServiceAccountToken(serviceAccount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error looking up service account token for %s/%s: %v", serviceAccount.Namespace, serviceAccount.Name, err)
|
||||
}
|
||||
if len(serviceAccountToken) == 0 {
|
||||
// We don't have an API token to mount, so return
|
||||
if s.RequireAPIToken {
|
||||
// If a token is required, this is considered an error
|
||||
err := errors.NewServerTimeout(schema.GroupResource{Resource: "serviceaccounts"}, "create pod", 1)
|
||||
err.ErrStatus.Message = fmt.Sprintf("No API token found for service account %q, retry after the token is automatically created and added to the service account", serviceAccount.Name)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
|
||||
tokenVolumeName := ""
|
||||
hasTokenVolume := false
|
||||
allVolumeNames := sets.NewString()
|
||||
for _, volume := range pod.Spec.Volumes {
|
||||
allVolumeNames.Insert(volume.Name)
|
||||
if volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken {
|
||||
tokenVolumeName = volume.Name
|
||||
hasTokenVolume = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Determine a volume name for the ServiceAccountTokenSecret in case we need it
|
||||
if len(tokenVolumeName) == 0 {
|
||||
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed
|
||||
tokenVolumeName = serviceAccountToken
|
||||
if allVolumeNames.Has(tokenVolumeName) {
|
||||
tokenVolumeName = api.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", serviceAccountToken))
|
||||
}
|
||||
}
|
||||
|
||||
// Create the prototypical VolumeMount
|
||||
volumeMount := api.VolumeMount{
|
||||
Name: tokenVolumeName,
|
||||
ReadOnly: true,
|
||||
MountPath: DefaultAPITokenMountPath,
|
||||
}
|
||||
|
||||
// Ensure every container mounts the APISecret volume
|
||||
needsTokenVolume := false
|
||||
for i, container := range pod.Spec.InitContainers {
|
||||
existingContainerMount := false
|
||||
for _, volumeMount := range container.VolumeMounts {
|
||||
// Existing mounts at the default mount path prevent mounting of the API token
|
||||
if volumeMount.MountPath == DefaultAPITokenMountPath {
|
||||
existingContainerMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !existingContainerMount {
|
||||
pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, volumeMount)
|
||||
needsTokenVolume = true
|
||||
}
|
||||
}
|
||||
for i, container := range pod.Spec.Containers {
|
||||
existingContainerMount := false
|
||||
for _, volumeMount := range container.VolumeMounts {
|
||||
// Existing mounts at the default mount path prevent mounting of the API token
|
||||
if volumeMount.MountPath == DefaultAPITokenMountPath {
|
||||
existingContainerMount = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !existingContainerMount {
|
||||
pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount)
|
||||
needsTokenVolume = true
|
||||
}
|
||||
}
|
||||
|
||||
// Add the volume if a container needs it
|
||||
if !hasTokenVolume && needsTokenVolume {
|
||||
volume := api.Volume{
|
||||
Name: tokenVolumeName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
Secret: &api.SecretVolumeSource{
|
||||
SecretName: serviceAccountToken,
|
||||
},
|
||||
},
|
||||
}
|
||||
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
740
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission_test.go
generated
vendored
Normal file
740
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// serviceaccount enforces all pods having an associated serviceaccount,
|
||||
// and all containers mounting the API token for that serviceaccount at a known location
|
||||
package serviceaccount // import "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
|
||||
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/BUILD
generated
vendored
Normal 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 = ["admission.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/errors:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/apis/storage/util:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/admission:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/apis/storage/util:go_default_library",
|
||||
"//pkg/conversion:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
170
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/admission.go
generated
vendored
Normal file
170
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/admission.go
generated
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
admission "k8s.io/kubernetes/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginName = "DefaultStorageClass"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin(PluginName, func(client clientset.Interface, config io.Reader) (admission.Interface, error) {
|
||||
plugin := newPlugin(client)
|
||||
plugin.Run()
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
// claimDefaulterPlugin holds state for and implements the admission plugin.
|
||||
type claimDefaulterPlugin struct {
|
||||
*admission.Handler
|
||||
client clientset.Interface
|
||||
|
||||
reflector *cache.Reflector
|
||||
stopChan chan struct{}
|
||||
store cache.Store
|
||||
}
|
||||
|
||||
var _ admission.Interface = &claimDefaulterPlugin{}
|
||||
|
||||
// newPlugin creates a new admission plugin.
|
||||
func newPlugin(kclient clientset.Interface) *claimDefaulterPlugin {
|
||||
store := cache.NewStore(cache.MetaNamespaceKeyFunc)
|
||||
reflector := cache.NewReflector(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return kclient.Storage().StorageClasses().List(internalOptions)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
internalOptions := api.ListOptions{}
|
||||
v1.Convert_v1_ListOptions_To_api_ListOptions(&options, &internalOptions, nil)
|
||||
return kclient.Storage().StorageClasses().Watch(internalOptions)
|
||||
},
|
||||
},
|
||||
&storage.StorageClass{},
|
||||
store,
|
||||
0,
|
||||
)
|
||||
|
||||
return &claimDefaulterPlugin{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
client: kclient,
|
||||
store: store,
|
||||
reflector: reflector,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *claimDefaulterPlugin) Run() {
|
||||
if a.stopChan == nil {
|
||||
a.stopChan = make(chan struct{})
|
||||
}
|
||||
a.reflector.RunUntil(a.stopChan)
|
||||
}
|
||||
func (a *claimDefaulterPlugin) Stop() {
|
||||
if a.stopChan != nil {
|
||||
close(a.stopChan)
|
||||
a.stopChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Admit sets the default value of a PersistentVolumeClaim's storage class, in case the user did
|
||||
// not provide a value.
|
||||
//
|
||||
// 1. Find available StorageClasses.
|
||||
// 2. Figure which is the default
|
||||
// 3. Write to the PVClaim
|
||||
func (c *claimDefaulterPlugin) Admit(a admission.Attributes) error {
|
||||
if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.GetSubresource()) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pvc, ok := a.GetObject().(*api.PersistentVolumeClaim)
|
||||
// if we can't convert then we don't handle this object so just return
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if storageutil.HasStorageClassAnnotation(pvc.ObjectMeta) {
|
||||
// The user asked for a class.
|
||||
return nil
|
||||
}
|
||||
|
||||
glog.V(4).Infof("no storage class for claim %s (generate: %s)", pvc.Name, pvc.GenerateName)
|
||||
|
||||
def, err := getDefaultClass(c.store)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
if def == nil {
|
||||
// No default class selected, do nothing about the PVC.
|
||||
return nil
|
||||
}
|
||||
|
||||
glog.V(4).Infof("defaulting storage class for claim %s (generate: %s) to %s", pvc.Name, pvc.GenerateName, def.Name)
|
||||
if pvc.ObjectMeta.Annotations == nil {
|
||||
pvc.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
pvc.Annotations[storageutil.StorageClassAnnotation] = def.Name
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDefaultClass returns the default StorageClass from the store, or nil.
|
||||
func getDefaultClass(store cache.Store) (*storage.StorageClass, error) {
|
||||
defaultClasses := []*storage.StorageClass{}
|
||||
for _, c := range store.List() {
|
||||
class, ok := c.(*storage.StorageClass)
|
||||
if !ok {
|
||||
return nil, errors.NewInternalError(fmt.Errorf("error converting stored object to StorageClass: %v", c))
|
||||
}
|
||||
if storageutil.IsDefaultAnnotation(class.ObjectMeta) {
|
||||
defaultClasses = append(defaultClasses, class)
|
||||
glog.V(4).Infof("getDefaultClass added: %s", class.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(defaultClasses) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(defaultClasses) > 1 {
|
||||
glog.V(4).Infof("getDefaultClass %s defaults found", len(defaultClasses))
|
||||
return nil, errors.NewInternalError(fmt.Errorf("%d default StorageClasses were found", len(defaultClasses)))
|
||||
}
|
||||
return defaultClasses[0], nil
|
||||
}
|
||||
233
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/admission_test.go
generated
vendored
Normal file
233
vendor/k8s.io/kubernetes/plugin/pkg/admission/storageclass/default/admission_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
||||
"k8s.io/kubernetes/pkg/conversion"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
defaultClass1 := &storage.StorageClass{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "default1",
|
||||
Annotations: map[string]string{
|
||||
storageutil.IsDefaultStorageClassAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Provisioner: "default1",
|
||||
}
|
||||
defaultClass2 := &storage.StorageClass{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "default2",
|
||||
Annotations: map[string]string{
|
||||
storageutil.IsDefaultStorageClassAnnotation: "true",
|
||||
},
|
||||
},
|
||||
Provisioner: "default2",
|
||||
}
|
||||
// Class that has explicit default = false
|
||||
classWithFalseDefault := &storage.StorageClass{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "nondefault1",
|
||||
Annotations: map[string]string{
|
||||
storageutil.IsDefaultStorageClassAnnotation: "false",
|
||||
},
|
||||
},
|
||||
Provisioner: "nondefault1",
|
||||
}
|
||||
// Class with missing default annotation (=non-default)
|
||||
classWithNoDefault := &storage.StorageClass{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "nondefault2",
|
||||
},
|
||||
Provisioner: "nondefault1",
|
||||
}
|
||||
// Class with empty default annotation (=non-default)
|
||||
classWithEmptyDefault := &storage.StorageClass{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "StorageClass",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "nondefault2",
|
||||
Annotations: map[string]string{
|
||||
storageutil.IsDefaultStorageClassAnnotation: "",
|
||||
},
|
||||
},
|
||||
Provisioner: "nondefault1",
|
||||
}
|
||||
|
||||
claimWithClass := &api.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "claimWithClass",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
claimWithEmptyClass := &api.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "claimWithEmptyClass",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
storageutil.StorageClassAnnotation: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
claimWithNoClass := &api.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PersistentVolumeClaim",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "claimWithNoClass",
|
||||
Namespace: "ns",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
classes []*storage.StorageClass
|
||||
claim *api.PersistentVolumeClaim
|
||||
expectError bool
|
||||
expectedClassName string
|
||||
}{
|
||||
{
|
||||
"no default, no modification of PVCs",
|
||||
[]*storage.StorageClass{classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithNoClass,
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"one default, modify PVC with class=nil",
|
||||
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithNoClass,
|
||||
false,
|
||||
"default1",
|
||||
},
|
||||
{
|
||||
"one default, no modification of PVC with class=''",
|
||||
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithEmptyClass,
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"one default, no modification of PVC with class='foo'",
|
||||
[]*storage.StorageClass{defaultClass1, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithClass,
|
||||
false,
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
"two defaults, error with PVC with class=nil",
|
||||
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithNoClass,
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"two defaults, no modification of PVC with class=''",
|
||||
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithEmptyClass,
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"two defaults, no modification of PVC with class='foo'",
|
||||
[]*storage.StorageClass{defaultClass1, defaultClass2, classWithFalseDefault, classWithNoDefault, classWithEmptyDefault},
|
||||
claimWithClass,
|
||||
false,
|
||||
"foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
glog.V(4).Infof("starting test %q", test.name)
|
||||
|
||||
// clone the claim, it's going to be modified
|
||||
clone, err := conversion.NewCloner().DeepCopy(test.claim)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot clone claim: %v", err)
|
||||
}
|
||||
claim := clone.(*api.PersistentVolumeClaim)
|
||||
|
||||
ctrl := newPlugin(nil)
|
||||
for _, c := range test.classes {
|
||||
ctrl.store.Add(c)
|
||||
}
|
||||
attrs := admission.NewAttributesRecord(
|
||||
claim, // new object
|
||||
nil, // old object
|
||||
api.Kind("PersistentVolumeClaim").WithVersion("version"),
|
||||
claim.Namespace,
|
||||
claim.Name,
|
||||
api.Resource("persistentvolumeclaims").WithVersion("version"),
|
||||
"", // subresource
|
||||
admission.Create,
|
||||
nil, // userInfo
|
||||
)
|
||||
err = ctrl.Admit(attrs)
|
||||
glog.Infof("Got %v", err)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error received: %v", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error and no error recevied", test.name)
|
||||
}
|
||||
|
||||
class := ""
|
||||
if claim.Annotations != nil {
|
||||
if value, ok := claim.Annotations[storageutil.StorageClassAnnotation]; ok {
|
||||
class = value
|
||||
}
|
||||
}
|
||||
if test.expectedClassName != "" && test.expectedClassName != class {
|
||||
t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class)
|
||||
}
|
||||
if test.expectedClassName == "" && class != "" {
|
||||
t.Errorf("Test %q: expected class name %q, got %q", test.name, test.expectedClassName, class)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/BUILD
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["doc.go"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
3
vendor/k8s.io/kubernetes/plugin/pkg/auth/OWNERS
generated
vendored
Normal file
3
vendor/k8s.io/kubernetes/plugin/pkg/auth/OWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
assignees:
|
||||
- erictune
|
||||
- liggitt
|
||||
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/BUILD
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
||||
18
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package authenticator contains implementations for pkg/auth/authenticator interfaces
|
||||
package authenticator // import "k8s.io/kubernetes/plugin/pkg/auth/authenticator"
|
||||
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/BUILD
generated
vendored
Normal file
17
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
||||
29
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/BUILD
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["allow.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["allow_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [],
|
||||
)
|
||||
38
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/allow.go
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/allow.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package allow
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
type allowAuthenticator struct{}
|
||||
|
||||
// NewAllow returns a password authenticator that allows any non-empty username
|
||||
func NewAllow() authenticator.Password {
|
||||
return allowAuthenticator{}
|
||||
}
|
||||
|
||||
// AuthenticatePassword implements authenticator.Password to allow any non-empty username,
|
||||
// using the specified username as the name and UID
|
||||
func (allowAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) {
|
||||
if username == "" {
|
||||
return nil, false, nil
|
||||
}
|
||||
return &user.DefaultInfo{Name: username, UID: username}, true, nil
|
||||
}
|
||||
47
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/allow_test.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/allow/allow_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package allow
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAllowEmpty(t *testing.T) {
|
||||
allow := NewAllow()
|
||||
user, ok, err := allow.AuthenticatePassword("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("Unexpected success")
|
||||
}
|
||||
if user != nil {
|
||||
t.Fatalf("Unexpected user: %v", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowPresent(t *testing.T) {
|
||||
allow := NewAllow()
|
||||
user, ok, err := allow.AuthenticatePassword("myuser", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected failure")
|
||||
}
|
||||
if user.GetName() != "myuser" || user.GetUID() != "myuser" {
|
||||
t.Fatalf("Unexpected user name or uid: %v", user)
|
||||
}
|
||||
}
|
||||
18
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package password contains authenticator.Password implementations
|
||||
package password // import "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password"
|
||||
28
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/BUILD
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"keystone.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:github.com/rackspace/gophercloud",
|
||||
"//vendor:github.com/rackspace/gophercloud/openstack",
|
||||
],
|
||||
)
|
||||
20
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/doc.go
generated
vendored
Normal file
20
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package keystone provides authentication via keystone.
|
||||
// For details about keystone and how to use the plugin, refer to
|
||||
// https://github.com/kubernetes/kubernetes.github.io/blob/master/docs/admin/authentication.md
|
||||
package keystone // import "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone"
|
||||
93
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/keystone.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/keystone/keystone.go
generated
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package keystone
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/rackspace/gophercloud"
|
||||
"github.com/rackspace/gophercloud/openstack"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
netutil "k8s.io/kubernetes/pkg/util/net"
|
||||
)
|
||||
|
||||
// KeystoneAuthenticator contacts openstack keystone to validate user's credentials passed in the request.
|
||||
// The keystone endpoint is passed during apiserver startup
|
||||
type KeystoneAuthenticator struct {
|
||||
authURL string
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
// AuthenticatePassword checks the username, password via keystone call
|
||||
func (keystoneAuthenticator *KeystoneAuthenticator) AuthenticatePassword(username string, password string) (user.Info, bool, error) {
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: keystoneAuthenticator.authURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
_, err := keystoneAuthenticator.AuthenticatedClient(opts)
|
||||
if err != nil {
|
||||
glog.Info("Failed: Starting openstack authenticate client:" + err.Error())
|
||||
return nil, false, errors.New("Failed to authenticate")
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{Name: username}, true, nil
|
||||
}
|
||||
|
||||
// AuthenticatedClient logs in to an OpenStack cloud found at the identity endpoint specified by options, acquires a
|
||||
// token, and returns a Client instance that's ready to operate.
|
||||
func (keystoneAuthenticator *KeystoneAuthenticator) AuthenticatedClient(options gophercloud.AuthOptions) (*gophercloud.ProviderClient, error) {
|
||||
client, err := openstack.NewClient(options.IdentityEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if keystoneAuthenticator.transport != nil {
|
||||
client.HTTPClient.Transport = keystoneAuthenticator.transport
|
||||
}
|
||||
|
||||
err = openstack.Authenticate(client, options)
|
||||
return client, err
|
||||
}
|
||||
|
||||
// NewKeystoneAuthenticator returns a password authenticator that validates credentials using openstack keystone
|
||||
func NewKeystoneAuthenticator(authURL string, caFile string) (*KeystoneAuthenticator, error) {
|
||||
if !strings.HasPrefix(authURL, "https") {
|
||||
return nil, errors.New("Auth URL should be secure and start with https")
|
||||
}
|
||||
if authURL == "" {
|
||||
return nil, errors.New("Auth URL is empty")
|
||||
}
|
||||
if caFile != "" {
|
||||
roots, err := certutil.NewPool(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &tls.Config{}
|
||||
config.RootCAs = roots
|
||||
transport := netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config})
|
||||
return &KeystoneAuthenticator{authURL, transport}, nil
|
||||
}
|
||||
|
||||
return &KeystoneAuthenticator{authURL: authURL}, nil
|
||||
}
|
||||
26
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/BUILD
generated
vendored
Normal file
26
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["passwordfile.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/auth/user:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["passwordfile_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/auth/user:go_default_library"],
|
||||
)
|
||||
78
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/passwordfile.go
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/passwordfile.go
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package passwordfile
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
type PasswordAuthenticator struct {
|
||||
users map[string]*userPasswordInfo
|
||||
}
|
||||
|
||||
type userPasswordInfo struct {
|
||||
info *user.DefaultInfo
|
||||
password string
|
||||
}
|
||||
|
||||
// NewCSV returns a PasswordAuthenticator, populated from a CSV file.
|
||||
// The CSV file must contain records in the format "password,username,useruid"
|
||||
func NewCSV(path string) (*PasswordAuthenticator, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
users := make(map[string]*userPasswordInfo)
|
||||
reader := csv.NewReader(file)
|
||||
for {
|
||||
record, err := reader.Read()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(record) < 3 {
|
||||
return nil, fmt.Errorf("password file '%s' must have at least 3 columns (password, user name, user uid), found %d", path, len(record))
|
||||
}
|
||||
obj := &userPasswordInfo{
|
||||
info: &user.DefaultInfo{Name: record[1], UID: record[2]},
|
||||
password: record[0],
|
||||
}
|
||||
users[obj.info.Name] = obj
|
||||
}
|
||||
|
||||
return &PasswordAuthenticator{users}, nil
|
||||
}
|
||||
|
||||
func (a *PasswordAuthenticator) AuthenticatePassword(username, password string) (user.Info, bool, error) {
|
||||
user, ok := a.users[username]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if user.password != password {
|
||||
return nil, false, nil
|
||||
}
|
||||
return user.info, true, nil
|
||||
}
|
||||
121
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/passwordfile_test.go
generated
vendored
Normal file
121
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile/passwordfile_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package passwordfile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
func TestPasswordFile(t *testing.T) {
|
||||
auth, err := newWithContents(t, `
|
||||
password1,user1,uid1
|
||||
password2,user2,uid2
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read passwordfile: %v", err)
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
Username string
|
||||
Password string
|
||||
User *user.DefaultInfo
|
||||
Ok bool
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
Username: "user1",
|
||||
Password: "password1",
|
||||
User: &user.DefaultInfo{Name: "user1", UID: "uid1"},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user2",
|
||||
Password: "password2",
|
||||
User: &user.DefaultInfo{Name: "user2", UID: "uid2"},
|
||||
Ok: true,
|
||||
},
|
||||
{
|
||||
Username: "user1",
|
||||
Password: "password2",
|
||||
},
|
||||
{
|
||||
Username: "user2",
|
||||
Password: "password1",
|
||||
},
|
||||
{
|
||||
Username: "user3",
|
||||
Password: "password3",
|
||||
},
|
||||
{
|
||||
Username: "user4",
|
||||
Password: "password4",
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
user, ok, err := auth.AuthenticatePassword(testCase.Username, testCase.Password)
|
||||
if err != nil {
|
||||
t.Errorf("%d: unexpected error: %v", i, err)
|
||||
}
|
||||
if testCase.User == nil {
|
||||
if user != nil {
|
||||
t.Errorf("%d: unexpected non-nil user %#v", i, user)
|
||||
}
|
||||
} else if !reflect.DeepEqual(testCase.User, user) {
|
||||
t.Errorf("%d: expected user %#v, got %#v", i, testCase.User, user)
|
||||
}
|
||||
if testCase.Ok != ok {
|
||||
t.Errorf("%d: expected auth %v, got %v", i, testCase.Ok, ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadPasswordFile(t *testing.T) {
|
||||
if _, err := newWithContents(t, `
|
||||
password1,user1,uid1
|
||||
password2,user2,uid2
|
||||
password3,user3
|
||||
password4
|
||||
`); err == nil {
|
||||
t.Fatalf("unexpected non error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsufficientColumnsPasswordFile(t *testing.T) {
|
||||
if _, err := newWithContents(t, "password4\n"); err == nil {
|
||||
t.Fatalf("unexpected non error")
|
||||
}
|
||||
}
|
||||
|
||||
func newWithContents(t *testing.T, contents string) (auth *PasswordAuthenticator, err error) {
|
||||
f, err := ioutil.TempFile("", "passwordfile_test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error creating passwordfile: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
|
||||
t.Fatalf("unexpected error writing passwordfile: %v", err)
|
||||
}
|
||||
|
||||
return NewCSV(f.Name())
|
||||
}
|
||||
33
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/BUILD
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["anonymous.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["anonymous_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
36
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/anonymous.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/anonymous.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package anonymous
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
const (
|
||||
anonymousUser = user.Anonymous
|
||||
|
||||
unauthenticatedGroup = user.AllUnauthenticated
|
||||
)
|
||||
|
||||
func NewAuthenticator() authenticator.Request {
|
||||
return authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||
return &user.DefaultInfo{Name: anonymousUser, Groups: []string{unauthenticatedGroup}}, true, nil
|
||||
})
|
||||
}
|
||||
42
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/anonymous_test.go
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous/anonymous_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package anonymous
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestAnonymous(t *testing.T) {
|
||||
var a authenticator.Request = NewAuthenticator()
|
||||
u, ok, err := a.AuthenticateRequest(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error %v", err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Unexpectedly unauthenticated")
|
||||
}
|
||||
if u.GetName() != user.Anonymous {
|
||||
t.Fatalf("Expected username %s, got %s", user.Anonymous, u.GetName())
|
||||
}
|
||||
if !sets.NewString(u.GetGroups()...).Equal(sets.NewString(user.AllUnauthenticated)) {
|
||||
t.Fatalf("Expected group %s, got %v", user.AllUnauthenticated, u.GetGroups())
|
||||
}
|
||||
}
|
||||
32
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/BUILD
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["basicauth.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["basicauth_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
],
|
||||
)
|
||||
43
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/basicauth.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/basicauth.go
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package basicauth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
// Authenticator authenticates requests using basic auth
|
||||
type Authenticator struct {
|
||||
auth authenticator.Password
|
||||
}
|
||||
|
||||
// New returns a request authenticator that validates credentials using the provided password authenticator
|
||||
func New(auth authenticator.Password) *Authenticator {
|
||||
return &Authenticator{auth}
|
||||
}
|
||||
|
||||
// AuthenticateRequest authenticates the request using the "Authorization: Basic" header in the request
|
||||
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||
username, password, found := req.BasicAuth()
|
||||
if !found {
|
||||
return nil, false, nil
|
||||
}
|
||||
return a.auth.AuthenticatePassword(username, password)
|
||||
}
|
||||
123
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/basicauth_test.go
generated
vendored
Normal file
123
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth/basicauth_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package basicauth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
type testPassword struct {
|
||||
Username string
|
||||
Password string
|
||||
Called bool
|
||||
|
||||
User user.Info
|
||||
OK bool
|
||||
Err error
|
||||
}
|
||||
|
||||
func (t *testPassword) AuthenticatePassword(user, password string) (user.Info, bool, error) {
|
||||
t.Called = true
|
||||
t.Username = user
|
||||
t.Password = password
|
||||
return t.User, t.OK, t.Err
|
||||
}
|
||||
|
||||
func TestBasicAuth(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
Header string
|
||||
Password testPassword
|
||||
|
||||
ExpectedCalled bool
|
||||
ExpectedUsername string
|
||||
ExpectedPassword string
|
||||
|
||||
ExpectedUser string
|
||||
ExpectedOK bool
|
||||
ExpectedErr bool
|
||||
}{
|
||||
"no auth": {},
|
||||
"empty password basic header": {
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "user_with_empty_password",
|
||||
ExpectedPassword: "",
|
||||
},
|
||||
"valid basic header": {
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypassword:withcolon",
|
||||
},
|
||||
"password auth returned user": {
|
||||
Password: testPassword{User: &user.DefaultInfo{Name: "returneduser"}, OK: true},
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypw",
|
||||
ExpectedUser: "returneduser",
|
||||
ExpectedOK: true,
|
||||
},
|
||||
"password auth returned error": {
|
||||
Password: testPassword{Err: errors.New("auth error")},
|
||||
ExpectedCalled: true,
|
||||
ExpectedUsername: "myuser",
|
||||
ExpectedPassword: "mypw",
|
||||
ExpectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, testCase := range testCases {
|
||||
password := testCase.Password
|
||||
auth := authenticator.Request(New(&password))
|
||||
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
if testCase.ExpectedUsername != "" || testCase.ExpectedPassword != "" {
|
||||
req.SetBasicAuth(testCase.ExpectedUsername, testCase.ExpectedPassword)
|
||||
}
|
||||
|
||||
user, ok, err := auth.AuthenticateRequest(req)
|
||||
|
||||
if testCase.ExpectedCalled != password.Called {
|
||||
t.Errorf("%s: Expected called=%v, got %v", k, testCase.ExpectedCalled, password.Called)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedUsername != password.Username {
|
||||
t.Errorf("%s: Expected called with username=%v, got %v", k, testCase.ExpectedUsername, password.Username)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedPassword != password.Password {
|
||||
t.Errorf("%s: Expected called with password=%v, got %v", k, testCase.ExpectedPassword, password.Password)
|
||||
continue
|
||||
}
|
||||
|
||||
if testCase.ExpectedErr != (err != nil) {
|
||||
t.Errorf("%s: Expected err=%v, got err=%v", k, testCase.ExpectedErr, err)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedOK != ok {
|
||||
t.Errorf("%s: Expected ok=%v, got ok=%v", k, testCase.ExpectedOK, ok)
|
||||
continue
|
||||
}
|
||||
if testCase.ExpectedUser != "" && testCase.ExpectedUser != user.GetName() {
|
||||
t.Errorf("%s: Expected user.GetName()=%v, got %v", k, testCase.ExpectedUser, user.GetName())
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest/BUILD
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["requestheader.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/auth/authenticator:go_default_library",
|
||||
"//pkg/auth/user:go_default_library",
|
||||
"//pkg/util/cert:go_default_library",
|
||||
"//pkg/util/sets:go_default_library",
|
||||
"//plugin/pkg/auth/authenticator/request/x509:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["requestheader_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//pkg/auth/user:go_default_library"],
|
||||
)
|
||||
178
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go
generated
vendored
Normal file
178
vendor/k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go
generated
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package headerrequest
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
utilcert "k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
x509request "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509"
|
||||
)
|
||||
|
||||
type requestHeaderAuthRequestHandler struct {
|
||||
// nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins.
|
||||
nameHeaders []string
|
||||
|
||||
// groupHeaders are the headers to check (case-insensitively) for group membership. All values of all headers will be added.
|
||||
groupHeaders []string
|
||||
|
||||
// extraHeaderPrefixes are the head prefixes to check (case-insensitively) for filling in
|
||||
// the user.Info.Extra. All values of all matching headers will be added.
|
||||
extraHeaderPrefixes []string
|
||||
}
|
||||
|
||||
func New(nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
trimmedNameHeaders, err := trimHeaders(nameHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedGroupHeaders, err := trimHeaders(groupHeaders...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trimmedExtraHeaderPrefixes, err := trimHeaders(extraHeaderPrefixes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &requestHeaderAuthRequestHandler{
|
||||
nameHeaders: trimmedNameHeaders,
|
||||
groupHeaders: trimmedGroupHeaders,
|
||||
extraHeaderPrefixes: trimmedExtraHeaderPrefixes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func trimHeaders(headerNames ...string) ([]string, error) {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
trimmedHeader := strings.TrimSpace(headerName)
|
||||
if len(trimmedHeader) == 0 {
|
||||
return nil, fmt.Errorf("empty header %q", headerName)
|
||||
}
|
||||
ret = append(ret, trimmedHeader)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func NewSecure(clientCA string, proxyClientNames []string, nameHeaders []string, groupHeaders []string, extraHeaderPrefixes []string) (authenticator.Request, error) {
|
||||
headerAuthenticator, err := New(nameHeaders, groupHeaders, extraHeaderPrefixes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(clientCA) == 0 {
|
||||
return nil, fmt.Errorf("missing clientCA file")
|
||||
}
|
||||
|
||||
// Wrap with an x509 verifier
|
||||
caData, err := ioutil.ReadFile(clientCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading %s: %v", clientCA, err)
|
||||
}
|
||||
opts := x509request.DefaultVerifyOptions()
|
||||
opts.Roots = x509.NewCertPool()
|
||||
certs, err := utilcert.ParseCertsPEM(caData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading certs from %s: %v", clientCA, err)
|
||||
}
|
||||
for _, cert := range certs {
|
||||
opts.Roots.AddCert(cert)
|
||||
}
|
||||
|
||||
return x509request.NewVerifier(opts, headerAuthenticator, sets.NewString(proxyClientNames...)), nil
|
||||
}
|
||||
|
||||
func (a *requestHeaderAuthRequestHandler) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
|
||||
name := headerValue(req.Header, a.nameHeaders)
|
||||
if len(name) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
groups := allHeaderValues(req.Header, a.groupHeaders)
|
||||
extra := newExtra(req.Header, a.extraHeaderPrefixes)
|
||||
|
||||
// clear headers used for authentication
|
||||
for _, headerName := range a.nameHeaders {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for _, headerName := range a.groupHeaders {
|
||||
req.Header.Del(headerName)
|
||||
}
|
||||
for k := range extra {
|
||||
for _, prefix := range a.extraHeaderPrefixes {
|
||||
req.Header.Del(prefix + k)
|
||||
}
|
||||
}
|
||||
|
||||
return &user.DefaultInfo{
|
||||
Name: name,
|
||||
Groups: groups,
|
||||
Extra: extra,
|
||||
}, true, nil
|
||||
}
|
||||
|
||||
func headerValue(h http.Header, headerNames []string) string {
|
||||
for _, headerName := range headerNames {
|
||||
headerValue := h.Get(headerName)
|
||||
if len(headerValue) > 0 {
|
||||
return headerValue
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func allHeaderValues(h http.Header, headerNames []string) []string {
|
||||
ret := []string{}
|
||||
for _, headerName := range headerNames {
|
||||
values, ok := h[headerName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, headerValue := range values {
|
||||
if len(headerValue) > 0 {
|
||||
ret = append(ret, headerValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func newExtra(h http.Header, headerPrefixes []string) map[string][]string {
|
||||
ret := map[string][]string{}
|
||||
|
||||
// we have to iterate over prefixes first in order to have proper ordering inside the value slices
|
||||
for _, prefix := range headerPrefixes {
|
||||
for headerName, vv := range h {
|
||||
if !strings.HasPrefix(strings.ToLower(headerName), strings.ToLower(prefix)) {
|
||||
continue
|
||||
}
|
||||
|
||||
extraKey := strings.ToLower(headerName[len(prefix):])
|
||||
ret[extraKey] = append(ret[extraKey], vv...)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue