admission/exec: externalize exec admission controller

pull/58/head
Marko Mudrinić 2018-08-24 16:47:21 +02:00
parent 963adda7f9
commit b622acf8ec
No known key found for this signature in database
GPG Key ID: F15730C52ACE0E9D
3 changed files with 61 additions and 67 deletions

View File

@ -11,11 +11,11 @@ go_library(
srcs = ["admission.go"], srcs = ["admission.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec", importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec",
deps = [ deps = [
"//pkg/apis/core:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
], ],
) )
@ -25,10 +25,11 @@ go_test(
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library", "//staging/src/k8s.io/client-go/testing:go_default_library",
], ],
) )

View File

@ -20,11 +20,11 @@ import (
"fmt" "fmt"
"io" "io"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core" genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/client-go/kubernetes"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
) )
const ( const (
@ -52,7 +52,7 @@ func Register(plugins *admission.Plugins) {
// a pod using host based configurations. // a pod using host based configurations.
type DenyExec struct { type DenyExec struct {
*admission.Handler *admission.Handler
client internalclientset.Interface client kubernetes.Interface
// these flags control which items will be checked to deny exec/attach // these flags control which items will be checked to deny exec/attach
hostNetwork bool hostNetwork bool
@ -62,20 +62,7 @@ type DenyExec struct {
} }
var _ admission.ValidationInterface = &DenyExec{} var _ admission.ValidationInterface = &DenyExec{}
var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&DenyExec{})
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&DenyExec{})
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
// using host based configurations.
func NewDenyEscalatingExec() *DenyExec {
return &DenyExec{
Handler: admission.NewHandler(admission.Connect),
hostNetwork: true,
hostIPC: true,
hostPID: true,
privileged: true,
}
}
// NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged // NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged
// option. This is for legacy support of the DenyExecOnPrivileged admission controller. // option. This is for legacy support of the DenyExecOnPrivileged admission controller.
@ -90,6 +77,31 @@ func NewDenyExecOnPrivileged() *DenyExec {
} }
} }
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
// using host based configurations.
func NewDenyEscalatingExec() *DenyExec {
return &DenyExec{
Handler: admission.NewHandler(admission.Connect),
hostNetwork: true,
hostIPC: true,
hostPID: true,
privileged: true,
}
}
// SetExternalKubeClientSet implements the WantsInternalKubeClientSet interface.
func (d *DenyExec) SetExternalKubeClientSet(client kubernetes.Interface) {
d.client = client
}
// ValidateInitialization implements the InitializationValidator interface.
func (d *DenyExec) ValidateInitialization() error {
if d.client == nil {
return fmt.Errorf("missing client")
}
return nil
}
// Validate makes an admission decision based on the request attributes // Validate makes an admission decision based on the request attributes
func (d *DenyExec) Validate(a admission.Attributes) (err error) { func (d *DenyExec) Validate(a admission.Attributes) (err error) {
path := a.GetResource().Resource path := a.GetResource().Resource
@ -100,24 +112,21 @@ func (d *DenyExec) Validate(a admission.Attributes) (err error) {
if path != "pods/exec" && path != "pods/attach" { if path != "pods/exec" && path != "pods/attach" {
return nil return nil
} }
pod, err := d.client.Core().Pods(a.GetNamespace()).Get(a.GetName(), metav1.GetOptions{}) pod, err := d.client.CoreV1().Pods(a.GetNamespace()).Get(a.GetName(), metav1.GetOptions{})
if err != nil { if err != nil {
return admission.NewForbidden(a, err) return admission.NewForbidden(a, err)
} }
if pod.Spec.SecurityContext != nil { if d.hostNetwork && pod.Spec.HostNetwork {
securityContext := pod.Spec.SecurityContext return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network"))
if d.hostNetwork && securityContext.HostNetwork { }
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network"))
}
if d.hostPID && securityContext.HostPID { if d.hostPID && pod.Spec.HostPID {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid")) return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
} }
if d.hostIPC && securityContext.HostIPC { if d.hostIPC && pod.Spec.HostIPC {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc")) return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
}
} }
if d.privileged && isPrivileged(pod) { if d.privileged && isPrivileged(pod) {
@ -128,7 +137,7 @@ func (d *DenyExec) Validate(a admission.Attributes) (err error) {
} }
// isPrivileged will return true a pod has any privileged containers // isPrivileged will return true a pod has any privileged containers
func isPrivileged(pod *api.Pod) bool { func isPrivileged(pod *corev1.Pod) bool {
for _, c := range pod.Spec.InitContainers { for _, c := range pod.Spec.InitContainers {
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil { if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
continue continue
@ -147,16 +156,3 @@ func isPrivileged(pod *api.Pod) bool {
} }
return false return false
} }
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
func (d *DenyExec) SetInternalKubeClientSet(client internalclientset.Interface) {
d.client = client
}
// ValidateInitialization implements the InitializationValidator interface.
func (d *DenyExec) ValidateInitialization() error {
if d.client == nil {
return fmt.Errorf("missing client")
}
return nil
}

View File

@ -19,12 +19,13 @@ package exec
import ( import (
"testing" "testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing" core "k8s.io/client-go/testing"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
) )
// newAllowEscalatingExec returns `admission.Interface` that allows execution on // newAllowEscalatingExec returns `admission.Interface` that allows execution on
@ -41,20 +42,18 @@ func newAllowEscalatingExec() *DenyExec {
func TestAdmission(t *testing.T) { func TestAdmission(t *testing.T) {
privPod := validPod("privileged") privPod := validPod("privileged")
priv := true priv := true
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{ privPod.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{
Privileged: &priv, Privileged: &priv,
} }
hostPIDPod := validPod("hostPID") hostPIDPod := validPod("hostPID")
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{} hostPIDPod.Spec.HostPID = true
hostPIDPod.Spec.SecurityContext.HostPID = true
hostIPCPod := validPod("hostIPC") hostIPCPod := validPod("hostIPC")
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{} hostIPCPod.Spec.HostIPC = true
hostIPCPod.Spec.SecurityContext.HostIPC = true
testCases := map[string]struct { testCases := map[string]struct {
pod *api.Pod pod *corev1.Pod
shouldAccept bool shouldAccept bool
}{ }{
"priv": { "priv": {
@ -106,7 +105,7 @@ func TestAdmission(t *testing.T) {
} }
} }
func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept bool) { func testAdmission(t *testing.T, pod *corev1.Pod, handler *DenyExec, shouldAccept bool) {
mockClient := &fake.Clientset{} mockClient := &fake.Clientset{}
mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) { mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) {
if action.(core.GetAction).GetName() == pod.Name { if action.(core.GetAction).GetName() == pod.Name {
@ -116,7 +115,7 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept b
return true, nil, nil return true, nil, nil
}) })
handler.SetInternalKubeClientSet(mockClient) handler.SetExternalKubeClientSet(mockClient)
admission.ValidateInitialization(handler) admission.ValidateInitialization(handler)
// pods/exec // pods/exec
@ -146,20 +145,18 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept b
func TestDenyExecOnPrivileged(t *testing.T) { func TestDenyExecOnPrivileged(t *testing.T) {
privPod := validPod("privileged") privPod := validPod("privileged")
priv := true priv := true
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{ privPod.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{
Privileged: &priv, Privileged: &priv,
} }
hostPIDPod := validPod("hostPID") hostPIDPod := validPod("hostPID")
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{} hostPIDPod.Spec.HostPID = true
hostPIDPod.Spec.SecurityContext.HostPID = true
hostIPCPod := validPod("hostIPC") hostIPCPod := validPod("hostIPC")
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{} hostIPCPod.Spec.HostIPC = true
hostIPCPod.Spec.SecurityContext.HostIPC = true
testCases := map[string]struct { testCases := map[string]struct {
pod *api.Pod pod *corev1.Pod
shouldAccept bool shouldAccept bool
}{ }{
"priv": { "priv": {
@ -195,11 +192,11 @@ func TestDenyExecOnPrivileged(t *testing.T) {
} }
} }
func validPod(name string) *api.Pod { func validPod(name string) *corev1.Pod {
return &api.Pod{ return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"}, ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
Spec: api.PodSpec{ Spec: corev1.PodSpec{
Containers: []api.Container{ Containers: []corev1.Container{
{Name: "ctr1", Image: "image"}, {Name: "ctr1", Image: "image"},
{Name: "ctr2", Image: "image2"}, {Name: "ctr2", Image: "image2"},
}, },