Skip to content

Commit

Permalink
Add ephemeralcontainer to imagepolicy securityaccount admission plugin
Browse files Browse the repository at this point in the history
Signed-off-by: Rita Zhang <rita.z.zhang@gmail.com>
  • Loading branch information
ritazh committed Jun 2, 2023
1 parent 544005c commit d6168bb
Show file tree
Hide file tree
Showing 4 changed files with 310 additions and 35 deletions.
28 changes: 19 additions & 9 deletions plugin/pkg/admission/imagepolicy/admission.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (

// PluginName indicates name of admission plugin.
const PluginName = "ImagePolicyWebhook"
const ephemeralcontainers = "ephemeralcontainers"

// AuditKeyPrefix is used as the prefix for all audit keys handled by this
// pluggin. Some well known suffixes are listed below.
Expand Down Expand Up @@ -132,8 +133,9 @@ func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err

// Validate makes an admission decision based on the request attributes
func (a *Plugin) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
// Ignore all calls to subresources or resources other than pods.
if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") {
// Ignore all calls to subresources other than ephemeralcontainers or calls to resources other than pods.
subresource := attributes.GetSubresource()
if (subresource != "" && subresource != ephemeralcontainers) || attributes.GetResource().GroupResource() != api.Resource("pods") {
return nil
}

Expand All @@ -144,13 +146,21 @@ func (a *Plugin) Validate(ctx context.Context, attributes admission.Attributes,

// 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,
})
if subresource == "" {
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,
})
}
} else if subresource == ephemeralcontainers {
for _, c := range pod.Spec.EphemeralContainers {
imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{
Image: c.Image,
})
}
}
imageReview := v1alpha1.ImageReview{
Spec: v1alpha1.ImageReviewSpec{
Expand Down
172 changes: 153 additions & 19 deletions plugin/pkg/admission/imagepolicy/admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"

"fmt"
"io/ioutil"
"os"
"path/filepath"
"text/template"
Expand Down Expand Up @@ -94,7 +93,7 @@ func TestNewFromConfig(t *testing.T) {
{data.Key, clientKey},
}
for _, file := range files {
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
if err := os.WriteFile(file.name, file.data, 0400); err != nil {
t.Fatal(err)
}
}
Expand Down Expand Up @@ -198,10 +197,11 @@ current-context: default
// Use a closure so defer statements trigger between loop iterations.
t.Run(tt.msg, func(t *testing.T) {
err := func() error {
tempfile, err := ioutil.TempFile("", "")
tempfile, err := os.CreateTemp("", "")
if err != nil {
return err
}
p := tempfile.Name()
defer utiltesting.CloseAndRemove(t, tempfile)

tmpl, err := template.New("test").Parse(tt.kubeConfigTmpl)
Expand All @@ -212,11 +212,12 @@ current-context: default
return fmt.Errorf("failed to execute test template: %v", err)
}

tempconfigfile, err := ioutil.TempFile("", "")
tempconfigfile, err := os.CreateTemp("", "")
if err != nil {
return err
}
defer os.Remove(tempconfigfile.Name())
pc := tempconfigfile.Name()
defer utiltesting.CloseAndRemove(t, tempconfigfile)

configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplJSON)
if err != nil {
Expand All @@ -229,7 +230,7 @@ current-context: default
RetryBackoff int
DefaultAllow bool
}{
KubeConfig: tempfile.Name(),
KubeConfig: p,
AllowTTL: 500,
DenyTTL: 500,
RetryBackoff: 500,
Expand All @@ -240,7 +241,7 @@ current-context: default
}

// Create a new admission controller
configFile, err := os.Open(tempconfigfile.Name())
configFile, err := os.Open(pc)
if err != nil {
return fmt.Errorf("failed to read test config: %v", err)
}
Expand Down Expand Up @@ -358,13 +359,13 @@ func (m *mockService) HTTPStatusCode() int { return m.statusCode }

// newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load
// a new newImagePolicyWebhook from it.
func newImagePolicyWebhook(t *testing.T, callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
tempfile, err := ioutil.TempFile("", "")
func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
tempfile, err := os.CreateTemp("", "")
if err != nil {
return nil, err
}
p := tempfile.Name()
defer utiltesting.CloseAndRemove(t, tempfile)
defer os.Remove(p)
config := v1.Config{
Clusters: []v1.NamedCluster{
{
Expand All @@ -381,12 +382,12 @@ func newImagePolicyWebhook(t *testing.T, callbackURL string, clientCert, clientK
return nil, err
}

tempconfigfile, err := ioutil.TempFile("", "")
tempconfigfile, err := os.CreateTemp("", "")
if err != nil {
return nil, err
}
pc := tempconfigfile.Name()
defer utiltesting.CloseAndRemove(t, tempconfigfile)
defer os.Remove(pc)

configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplYAML)
if err != nil {
Expand Down Expand Up @@ -478,7 +479,7 @@ func TestTLSConfig(t *testing.T) {
}
defer server.Close()

wh, err := newImagePolicyWebhook(t, server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
wh, err := newImagePolicyWebhook(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err)
return
Expand Down Expand Up @@ -559,7 +560,7 @@ func TestWebhookCache(t *testing.T) {
defer s.Close()

// Create an admission controller that caches successful responses.
wh, err := newImagePolicyWebhook(t, s.URL, clientCert, clientKey, caCert, 200, false)
wh, err := newImagePolicyWebhook(s.URL, clientCert, clientKey, caCert, 200, false)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -595,17 +596,23 @@ func TestContainerCombinations(t *testing.T) {
test string
pod *api.Pod
wantAllowed, wantErr bool
subresource string
operation admission.Operation
}{
{
test: "Single container allowed",
pod: goodPod("good"),
wantAllowed: true,
subresource: "",
operation: admission.Create,
},
{
test: "Single container denied",
pod: goodPod("bad"),
wantAllowed: false,
wantErr: true,
subresource: "",
operation: admission.Create,
},
{
test: "One good container, one bad",
Expand All @@ -627,6 +634,8 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: false,
wantErr: true,
subresource: "",
operation: admission.Create,
},
{
test: "Multiple good containers",
Expand All @@ -648,6 +657,8 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: true,
wantErr: false,
subresource: "",
operation: admission.Create,
},
{
test: "Multiple bad containers",
Expand All @@ -669,6 +680,8 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: false,
wantErr: true,
subresource: "",
operation: admission.Create,
},
{
test: "Good container, bad init container",
Expand All @@ -692,6 +705,8 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: false,
wantErr: true,
subresource: "",
operation: admission.Create,
},
{
test: "Bad container, good init container",
Expand All @@ -715,6 +730,8 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: false,
wantErr: true,
subresource: "",
operation: admission.Create,
},
{
test: "Good container, good init container",
Expand All @@ -738,6 +755,123 @@ func TestContainerCombinations(t *testing.T) {
},
wantAllowed: true,
wantErr: false,
subresource: "",
operation: admission.Create,
},
{
test: "Good container, good init container, bad ephemeral container when updating ephemeralcontainers subresource",
pod: &api.Pod{
Spec: api.PodSpec{
ServiceAccountName: "default",
SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{
{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
InitContainers: []api.Container{
{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
EphemeralContainers: []api.EphemeralContainer{
{
EphemeralContainerCommon: api.EphemeralContainerCommon{
Image: "bad",
SecurityContext: &api.SecurityContext{},
},
},
},
},
},
wantAllowed: false,
wantErr: true,
subresource: "ephemeralcontainers",
operation: admission.Update,
},
{
test: "Good container, good init container, bad ephemeral container when updating subresource=='' which sets initContainer and container only",
pod: &api.Pod{
Spec: api.PodSpec{
ServiceAccountName: "default",
SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{
{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
InitContainers: []api.Container{
{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
EphemeralContainers: []api.EphemeralContainer{
{
EphemeralContainerCommon: api.EphemeralContainerCommon{
Image: "bad",
SecurityContext: &api.SecurityContext{},
},
},
},
},
},
wantAllowed: true,
wantErr: false,
subresource: "",
operation: admission.Update,
},

{
test: "Bad container, good ephemeral container when updating subresource=='ephemeralcontainers' which sets ephemeralcontainers only",
pod: &api.Pod{
Spec: api.PodSpec{
ServiceAccountName: "default",
SecurityContext: &api.PodSecurityContext{},
Containers: []api.Container{
{
Image: "bad",
SecurityContext: &api.SecurityContext{},
},
},
EphemeralContainers: []api.EphemeralContainer{
{
EphemeralContainerCommon: api.EphemeralContainerCommon{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
},
},
},
wantAllowed: true,
wantErr: false,
subresource: "ephemeralcontainers",
operation: admission.Update,
},
{
test: "Good ephemeral container",
pod: &api.Pod{
Spec: api.PodSpec{
ServiceAccountName: "default",
SecurityContext: &api.PodSecurityContext{},
EphemeralContainers: []api.EphemeralContainer{
{
EphemeralContainerCommon: api.EphemeralContainerCommon{
Image: "good",
SecurityContext: &api.SecurityContext{},
},
},
},
},
},
wantAllowed: true,
wantErr: false,
subresource: "ephemeralcontainers",
operation: admission.Update,
},
}
for _, tt := range tests {
Expand All @@ -753,13 +887,13 @@ func TestContainerCombinations(t *testing.T) {
}
defer server.Close()

wh, err := newImagePolicyWebhook(t, server.URL, clientCert, clientKey, caCert, 0, false)
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, false)
if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err)
return
}

attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, &user.DefaultInfo{})
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), tt.subresource, tt.operation, &metav1.CreateOptions{}, false, &user.DefaultInfo{})

err = wh.Validate(context.TODO(), attr, nil)
if tt.wantAllowed {
Expand Down Expand Up @@ -847,7 +981,7 @@ func TestDefaultAllow(t *testing.T) {
}
defer server.Close()

wh, err := newImagePolicyWebhook(t, server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err)
return
Expand Down Expand Up @@ -954,7 +1088,7 @@ func TestAnnotationFiltering(t *testing.T) {
}
defer server.Close()

wh, err := newImagePolicyWebhook(t, server.URL, clientCert, clientKey, caCert, 0, true)
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err)
return
Expand Down Expand Up @@ -1047,7 +1181,7 @@ func TestReturnedAnnotationAdd(t *testing.T) {
}
defer server.Close()

wh, err := newImagePolicyWebhook(t, server.URL, clientCert, clientKey, caCert, 0, true)
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
if err != nil {
t.Errorf("%s: failed to create client: %v", tt.test, err)
return
Expand Down

0 comments on commit d6168bb

Please sign in to comment.