mirror of https://github.com/k3s-io/k3s
admission/exec: externalize exec admission controller
parent
963adda7f9
commit
b622acf8ec
|
@ -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",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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"},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue