Merge pull request #67831 from xmudrii/extern-exec

admission/exec: externalize exec admission controller
pull/58/head
k8s-ci-robot 2018-09-26 09:55:05 -07:00 committed by GitHub
commit 07e81cb8ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 67 deletions

View File

@ -11,11 +11,11 @@ go_library(
srcs = ["admission.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubeapiserver/admission: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/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"],
deps = [
"//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/runtime: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",
],
)

View File

@ -20,11 +20,11 @@ import (
"fmt"
"io"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/admission"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/client-go/kubernetes"
)
const (
@ -52,7 +52,7 @@ func Register(plugins *admission.Plugins) {
// a pod using host based configurations.
type DenyExec struct {
*admission.Handler
client internalclientset.Interface
client kubernetes.Interface
// these flags control which items will be checked to deny exec/attach
hostNetwork bool
@ -62,20 +62,7 @@ type DenyExec struct {
}
var _ admission.ValidationInterface = &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,
}
}
var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&DenyExec{})
// NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged
// 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
func (d *DenyExec) Validate(a admission.Attributes) (err error) {
path := a.GetResource().Resource
@ -100,24 +112,21 @@ func (d *DenyExec) Validate(a admission.Attributes) (err error) {
if path != "pods/exec" && path != "pods/attach" {
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 {
return admission.NewForbidden(a, err)
}
if pod.Spec.SecurityContext != nil {
securityContext := pod.Spec.SecurityContext
if d.hostNetwork && securityContext.HostNetwork {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network"))
}
if d.hostNetwork && pod.Spec.HostNetwork {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host network"))
}
if d.hostPID && securityContext.HostPID {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
}
if d.hostPID && pod.Spec.HostPID {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
}
if d.hostIPC && securityContext.HostIPC {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
}
if d.hostIPC && pod.Spec.HostIPC {
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
}
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
func isPrivileged(pod *api.Pod) bool {
func isPrivileged(pod *corev1.Pod) bool {
for _, c := range pod.Spec.InitContainers {
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
continue
@ -147,16 +156,3 @@ func isPrivileged(pod *api.Pod) bool {
}
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 (
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/kubernetes/fake"
core "k8s.io/client-go/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
// newAllowEscalatingExec returns `admission.Interface` that allows execution on
@ -41,20 +42,18 @@ func newAllowEscalatingExec() *DenyExec {
func TestAdmission(t *testing.T) {
privPod := validPod("privileged")
priv := true
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
privPod.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{
Privileged: &priv,
}
hostPIDPod := validPod("hostPID")
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
hostPIDPod.Spec.SecurityContext.HostPID = true
hostPIDPod.Spec.HostPID = true
hostIPCPod := validPod("hostIPC")
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
hostIPCPod.Spec.SecurityContext.HostIPC = true
hostIPCPod.Spec.HostIPC = true
testCases := map[string]struct {
pod *api.Pod
pod *corev1.Pod
shouldAccept bool
}{
"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.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) {
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
})
handler.SetInternalKubeClientSet(mockClient)
handler.SetExternalKubeClientSet(mockClient)
admission.ValidateInitialization(handler)
// pods/exec
@ -146,20 +145,18 @@ func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept b
func TestDenyExecOnPrivileged(t *testing.T) {
privPod := validPod("privileged")
priv := true
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
privPod.Spec.Containers[0].SecurityContext = &corev1.SecurityContext{
Privileged: &priv,
}
hostPIDPod := validPod("hostPID")
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
hostPIDPod.Spec.SecurityContext.HostPID = true
hostPIDPod.Spec.HostPID = true
hostIPCPod := validPod("hostIPC")
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
hostIPCPod.Spec.SecurityContext.HostIPC = true
hostIPCPod.Spec.HostIPC = true
testCases := map[string]struct {
pod *api.Pod
pod *corev1.Pod
shouldAccept bool
}{
"priv": {
@ -195,11 +192,11 @@ func TestDenyExecOnPrivileged(t *testing.T) {
}
}
func validPod(name string) *api.Pod {
return &api.Pod{
func validPod(name string) *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
Spec: api.PodSpec{
Containers: []api.Container{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{Name: "ctr1", Image: "image"},
{Name: "ctr2", Image: "image2"},
},