!148 adapt old patch & fix CVE-2024-3177
From: @liuxu180400617 Reviewed-by: @xuxuepeng Signed-off-by: @xuxuepeng
This commit is contained in:
commit
e14c9359bc
@ -1,18 +1,18 @@
|
||||
From d49390e67b2d3027c8c0ad799bf294a89d4d149f Mon Sep 17 00:00:00 2001
|
||||
From: zhangxiaoyu <zhangxiaoyu58@huawei.com>
|
||||
Date: Thu, 20 Jul 2023 17:27:05 +0800
|
||||
From ed51f4be525cbaf5d2fa508260e1ecc80c95dd25 Mon Sep 17 00:00:00 2001
|
||||
From: liuxu <liuxu156@huawei.com>
|
||||
Date: Fri, 26 Apr 2024 10:30:18 +0800
|
||||
Subject: [PATCH] fix compile options
|
||||
|
||||
Signed-off-by: zhangxiaoyu <zhangxiaoyu58@huawei.com>
|
||||
Signed-off-by: liuxu <liuxu156@huawei.com>
|
||||
---
|
||||
hack/lib/golang.sh | 4 +++-
|
||||
1 file changed, 3 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh
|
||||
index 232c17b3..c6cbce44 100755
|
||||
index f3466ff7..0cc59787 100755
|
||||
--- a/hack/lib/golang.sh
|
||||
+++ b/hack/lib/golang.sh
|
||||
@@ -744,6 +744,7 @@ kube::golang::build_binaries_for_platform() {
|
||||
@@ -781,6 +781,7 @@ kube::golang::build_binaries_for_platform() {
|
||||
-installsuffix=static
|
||||
${goflags:+"${goflags[@]}"}
|
||||
-gcflags="${gogcflags}"
|
||||
@ -20,7 +20,7 @@ index 232c17b3..c6cbce44 100755
|
||||
-asmflags="${goasmflags}"
|
||||
-ldflags="${goldflags}"
|
||||
-tags="${gotags:-}"
|
||||
@@ -755,6 +756,7 @@ kube::golang::build_binaries_for_platform() {
|
||||
@@ -792,6 +793,7 @@ kube::golang::build_binaries_for_platform() {
|
||||
build_args=(
|
||||
${goflags:+"${goflags[@]}"}
|
||||
-gcflags="${gogcflags}"
|
||||
@ -28,7 +28,7 @@ index 232c17b3..c6cbce44 100755
|
||||
-asmflags="${goasmflags}"
|
||||
-ldflags="${goldflags}"
|
||||
-tags="${gotags:-}"
|
||||
@@ -844,7 +846,7 @@ kube::golang::build_binaries() {
|
||||
@@ -881,7 +883,7 @@ kube::golang::build_binaries() {
|
||||
goasmflags=""
|
||||
fi
|
||||
|
||||
@ -38,5 +38,5 @@ index 232c17b3..c6cbce44 100755
|
||||
# Not debugging - disable symbols and DWARF.
|
||||
goldflags="${goldflags} -s -w"
|
||||
--
|
||||
2.25.1
|
||||
2.34.1
|
||||
|
||||
|
||||
@ -1,20 +1,111 @@
|
||||
From cb4f12375beffd61f3b2d666ca3f92d574ceb597 Mon Sep 17 00:00:00 2001
|
||||
From: zhangxiaoyu <zhangxiaoyu58@huawei.com>
|
||||
Date: Tue, 3 Jan 2023 14:20:10 +0800
|
||||
From 5ad72e2b135afb5fbe5112f901bdec79f8943611 Mon Sep 17 00:00:00 2001
|
||||
From: liuxu <liuxu156@huawei.com>
|
||||
Date: Fri, 26 Apr 2024 10:49:14 +0800
|
||||
Subject: [PATCH] kubelet support exec and attach websocket protocol
|
||||
|
||||
Signed-off-by: zhangxiaoyu <zhangxiaoyu58@huawei.com>
|
||||
Signed-off-by: liuxu <liuxu156@huawei.com>
|
||||
---
|
||||
.../cri/streaming/remotecommand/proxy.go | 212 ++++++++++++++++++
|
||||
pkg/kubelet/server/server.go | 43 +++-
|
||||
.../pkg/cri/streaming/remotecommand/proxy.go | 212 ++++++++++++++++++
|
||||
2 files changed, 247 insertions(+), 8 deletions(-)
|
||||
create mode 100644 pkg/kubelet/cri/streaming/remotecommand/proxy.go
|
||||
create mode 100644 staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/proxy.go
|
||||
|
||||
diff --git a/pkg/kubelet/cri/streaming/remotecommand/proxy.go b/pkg/kubelet/cri/streaming/remotecommand/proxy.go
|
||||
diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go
|
||||
index 87a017f9..887e663a 100644
|
||||
--- a/pkg/kubelet/server/server.go
|
||||
+++ b/pkg/kubelet/server/server.go
|
||||
@@ -834,51 +834,78 @@ func proxyStream(w http.ResponseWriter, r *http.Request, url *url.URL) {
|
||||
|
||||
// getAttach handles requests to attach to a container.
|
||||
func (s *Server) getAttach(request *restful.Request, response *restful.Response) {
|
||||
- params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
+
|
||||
+ url, err := s.getAttachUrl(request, response, streamOpts)
|
||||
+ if err != nil {
|
||||
+ klog.Errorf("failed to get backend url %v", err)
|
||||
+ return
|
||||
+ }
|
||||
+ if url.Scheme == "ws" || url.Scheme == "wss" {
|
||||
+ remotecommandserver.ProxyToWebSocket(response.ResponseWriter, request.Request, url, streamOpts)
|
||||
+ } else {
|
||||
+ proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *Server) getAttachUrl(request *restful.Request, response *restful.Response, streamOpts *remotecommandserver.Options) (*url.URL, error) {
|
||||
+ params := getExecRequestParams(request)
|
||||
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
||||
if !ok {
|
||||
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
||||
- return
|
||||
+ return nil, fmt.Errorf("pod not found")
|
||||
}
|
||||
|
||||
podFullName := kubecontainer.GetPodFullName(pod)
|
||||
url, err := s.host.GetAttach(request.Request.Context(), podFullName, params.podUID, params.containerName, *streamOpts)
|
||||
if err != nil {
|
||||
streaming.WriteError(err, response.ResponseWriter)
|
||||
- return
|
||||
+ return nil, err
|
||||
}
|
||||
|
||||
- proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ return url, nil
|
||||
}
|
||||
|
||||
// getExec handles requests to run a command inside a container.
|
||||
func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
||||
- params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
+ url, err := s.getExecUrl(request, response, streamOpts)
|
||||
+ if err != nil {
|
||||
+ klog.Errorf("failed to get backend url %v", err)
|
||||
+ return
|
||||
+ }
|
||||
+ if url.Scheme == "ws" || url.Scheme == "wss" {
|
||||
+ remotecommandserver.ProxyToWebSocket(response.ResponseWriter, request.Request, url, streamOpts)
|
||||
+ } else {
|
||||
+ proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *Server) getExecUrl(request *restful.Request, response *restful.Response, streamOpts *remotecommandserver.Options) (*url.URL, error) {
|
||||
+ params := getExecRequestParams(request)
|
||||
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
||||
if !ok {
|
||||
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
||||
- return
|
||||
+ return nil, fmt.Errorf("pod not found")
|
||||
}
|
||||
|
||||
podFullName := kubecontainer.GetPodFullName(pod)
|
||||
url, err := s.host.GetExec(request.Request.Context(), podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
|
||||
if err != nil {
|
||||
streaming.WriteError(err, response.ResponseWriter)
|
||||
- return
|
||||
+ return nil, err
|
||||
}
|
||||
- proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ return url, nil
|
||||
}
|
||||
|
||||
// getRun handles requests to run a command inside a container.
|
||||
diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/proxy.go b/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/proxy.go
|
||||
new file mode 100644
|
||||
index 00000000..179d8183
|
||||
--- /dev/null
|
||||
+++ b/pkg/kubelet/cri/streaming/remotecommand/proxy.go
|
||||
+++ b/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/proxy.go
|
||||
@@ -0,0 +1,212 @@
|
||||
+package remotecommand
|
||||
+
|
||||
@ -228,97 +319,6 @@ index 00000000..179d8183
|
||||
+ }
|
||||
+ return len(p), nil
|
||||
+}
|
||||
diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go
|
||||
index 84dbd545..73627d5b 100644
|
||||
--- a/pkg/kubelet/server/server.go
|
||||
+++ b/pkg/kubelet/server/server.go
|
||||
@@ -797,51 +797,78 @@ func proxyStream(w http.ResponseWriter, r *http.Request, url *url.URL) {
|
||||
|
||||
// getAttach handles requests to attach to a container.
|
||||
func (s *Server) getAttach(request *restful.Request, response *restful.Response) {
|
||||
- params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
+
|
||||
+ url, err := s.getAttachUrl(request, response, streamOpts)
|
||||
+ if err != nil {
|
||||
+ klog.Errorf("failed to get backend url %v", err)
|
||||
+ return
|
||||
+ }
|
||||
+ if url.Scheme == "ws" || url.Scheme == "wss" {
|
||||
+ remotecommandserver.ProxyToWebSocket(response.ResponseWriter, request.Request, url, streamOpts)
|
||||
+ } else {
|
||||
+ proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *Server) getAttachUrl(request *restful.Request, response *restful.Response, streamOpts *remotecommandserver.Options) (*url.URL, error) {
|
||||
+ params := getExecRequestParams(request)
|
||||
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
||||
if !ok {
|
||||
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
||||
- return
|
||||
+ return nil, fmt.Errorf("pod not found")
|
||||
}
|
||||
|
||||
podFullName := kubecontainer.GetPodFullName(pod)
|
||||
url, err := s.host.GetAttach(podFullName, params.podUID, params.containerName, *streamOpts)
|
||||
if err != nil {
|
||||
streaming.WriteError(err, response.ResponseWriter)
|
||||
- return
|
||||
+ return nil, err
|
||||
}
|
||||
|
||||
- proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ return url, nil
|
||||
}
|
||||
|
||||
// getExec handles requests to run a command inside a container.
|
||||
func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
||||
- params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
+ url, err := s.getExecUrl(request, response, streamOpts)
|
||||
+ if err != nil {
|
||||
+ klog.Errorf("failed to get backend url %v", err)
|
||||
+ return
|
||||
+ }
|
||||
+ if url.Scheme == "ws" || url.Scheme == "wss" {
|
||||
+ remotecommandserver.ProxyToWebSocket(response.ResponseWriter, request.Request, url, streamOpts)
|
||||
+ } else {
|
||||
+ proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func (s *Server) getExecUrl(request *restful.Request, response *restful.Response, streamOpts *remotecommandserver.Options) (*url.URL, error) {
|
||||
+ params := getExecRequestParams(request)
|
||||
pod, ok := s.host.GetPodByName(params.podNamespace, params.podName)
|
||||
if !ok {
|
||||
response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist"))
|
||||
- return
|
||||
+ return nil, fmt.Errorf("pod not found")
|
||||
}
|
||||
|
||||
podFullName := kubecontainer.GetPodFullName(pod)
|
||||
url, err := s.host.GetExec(podFullName, params.podUID, params.containerName, params.cmd, *streamOpts)
|
||||
if err != nil {
|
||||
streaming.WriteError(err, response.ResponseWriter)
|
||||
- return
|
||||
+ return nil, err
|
||||
}
|
||||
- proxyStream(response.ResponseWriter, request.Request, url)
|
||||
+ return url, nil
|
||||
}
|
||||
|
||||
// getRun handles requests to run a command inside a container.
|
||||
--
|
||||
2.25.1
|
||||
2.34.1
|
||||
|
||||
|
||||
236
0003-Add-envFrom-to-serviceaccount-admission-plugin.patch
Normal file
236
0003-Add-envFrom-to-serviceaccount-admission-plugin.patch
Normal file
@ -0,0 +1,236 @@
|
||||
From 3f0922513d235d8bdebe79f0d07da769c04211b8 Mon Sep 17 00:00:00 2001
|
||||
From: Rita Zhang <rita.z.zhang@gmail.com>
|
||||
Date: Mon, 25 Mar 2024 10:33:41 -0700
|
||||
Subject: [PATCH] Add envFrom to serviceaccount admission plugin
|
||||
|
||||
Signed-off-by: Rita Zhang <rita.z.zhang@gmail.com>
|
||||
---
|
||||
.../pkg/admission/serviceaccount/admission.go | 21 +++
|
||||
.../serviceaccount/admission_test.go | 122 ++++++++++++++++--
|
||||
2 files changed, 132 insertions(+), 11 deletions(-)
|
||||
|
||||
diff --git a/plugin/pkg/admission/serviceaccount/admission.go b/plugin/pkg/admission/serviceaccount/admission.go
|
||||
index c844a051c24..3f4338128e5 100644
|
||||
--- a/plugin/pkg/admission/serviceaccount/admission.go
|
||||
+++ b/plugin/pkg/admission/serviceaccount/admission.go
|
||||
@@ -337,6 +337,13 @@ func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, po
|
||||
}
|
||||
}
|
||||
}
|
||||
+ for _, envFrom := range container.EnvFrom {
|
||||
+ if envFrom.SecretRef != nil {
|
||||
+ if !mountableSecrets.Has(envFrom.SecretRef.Name) {
|
||||
+ return fmt.Errorf("init container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
@@ -347,6 +354,13 @@ func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, po
|
||||
}
|
||||
}
|
||||
}
|
||||
+ for _, envFrom := range container.EnvFrom {
|
||||
+ if envFrom.SecretRef != nil {
|
||||
+ if !mountableSecrets.Has(envFrom.SecretRef.Name) {
|
||||
+ return fmt.Errorf("container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
// limit pull secret references as well
|
||||
@@ -388,6 +402,13 @@ func (s *Plugin) limitEphemeralContainerSecretReferences(pod *api.Pod, a admissi
|
||||
}
|
||||
}
|
||||
}
|
||||
+ for _, envFrom := range container.EnvFrom {
|
||||
+ if envFrom.SecretRef != nil {
|
||||
+ if !mountableSecrets.Has(envFrom.SecretRef.Name) {
|
||||
+ return fmt.Errorf("ephemeral container %s with envFrom referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, envFrom.SecretRef.Name, serviceAccount.Name)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
diff --git a/plugin/pkg/admission/serviceaccount/admission_test.go b/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
index bf15f870d75..4dba6cd8b13 100644
|
||||
--- a/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
+++ b/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
@@ -521,6 +521,25 @@ func TestAllowsReferencedSecret(t *testing.T) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ Containers: []api.Container{
|
||||
+ {
|
||||
+ Name: "container-1",
|
||||
+ EnvFrom: []api.EnvFromSource{
|
||||
+ {
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("Unexpected error: %v", err)
|
||||
+ }
|
||||
+
|
||||
pod2 = &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
@@ -545,6 +564,25 @@ func TestAllowsReferencedSecret(t *testing.T) {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ InitContainers: []api.Container{
|
||||
+ {
|
||||
+ Name: "container-1",
|
||||
+ EnvFrom: []api.EnvFromSource{
|
||||
+ {
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("Unexpected error: %v", err)
|
||||
+ }
|
||||
+
|
||||
pod2 = &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: DefaultServiceAccountName,
|
||||
@@ -572,6 +610,28 @@ func TestAllowsReferencedSecret(t *testing.T) {
|
||||
if err := admit.Validate(context.TODO(), attrs, nil); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
+
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ EphemeralContainers: []api.EphemeralContainer{
|
||||
+ {
|
||||
+ EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
+ Name: "container-2",
|
||||
+ EnvFrom: []api.EnvFromSource{{
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ // validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers"
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("Unexpected error: %v", err)
|
||||
+ }
|
||||
}
|
||||
|
||||
func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
@@ -628,25 +688,20 @@ func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
|
||||
pod2 = &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
- InitContainers: []api.Container{
|
||||
+ Containers: []api.Container{
|
||||
{
|
||||
Name: "container-1",
|
||||
- Env: []api.EnvVar{
|
||||
+ EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
- Name: "env-1",
|
||||
- ValueFrom: &api.EnvVarSource{
|
||||
- SecretKeyRef: &api.SecretKeySelector{
|
||||
- LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||
- },
|
||||
- },
|
||||
- },
|
||||
- },
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
- if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") {
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
@@ -679,6 +734,30 @@ func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ InitContainers: []api.Container{
|
||||
+ {
|
||||
+ Name: "container-1",
|
||||
+ EnvFrom: []api.EnvFromSource{
|
||||
+ {
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("admit only enforces restrictions on secret mounts when operation==create. Unexpected error: %v", err)
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") {
|
||||
+ t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err)
|
||||
+ }
|
||||
+
|
||||
pod2 = &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: DefaultServiceAccountName,
|
||||
@@ -709,6 +788,27 @@ func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") {
|
||||
t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err)
|
||||
}
|
||||
+
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ EphemeralContainers: []api.EphemeralContainer{
|
||||
+ {
|
||||
+ EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
+ Name: "container-2",
|
||||
+ EnvFrom: []api.EnvFromSource{{
|
||||
+ SecretRef: &api.SecretEnvSource{
|
||||
+ LocalObjectReference: api.LocalObjectReference{
|
||||
+ Name: "foo"}}}},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envFrom") {
|
||||
+ t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err)
|
||||
+ }
|
||||
}
|
||||
|
||||
func TestAllowUnreferencedSecretVolumesForPermissiveSAs(t *testing.T) {
|
||||
--
|
||||
2.34.1
|
||||
|
||||
@ -1,215 +0,0 @@
|
||||
From 98128bcf1bd9c9ea86c1c8d48c0b1acda4ec7e73 Mon Sep 17 00:00:00 2001
|
||||
From: David Leadbeater <dgl@dgl.cx>
|
||||
Date: Mon, 31 Oct 2022 01:08:43 +1000
|
||||
Subject: [PATCH] Escape terminal special characters in kubectl (#112553)
|
||||
|
||||
* Escape terminal special characters in kubectl
|
||||
|
||||
* Add escaping for kubectl alpha events
|
||||
---
|
||||
.../cli-runtime/pkg/printers/tableprinter.go | 13 ++++---
|
||||
.../pkg/printers/tableprinter_test.go | 12 ++++++
|
||||
.../cli-runtime/pkg/printers/terminal.go | 39 +++++++++++++++++++
|
||||
.../kubectl/pkg/cmd/get/customcolumn.go | 2 +-
|
||||
.../kubectl/pkg/cmd/get/customcolumn_test.go | 16 ++++++++
|
||||
.../k8s.io/kubectl/pkg/describe/describe.go | 7 +++-
|
||||
.../kubectl/pkg/describe/describe_test.go | 19 +++++++++
|
||||
7 files changed, 99 insertions(+), 9 deletions(-)
|
||||
create mode 100644 staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
|
||||
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
|
||||
index 87bc3f41..54859665 100644
|
||||
--- a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter.go
|
||||
@@ -212,18 +212,19 @@ func printTable(table *metav1.Table, output io.Writer, options PrintOptions) err
|
||||
case string:
|
||||
print := val
|
||||
truncated := false
|
||||
- // truncate at newlines
|
||||
- newline := strings.Index(print, "\n")
|
||||
- if newline >= 0 {
|
||||
+ // Truncate at the first newline, carriage return or formfeed
|
||||
+ // (treated as a newline by tabwriter).
|
||||
+ breakchar := strings.IndexAny(print, "\f\n\r")
|
||||
+ if breakchar >= 0 {
|
||||
truncated = true
|
||||
- print = print[:newline]
|
||||
+ print = print[:breakchar]
|
||||
}
|
||||
- fmt.Fprint(output, print)
|
||||
+ WriteEscaped(output, print)
|
||||
if truncated {
|
||||
fmt.Fprint(output, "...")
|
||||
}
|
||||
default:
|
||||
- fmt.Fprint(output, val)
|
||||
+ WriteEscaped(output, fmt.Sprint(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
|
||||
index b2caaa12..edbff3ff 100644
|
||||
--- a/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/tableprinter_test.go
|
||||
@@ -769,6 +769,18 @@ test1 20h This is first line which is long and goes for on and on and on an
|
||||
},
|
||||
expected: `NAME AGE DESCRIPTION
|
||||
test1 20h This is first...
|
||||
+`,
|
||||
+ },
|
||||
+ // terminal special character, should be escaped
|
||||
+ {
|
||||
+ columns: []metav1.TableColumnDefinition{
|
||||
+ {Name: "Name", Type: "string"},
|
||||
+ },
|
||||
+ rows: []metav1.TableRow{
|
||||
+ {Cells: []interface{}{"test1\x1b"}},
|
||||
+ },
|
||||
+ expected: `NAME
|
||||
+test1^[
|
||||
`,
|
||||
},
|
||||
}
|
||||
diff --git a/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go b/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
|
||||
new file mode 100644
|
||||
index 00000000..5a59491e
|
||||
--- /dev/null
|
||||
+++ b/staging/src/k8s.io/cli-runtime/pkg/printers/terminal.go
|
||||
@@ -0,0 +1,39 @@
|
||||
+/*
|
||||
+Copyright 2022 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 printers
|
||||
+
|
||||
+import (
|
||||
+ "io"
|
||||
+ "strings"
|
||||
+)
|
||||
+
|
||||
+// terminalEscaper replaces ANSI escape sequences and other terminal special
|
||||
+// characters to avoid terminal escape character attacks (issue #101695).
|
||||
+var terminalEscaper = strings.NewReplacer("\x1b", "^[", "\r", "\\r")
|
||||
+
|
||||
+// WriteEscaped replaces unsafe terminal characters with replacement strings
|
||||
+// and writes them to the given writer.
|
||||
+func WriteEscaped(writer io.Writer, output string) error {
|
||||
+ _, err := terminalEscaper.WriteString(writer, output)
|
||||
+ return err
|
||||
+}
|
||||
+
|
||||
+// EscapeTerminal escapes terminal special characters in a human readable (but
|
||||
+// non-reversible) format.
|
||||
+func EscapeTerminal(in string) string {
|
||||
+ return terminalEscaper.Replace(in)
|
||||
+}
|
||||
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
|
||||
index 2b205667..38024cfa 100644
|
||||
--- a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
|
||||
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn.go
|
||||
@@ -252,7 +252,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
|
||||
}
|
||||
for arrIx := range values {
|
||||
for valIx := range values[arrIx] {
|
||||
- valueStrings = append(valueStrings, fmt.Sprintf("%v", values[arrIx][valIx].Interface()))
|
||||
+ valueStrings = append(valueStrings, printers.EscapeTerminal(fmt.Sprint(values[arrIx][valIx].Interface())))
|
||||
}
|
||||
}
|
||||
columns[ix] = strings.Join(valueStrings, ",")
|
||||
diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
|
||||
index e4fb17a8..de403142 100644
|
||||
--- a/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
|
||||
+++ b/staging/src/k8s.io/kubectl/pkg/cmd/get/customcolumn_test.go
|
||||
@@ -311,6 +311,22 @@ foo baz
|
||||
obj: &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, TypeMeta: metav1.TypeMeta{APIVersion: "baz"}},
|
||||
expectedOutput: `NAME API_VERSION NOT_FOUND
|
||||
foo baz <none>
|
||||
+`,
|
||||
+ },
|
||||
+ {
|
||||
+ columns: []Column{
|
||||
+ {
|
||||
+ Header: "NAME",
|
||||
+ FieldSpec: "{.metadata.name}",
|
||||
+ },
|
||||
+ },
|
||||
+ obj: &corev1.PodList{
|
||||
+ Items: []corev1.Pod{
|
||||
+ {ObjectMeta: metav1.ObjectMeta{Name: "\x1b \r"}},
|
||||
+ },
|
||||
+ },
|
||||
+ expectedOutput: `NAME
|
||||
+^[ \r
|
||||
`,
|
||||
},
|
||||
}
|
||||
diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe.go b/staging/src/k8s.io/kubectl/pkg/describe/describe.go
|
||||
index 484ed09f..d3b65132 100644
|
||||
--- a/staging/src/k8s.io/kubectl/pkg/describe/describe.go
|
||||
+++ b/staging/src/k8s.io/kubectl/pkg/describe/describe.go
|
||||
@@ -65,6 +65,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
+ "k8s.io/cli-runtime/pkg/printers"
|
||||
runtimeresource "k8s.io/cli-runtime/pkg/resource"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
@@ -148,11 +149,13 @@ func (pw *prefixWriter) Write(level int, format string, a ...interface{}) {
|
||||
for i := 0; i < level; i++ {
|
||||
prefix += levelSpace
|
||||
}
|
||||
- fmt.Fprintf(pw.out, prefix+format, a...)
|
||||
+ output := fmt.Sprintf(prefix+format, a...)
|
||||
+ printers.WriteEscaped(pw.out, output)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) WriteLine(a ...interface{}) {
|
||||
- fmt.Fprintln(pw.out, a...)
|
||||
+ output := fmt.Sprintln(a...)
|
||||
+ printers.WriteEscaped(pw.out, output)
|
||||
}
|
||||
|
||||
func (pw *prefixWriter) Flush() {
|
||||
diff --git a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
|
||||
index 5225ec2d..a3e2feba 100644
|
||||
--- a/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
|
||||
+++ b/staging/src/k8s.io/kubectl/pkg/describe/describe_test.go
|
||||
@@ -5520,3 +5520,22 @@ func TestControllerRef(t *testing.T) {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
}
|
||||
+
|
||||
+func TestDescribeTerminalEscape(t *testing.T) {
|
||||
+ fake := fake.NewSimpleClientset(&corev1.ConfigMap{
|
||||
+ ObjectMeta: metav1.ObjectMeta{
|
||||
+ Name: "mycm",
|
||||
+ Namespace: "foo",
|
||||
+ Annotations: map[string]string{"annotation1": "terminal escape: \x1b"},
|
||||
+ },
|
||||
+ })
|
||||
+ c := &describeClient{T: t, Namespace: "foo", Interface: fake}
|
||||
+ d := ConfigMapDescriber{c}
|
||||
+ out, err := d.Describe("foo", "mycm", DescriberSettings{ShowEvents: true})
|
||||
+ if err != nil {
|
||||
+ t.Errorf("unexpected error: %v", err)
|
||||
+ }
|
||||
+ if strings.Contains(out, "\x1b") || !strings.Contains(out, "^[") {
|
||||
+ t.Errorf("unexpected out: %s", out)
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -1,439 +0,0 @@
|
||||
From 604ad21799c43d87456cc76d3e591487de0a5152 Mon Sep 17 00:00:00 2001
|
||||
From: Craig Ingram <cjingram@google.com>
|
||||
Date: Fri, 24 Feb 2023 15:24:49 -0500
|
||||
Subject: [PATCH] Return error for localhost seccomp type with no localhost
|
||||
profile defined
|
||||
|
||||
---
|
||||
pkg/kubelet/kuberuntime/helpers.go | 60 +++++-----
|
||||
pkg/kubelet/kuberuntime/helpers_test.go | 120 +++++++++++++-------
|
||||
pkg/kubelet/kuberuntime/security_context.go | 11 +-
|
||||
3 files changed, 121 insertions(+), 70 deletions(-)
|
||||
|
||||
diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go
|
||||
index 9343b78335a..14ed0dd3bfb 100644
|
||||
--- a/pkg/kubelet/kuberuntime/helpers.go
|
||||
+++ b/pkg/kubelet/kuberuntime/helpers.go
|
||||
@@ -210,32 +210,36 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim
|
||||
return &kubecontainer.RuntimeStatus{Conditions: conditions}
|
||||
}
|
||||
|
||||
-func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) string {
|
||||
+func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) (string, error) {
|
||||
if scmp == nil {
|
||||
if fallbackToRuntimeDefault {
|
||||
- return v1.SeccompProfileRuntimeDefault
|
||||
+ return v1.SeccompProfileRuntimeDefault, nil
|
||||
}
|
||||
- return ""
|
||||
+ return "", nil
|
||||
}
|
||||
if scmp.Type == v1.SeccompProfileTypeRuntimeDefault {
|
||||
- return v1.SeccompProfileRuntimeDefault
|
||||
+ return v1.SeccompProfileRuntimeDefault, nil
|
||||
}
|
||||
- if scmp.Type == v1.SeccompProfileTypeLocalhost && scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 {
|
||||
- fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile)
|
||||
- return v1.SeccompLocalhostProfileNamePrefix + fname
|
||||
+ if scmp.Type == v1.SeccompProfileTypeLocalhost {
|
||||
+ if scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 {
|
||||
+ fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile)
|
||||
+ return v1.SeccompLocalhostProfileNamePrefix + fname, nil
|
||||
+ } else {
|
||||
+ return "", fmt.Errorf("localhostProfile must be set if seccompProfile type is Localhost.")
|
||||
+ }
|
||||
}
|
||||
if scmp.Type == v1.SeccompProfileTypeUnconfined {
|
||||
- return v1.SeccompProfileNameUnconfined
|
||||
+ return v1.SeccompProfileNameUnconfined, nil
|
||||
}
|
||||
|
||||
if fallbackToRuntimeDefault {
|
||||
- return v1.SeccompProfileRuntimeDefault
|
||||
+ return v1.SeccompProfileRuntimeDefault, nil
|
||||
}
|
||||
- return ""
|
||||
+ return "", nil
|
||||
}
|
||||
|
||||
func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string]string, containerName string,
|
||||
- podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) string {
|
||||
+ podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) (string, error) {
|
||||
// container fields are applied first
|
||||
if containerSecContext != nil && containerSecContext.SeccompProfile != nil {
|
||||
return fieldProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
|
||||
@@ -247,42 +251,46 @@ func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string
|
||||
}
|
||||
|
||||
if fallbackToRuntimeDefault {
|
||||
- return v1.SeccompProfileRuntimeDefault
|
||||
+ return v1.SeccompProfileRuntimeDefault, nil
|
||||
}
|
||||
|
||||
- return ""
|
||||
+ return "", nil
|
||||
}
|
||||
|
||||
-func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile {
|
||||
+func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) (*runtimeapi.SecurityProfile, error) {
|
||||
if scmp == nil {
|
||||
if fallbackToRuntimeDefault {
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_Unconfined,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
if scmp.Type == v1.SeccompProfileTypeRuntimeDefault {
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
- if scmp.Type == v1.SeccompProfileTypeLocalhost && scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 {
|
||||
- fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile)
|
||||
- return &runtimeapi.SecurityProfile{
|
||||
- ProfileType: runtimeapi.SecurityProfile_Localhost,
|
||||
- LocalhostRef: fname,
|
||||
+ if scmp.Type == v1.SeccompProfileTypeLocalhost {
|
||||
+ if scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 {
|
||||
+ fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile)
|
||||
+ return &runtimeapi.SecurityProfile{
|
||||
+ ProfileType: runtimeapi.SecurityProfile_Localhost,
|
||||
+ LocalhostRef: fname,
|
||||
+ }, nil
|
||||
+ } else {
|
||||
+ return nil, fmt.Errorf("localhostProfile must be set if seccompProfile type is Localhost.")
|
||||
}
|
||||
}
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_Unconfined,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
|
||||
func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]string, containerName string,
|
||||
- podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile {
|
||||
+ podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) (*runtimeapi.SecurityProfile, error) {
|
||||
// container fields are applied first
|
||||
if containerSecContext != nil && containerSecContext.SeccompProfile != nil {
|
||||
return fieldSeccompProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
|
||||
@@ -296,10 +304,10 @@ func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]str
|
||||
if fallbackToRuntimeDefault {
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
|
||||
return &runtimeapi.SecurityProfile{
|
||||
ProfileType: runtimeapi.SecurityProfile_Unconfined,
|
||||
- }
|
||||
+ }, nil
|
||||
}
|
||||
diff --git a/pkg/kubelet/kuberuntime/helpers_test.go b/pkg/kubelet/kuberuntime/helpers_test.go
|
||||
index 83a8da8793a..5fbe640b44a 100644
|
||||
--- a/pkg/kubelet/kuberuntime/helpers_test.go
|
||||
+++ b/pkg/kubelet/kuberuntime/helpers_test.go
|
||||
@@ -242,17 +242,18 @@ func TestFieldProfile(t *testing.T) {
|
||||
scmpProfile *v1.SeccompProfile
|
||||
rootPath string
|
||||
expectedProfile string
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccompProfile should return empty",
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
- description: "type localhost without profile should return empty",
|
||||
+ description: "type localhost without profile should return error",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
},
|
||||
- expectedProfile: "",
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "unknown type should return empty",
|
||||
@@ -279,7 +280,7 @@ func TestFieldProfile(t *testing.T) {
|
||||
description: "SeccompProfileTypeLocalhost should return localhost",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
- LocalhostProfile: utilpointer.StringPtr("profile.json"),
|
||||
+ LocalhostProfile: utilpointer.String("profile.json"),
|
||||
},
|
||||
rootPath: "/test/",
|
||||
expectedProfile: "localhost//test/profile.json",
|
||||
@@ -287,8 +288,13 @@ func TestFieldProfile(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := fieldProfile(test.scmpProfile, test.rootPath, false)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := fieldProfile(test.scmpProfile, test.rootPath, false)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,17 +304,18 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) {
|
||||
scmpProfile *v1.SeccompProfile
|
||||
rootPath string
|
||||
expectedProfile string
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccompProfile should return runtime/default",
|
||||
expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
},
|
||||
{
|
||||
- description: "type localhost without profile should return runtime/default",
|
||||
+ description: "type localhost without profile should return error",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
},
|
||||
- expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "unknown type should return runtime/default",
|
||||
@@ -335,7 +342,7 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) {
|
||||
description: "SeccompProfileTypeLocalhost should return localhost",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
- LocalhostProfile: utilpointer.StringPtr("profile.json"),
|
||||
+ LocalhostProfile: utilpointer.String("profile.json"),
|
||||
},
|
||||
rootPath: "/test/",
|
||||
expectedProfile: "localhost//test/profile.json",
|
||||
@@ -343,8 +350,13 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := fieldProfile(test.scmpProfile, test.rootPath, true)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := fieldProfile(test.scmpProfile, test.rootPath, true)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,6 +371,7 @@ func TestGetSeccompProfilePath(t *testing.T) {
|
||||
containerSc *v1.SecurityContext
|
||||
containerName string
|
||||
expectedProfile string
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccomp should return empty",
|
||||
@@ -395,14 +408,14 @@ func TestGetSeccompProfilePath(t *testing.T) {
|
||||
expectedProfile: seccompLocalhostPath("filename"),
|
||||
},
|
||||
{
|
||||
- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns empty",
|
||||
- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: "",
|
||||
+ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
- description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns empty",
|
||||
- containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: "",
|
||||
+ description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
@@ -418,8 +431,13 @@ func TestGetSeccompProfilePath(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, false)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, false)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +452,7 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) {
|
||||
containerSc *v1.SecurityContext
|
||||
containerName string
|
||||
expectedProfile string
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccomp should return runtime/default",
|
||||
@@ -470,14 +489,14 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) {
|
||||
expectedProfile: seccompLocalhostPath("filename"),
|
||||
},
|
||||
{
|
||||
- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns runtime/default",
|
||||
- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
+ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
- description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns runtime/default",
|
||||
- containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
+ description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
@@ -493,8 +512,13 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, true)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, true)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +541,7 @@ func TestGetSeccompProfile(t *testing.T) {
|
||||
containerSc *v1.SecurityContext
|
||||
containerName string
|
||||
expectedProfile *runtimeapi.SecurityProfile
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccomp should return unconfined",
|
||||
@@ -551,14 +576,14 @@ func TestGetSeccompProfile(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined",
|
||||
- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: unconfinedProfile,
|
||||
+ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
- description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined",
|
||||
- containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: unconfinedProfile,
|
||||
+ description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
@@ -587,8 +612,13 @@ func TestGetSeccompProfile(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,6 +641,7 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) {
|
||||
containerSc *v1.SecurityContext
|
||||
containerName string
|
||||
expectedProfile *runtimeapi.SecurityProfile
|
||||
+ expectedError string
|
||||
}{
|
||||
{
|
||||
description: "no seccomp should return RuntimeDefault",
|
||||
@@ -645,14 +676,14 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined",
|
||||
- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: unconfinedProfile,
|
||||
+ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
- description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined",
|
||||
- containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
- expectedProfile: unconfinedProfile,
|
||||
+ description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error",
|
||||
+ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
+ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
@@ -681,8 +712,13 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
- seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true)
|
||||
- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true)
|
||||
+ if test.expectedError != "" {
|
||||
+ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description)
|
||||
+ } else {
|
||||
+ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description)
|
||||
+ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/pkg/kubelet/kuberuntime/security_context.go b/pkg/kubelet/kuberuntime/security_context.go
|
||||
index 5e6f05b4e18..d933a710424 100644
|
||||
--- a/pkg/kubelet/kuberuntime/security_context.go
|
||||
+++ b/pkg/kubelet/kuberuntime/security_context.go
|
||||
@@ -37,9 +37,16 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po
|
||||
|
||||
// TODO: Deprecated, remove after we switch to Seccomp field
|
||||
// set SeccompProfilePath.
|
||||
- synthesized.SeccompProfilePath = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
|
||||
+ var err error
|
||||
+ synthesized.SeccompProfilePath, err = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
|
||||
- synthesized.Seccomp = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
|
||||
+ synthesized.Seccomp, err = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
|
||||
// set ApparmorProfile.
|
||||
synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -1,806 +0,0 @@
|
||||
From 6775c99cd008c457ce3eed401ac1c60c3812dbfa Mon Sep 17 00:00:00 2001
|
||||
From: Tim Allclair <tallclair@google.com>
|
||||
Date: Mon, 10 Oct 2022 18:15:22 -0700
|
||||
Subject: [PATCH] Validate etcd paths
|
||||
|
||||
---
|
||||
.../pkg/storage/etcd3/linearized_read_test.go | 5 +-
|
||||
.../apiserver/pkg/storage/etcd3/store.go | 138 +++++++++++------
|
||||
.../apiserver/pkg/storage/etcd3/store_test.go | 140 ++++++++++++++----
|
||||
.../pkg/storage/testing/store_tests.go | 24 +--
|
||||
4 files changed, 218 insertions(+), 89 deletions(-)
|
||||
|
||||
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/linearized_read_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/linearized_read_test.go
|
||||
index bb1b9df7818..7331c8245ad 100644
|
||||
--- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/linearized_read_test.go
|
||||
+++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/linearized_read_test.go
|
||||
@@ -37,7 +37,8 @@ func TestLinearizedReadRevisionInvariant(t *testing.T) {
|
||||
// [1] https://etcd.io/docs/v3.5/learning/api_guarantees/#isolation-level-and-consistency-of-replicas
|
||||
ctx, store, etcdClient := testSetup(t)
|
||||
|
||||
- key := "/testkey"
|
||||
+ dir := "/testing"
|
||||
+ key := dir + "/testkey"
|
||||
out := &example.Pod{}
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo", SelfLink: "testlink"}}
|
||||
|
||||
@@ -53,7 +54,7 @@ func TestLinearizedReadRevisionInvariant(t *testing.T) {
|
||||
}
|
||||
|
||||
list := &example.PodList{}
|
||||
- if err := store.GetList(ctx, "/", storage.ListOptions{Predicate: storage.Everything, Recursive: true}, list); err != nil {
|
||||
+ if err := store.GetList(ctx, dir, storage.ListOptions{Predicate: storage.Everything, Recursive: true}, list); err != nil {
|
||||
t.Errorf("Unexpected List error: %v", err)
|
||||
}
|
||||
finalRevision := list.ResourceVersion
|
||||
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go
|
||||
index 163eb111bcb..b2e4c674687 100644
|
||||
--- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go
|
||||
+++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go
|
||||
@@ -98,16 +98,21 @@ func New(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object,
|
||||
|
||||
func newStore(c *clientv3.Client, codec runtime.Codec, newFunc func() runtime.Object, prefix string, groupResource schema.GroupResource, transformer value.Transformer, pagingEnabled bool, leaseManagerConfig LeaseManagerConfig) *store {
|
||||
versioner := storage.APIObjectVersioner{}
|
||||
+ // for compatibility with etcd2 impl.
|
||||
+ // no-op for default prefix of '/registry'.
|
||||
+ // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
|
||||
+ pathPrefix := path.Join("/", prefix)
|
||||
+ if !strings.HasSuffix(pathPrefix, "/") {
|
||||
+ // Ensure the pathPrefix ends in "/" here to simplify key concatenation later.
|
||||
+ pathPrefix += "/"
|
||||
+ }
|
||||
result := &store{
|
||||
- client: c,
|
||||
- codec: codec,
|
||||
- versioner: versioner,
|
||||
- transformer: transformer,
|
||||
- pagingEnabled: pagingEnabled,
|
||||
- // for compatibility with etcd2 impl.
|
||||
- // no-op for default prefix of '/registry'.
|
||||
- // keeps compatibility with etcd2 impl for custom prefixes that don't start with '/'
|
||||
- pathPrefix: path.Join("/", prefix),
|
||||
+ client: c,
|
||||
+ codec: codec,
|
||||
+ versioner: versioner,
|
||||
+ transformer: transformer,
|
||||
+ pagingEnabled: pagingEnabled,
|
||||
+ pathPrefix: pathPrefix,
|
||||
groupResource: groupResource,
|
||||
groupResourceString: groupResource.String(),
|
||||
watcher: newWatcher(c, codec, newFunc, versioner, transformer),
|
||||
@@ -123,9 +128,12 @@ func (s *store) Versioner() storage.Versioner {
|
||||
|
||||
// Get implements storage.Interface.Get.
|
||||
func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, out runtime.Object) error {
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
startTime := time.Now()
|
||||
- getResp, err := s.client.KV.Get(ctx, key)
|
||||
+ getResp, err := s.client.KV.Get(ctx, preparedKey)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(out), startTime)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -138,11 +146,11 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
|
||||
if opts.IgnoreNotFound {
|
||||
return runtime.SetZeroValue(out)
|
||||
}
|
||||
- return storage.NewKeyNotFoundError(key, 0)
|
||||
+ return storage.NewKeyNotFoundError(preparedKey, 0)
|
||||
}
|
||||
kv := getResp.Kvs[0]
|
||||
|
||||
- data, _, err := s.transformer.TransformFromStorage(ctx, kv.Value, authenticatedDataString(key))
|
||||
+ data, _, err := s.transformer.TransformFromStorage(ctx, kv.Value, authenticatedDataString(preparedKey))
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
}
|
||||
@@ -152,6 +160,10 @@ func (s *store) Get(ctx context.Context, key string, opts storage.GetOptions, ou
|
||||
|
||||
// Create implements storage.Interface.Create.
|
||||
func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object, ttl uint64) error {
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
trace := utiltrace.New("Create etcd3",
|
||||
utiltrace.Field{"audit-id", endpointsrequest.GetAuditIDTruncated(ctx)},
|
||||
utiltrace.Field{"key", key},
|
||||
@@ -170,14 +182,13 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
|
||||
opts, err := s.ttlOpts(ctx, int64(ttl))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
- newData, err := s.transformer.TransformToStorage(ctx, data, authenticatedDataString(key))
|
||||
+ newData, err := s.transformer.TransformToStorage(ctx, data, authenticatedDataString(preparedKey))
|
||||
trace.Step("TransformToStorage finished", utiltrace.Field{"err", err})
|
||||
if err != nil {
|
||||
return storage.NewInternalError(err.Error())
|
||||
@@ -185,9 +196,9 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
|
||||
startTime := time.Now()
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
- notFound(key),
|
||||
+ notFound(preparedKey),
|
||||
).Then(
|
||||
- clientv3.OpPut(key, string(newData), opts...),
|
||||
+ clientv3.OpPut(preparedKey, string(newData), opts...),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("create", getTypeName(obj), startTime)
|
||||
trace.Step("Txn call finished", utiltrace.Field{"err", err})
|
||||
@@ -196,7 +207,7 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
}
|
||||
|
||||
if !txnResp.Succeeded {
|
||||
- return storage.NewKeyExistsError(key, 0)
|
||||
+ return storage.NewKeyExistsError(preparedKey, 0)
|
||||
}
|
||||
|
||||
if out != nil {
|
||||
@@ -212,12 +223,15 @@ func (s *store) Create(ctx context.Context, key string, obj, out runtime.Object,
|
||||
func (s *store) Delete(
|
||||
ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions,
|
||||
validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error {
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
v, err := conversion.EnforcePtr(out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert output object to pointer: %v", err)
|
||||
}
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
- return s.conditionalDelete(ctx, key, out, v, preconditions, validateDeletion, cachedExistingObject)
|
||||
+ return s.conditionalDelete(ctx, preparedKey, out, v, preconditions, validateDeletion, cachedExistingObject)
|
||||
}
|
||||
|
||||
func (s *store) conditionalDelete(
|
||||
@@ -330,6 +344,10 @@ func (s *store) conditionalDelete(
|
||||
func (s *store) GuaranteedUpdate(
|
||||
ctx context.Context, key string, destination runtime.Object, ignoreNotFound bool,
|
||||
preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, cachedExistingObject runtime.Object) error {
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
trace := utiltrace.New("GuaranteedUpdate etcd3",
|
||||
utiltrace.Field{"audit-id", endpointsrequest.GetAuditIDTruncated(ctx)},
|
||||
utiltrace.Field{"key", key},
|
||||
@@ -340,16 +358,15 @@ func (s *store) GuaranteedUpdate(
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to convert output object to pointer: %v", err)
|
||||
}
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
|
||||
getCurrentState := func() (*objState, error) {
|
||||
startTime := time.Now()
|
||||
- getResp, err := s.client.KV.Get(ctx, key)
|
||||
+ getResp, err := s.client.KV.Get(ctx, preparedKey)
|
||||
metrics.RecordEtcdRequestLatency("get", getTypeName(destination), startTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
- return s.getState(ctx, getResp, key, v, ignoreNotFound)
|
||||
+ return s.getState(ctx, getResp, preparedKey, v, ignoreNotFound)
|
||||
}
|
||||
|
||||
var origState *objState
|
||||
@@ -365,9 +382,9 @@ func (s *store) GuaranteedUpdate(
|
||||
}
|
||||
trace.Step("initial value restored")
|
||||
|
||||
- transformContext := authenticatedDataString(key)
|
||||
+ transformContext := authenticatedDataString(preparedKey)
|
||||
for {
|
||||
- if err := preconditions.Check(key, origState.obj); err != nil {
|
||||
+ if err := preconditions.Check(preparedKey, origState.obj); err != nil {
|
||||
// If our data is already up to date, return the error
|
||||
if origStateIsCurrent {
|
||||
return err
|
||||
@@ -453,11 +470,11 @@ func (s *store) GuaranteedUpdate(
|
||||
|
||||
startTime := time.Now()
|
||||
txnResp, err := s.client.KV.Txn(ctx).If(
|
||||
- clientv3.Compare(clientv3.ModRevision(key), "=", origState.rev),
|
||||
+ clientv3.Compare(clientv3.ModRevision(preparedKey), "=", origState.rev),
|
||||
).Then(
|
||||
- clientv3.OpPut(key, string(newData), opts...),
|
||||
+ clientv3.OpPut(preparedKey, string(newData), opts...),
|
||||
).Else(
|
||||
- clientv3.OpGet(key),
|
||||
+ clientv3.OpGet(preparedKey),
|
||||
).Commit()
|
||||
metrics.RecordEtcdRequestLatency("update", getTypeName(destination), startTime)
|
||||
trace.Step("Txn call finished", utiltrace.Field{"err", err})
|
||||
@@ -467,8 +484,8 @@ func (s *store) GuaranteedUpdate(
|
||||
trace.Step("Transaction committed")
|
||||
if !txnResp.Succeeded {
|
||||
getResp := (*clientv3.GetResponse)(txnResp.Responses[0].GetResponseRange())
|
||||
- klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", key)
|
||||
- origState, err = s.getState(ctx, getResp, key, v, ignoreNotFound)
|
||||
+ klog.V(4).Infof("GuaranteedUpdate of %s failed because of a conflict, going to retry", preparedKey)
|
||||
+ origState, err = s.getState(ctx, getResp, preparedKey, v, ignoreNotFound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -502,18 +519,21 @@ func getNewItemFunc(listObj runtime.Object, v reflect.Value) func() runtime.Obje
|
||||
}
|
||||
|
||||
func (s *store) Count(key string) (int64, error) {
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return 0, err
|
||||
+ }
|
||||
|
||||
// We need to make sure the key ended with "/" so that we only get children "directories".
|
||||
// e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three,
|
||||
// while with prefix "/a/" will return only "/a/b" which is the correct answer.
|
||||
- if !strings.HasSuffix(key, "/") {
|
||||
- key += "/"
|
||||
+ if !strings.HasSuffix(preparedKey, "/") {
|
||||
+ preparedKey += "/"
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
- getResp, err := s.client.KV.Get(context.Background(), key, clientv3.WithRange(clientv3.GetPrefixRangeEnd(key)), clientv3.WithCountOnly())
|
||||
- metrics.RecordEtcdRequestLatency("listWithCount", key, startTime)
|
||||
+ getResp, err := s.client.KV.Get(context.Background(), preparedKey, clientv3.WithRange(clientv3.GetPrefixRangeEnd(preparedKey)), clientv3.WithCountOnly())
|
||||
+ metrics.RecordEtcdRequestLatency("listWithCount", preparedKey, startTime)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -522,6 +542,10 @@ func (s *store) Count(key string) (int64, error) {
|
||||
|
||||
// GetList implements storage.Interface.
|
||||
func (s *store) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error {
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return err
|
||||
+ }
|
||||
recursive := opts.Recursive
|
||||
resourceVersion := opts.ResourceVersion
|
||||
match := opts.ResourceVersionMatch
|
||||
@@ -542,16 +566,15 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
|
||||
if err != nil || v.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("need ptr to slice: %v", err)
|
||||
}
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
|
||||
// For recursive lists, we need to make sure the key ended with "/" so that we only
|
||||
// get children "directories". e.g. if we have key "/a", "/a/b", "/ab", getting keys
|
||||
// with prefix "/a" will return all three, while with prefix "/a/" will return only
|
||||
// "/a/b" which is the correct answer.
|
||||
- if recursive && !strings.HasSuffix(key, "/") {
|
||||
- key += "/"
|
||||
+ if recursive && !strings.HasSuffix(preparedKey, "/") {
|
||||
+ preparedKey += "/"
|
||||
}
|
||||
- keyPrefix := key
|
||||
+ keyPrefix := preparedKey
|
||||
|
||||
// set the appropriate clientv3 options to filter the returned data set
|
||||
var limitOption *clientv3.OpOption
|
||||
@@ -590,7 +613,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
|
||||
|
||||
rangeEnd := clientv3.GetPrefixRangeEnd(keyPrefix)
|
||||
options = append(options, clientv3.WithRange(rangeEnd))
|
||||
- key = continueKey
|
||||
+ preparedKey = continueKey
|
||||
|
||||
// If continueRV > 0, the LIST request needs a specific resource version.
|
||||
// continueRV==0 is invalid.
|
||||
@@ -657,7 +680,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
|
||||
}()
|
||||
for {
|
||||
startTime := time.Now()
|
||||
- getResp, err = s.client.KV.Get(ctx, key, options...)
|
||||
+ getResp, err = s.client.KV.Get(ctx, preparedKey, options...)
|
||||
if recursive {
|
||||
metrics.RecordEtcdRequestLatency("list", getTypeName(listPtr), startTime)
|
||||
} else {
|
||||
@@ -729,7 +752,7 @@ func (s *store) GetList(ctx context.Context, key string, opts storage.ListOption
|
||||
}
|
||||
*limitOption = clientv3.WithLimit(limit)
|
||||
}
|
||||
- key = string(lastKey) + "\x00"
|
||||
+ preparedKey = string(lastKey) + "\x00"
|
||||
if withRev == 0 {
|
||||
withRev = returnedRV
|
||||
options = append(options, clientv3.WithRev(withRev))
|
||||
@@ -794,12 +817,15 @@ func growSlice(v reflect.Value, maxCapacity int, sizes ...int) {
|
||||
|
||||
// Watch implements storage.Interface.Watch.
|
||||
func (s *store) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) {
|
||||
+ preparedKey, err := s.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ return nil, err
|
||||
+ }
|
||||
rev, err := s.versioner.ParseResourceVersion(opts.ResourceVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
- key = path.Join(s.pathPrefix, key)
|
||||
- return s.watcher.Watch(ctx, key, int64(rev), opts.Recursive, opts.ProgressNotify, opts.Predicate)
|
||||
+ return s.watcher.Watch(ctx, preparedKey, int64(rev), opts.Recursive, opts.ProgressNotify, opts.Predicate)
|
||||
}
|
||||
|
||||
func (s *store) getState(ctx context.Context, getResp *clientv3.GetResponse, key string, v reflect.Value, ignoreNotFound bool) (*objState, error) {
|
||||
@@ -911,6 +937,30 @@ func (s *store) validateMinimumResourceVersion(minimumResourceVersion string, ac
|
||||
return nil
|
||||
}
|
||||
|
||||
+func (s *store) prepareKey(key string) (string, error) {
|
||||
+ if key == ".." ||
|
||||
+ strings.HasPrefix(key, "../") ||
|
||||
+ strings.HasSuffix(key, "/..") ||
|
||||
+ strings.Contains(key, "/../") {
|
||||
+ return "", fmt.Errorf("invalid key: %q", key)
|
||||
+ }
|
||||
+ if key == "." ||
|
||||
+ strings.HasPrefix(key, "./") ||
|
||||
+ strings.HasSuffix(key, "/.") ||
|
||||
+ strings.Contains(key, "/./") {
|
||||
+ return "", fmt.Errorf("invalid key: %q", key)
|
||||
+ }
|
||||
+ if key == "" || key == "/" {
|
||||
+ return "", fmt.Errorf("empty key: %q", key)
|
||||
+ }
|
||||
+ // We ensured that pathPrefix ends in '/' in construction, so skip any leading '/' in the key now.
|
||||
+ startIndex := 0
|
||||
+ if key[0] == '/' {
|
||||
+ startIndex = 1
|
||||
+ }
|
||||
+ return s.pathPrefix + key[startIndex:], nil
|
||||
+}
|
||||
+
|
||||
// decode decodes value of bytes into object. It will also set the object resource version to rev.
|
||||
// On success, objPtr would be set to the object.
|
||||
func decode(codec runtime.Codec, versioner storage.Versioner, value []byte, objPtr runtime.Object, rev int64) error {
|
||||
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go
|
||||
index 84ee65a1fac..2f0dfe6e588 100644
|
||||
--- a/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go
|
||||
+++ b/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store_test.go
|
||||
@@ -54,6 +54,7 @@ var scheme = runtime.NewScheme()
|
||||
var codecs = serializer.NewCodecFactory(scheme)
|
||||
|
||||
const defaultTestPrefix = "test!"
|
||||
+const basePath = "/keybase"
|
||||
|
||||
func init() {
|
||||
metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion)
|
||||
@@ -446,13 +447,13 @@ func TestTransformationFailure(t *testing.T) {
|
||||
obj *example.Pod
|
||||
storedObj *example.Pod
|
||||
}{{
|
||||
- key: "/one-level/test",
|
||||
+ key: basePath + "/one-level/test",
|
||||
obj: &example.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "bar"},
|
||||
Spec: storagetesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
}, {
|
||||
- key: "/two-level/1/test",
|
||||
+ key: basePath + "/two-level/1/test",
|
||||
obj: &example.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "baz"},
|
||||
Spec: storagetesting.DeepEqualSafePodSpec(),
|
||||
@@ -484,7 +485,7 @@ func TestTransformationFailure(t *testing.T) {
|
||||
Predicate: storage.Everything,
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", storageOpts, &got); !storage.IsInternalError(err) {
|
||||
+ if err := store.GetList(ctx, basePath, storageOpts, &got); !storage.IsInternalError(err) {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
|
||||
@@ -531,7 +532,7 @@ func TestListContinuation(t *testing.T) {
|
||||
etcdClient.KV = recorder
|
||||
|
||||
// Setup storage with the following structure:
|
||||
- // /
|
||||
+ // /keybase/
|
||||
// - one-level/
|
||||
// | - test
|
||||
// |
|
||||
@@ -548,15 +549,15 @@ func TestListContinuation(t *testing.T) {
|
||||
storedObj *example.Pod
|
||||
}{
|
||||
{
|
||||
- key: "/one-level/test",
|
||||
+ key: basePath + "/one-level/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/1/test",
|
||||
+ key: basePath + "/two-level/1/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/2/test",
|
||||
+ key: basePath + "/two-level/2/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
|
||||
},
|
||||
}
|
||||
@@ -588,7 +589,7 @@ func TestListContinuation(t *testing.T) {
|
||||
Predicate: pred(1, ""),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get initial list: %v", err)
|
||||
}
|
||||
if len(out.Continue) == 0 {
|
||||
@@ -613,13 +614,13 @@ func TestListContinuation(t *testing.T) {
|
||||
Predicate: pred(0, continueFromSecondItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) != 0 {
|
||||
t.Fatalf("Unexpected continuation token set")
|
||||
}
|
||||
- key, rv, err := storage.DecodeContinue(continueFromSecondItem, "/")
|
||||
+ key, rv, err := storage.DecodeContinue(continueFromSecondItem, basePath)
|
||||
t.Logf("continue token was %d %s %v", rv, key, err)
|
||||
storagetesting.ExpectNoDiff(t, "incorrect second page", []example.Pod{*preset[1].storedObj, *preset[2].storedObj}, out.Items)
|
||||
if transformer.reads != 2 {
|
||||
@@ -638,7 +639,7 @@ func TestListContinuation(t *testing.T) {
|
||||
Predicate: pred(1, continueFromSecondItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) == 0 {
|
||||
@@ -662,7 +663,7 @@ func TestListContinuation(t *testing.T) {
|
||||
Predicate: pred(1, continueFromThirdItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) != 0 {
|
||||
@@ -688,7 +689,7 @@ func TestListPaginationRareObject(t *testing.T) {
|
||||
podCount := 1000
|
||||
var pods []*example.Pod
|
||||
for i := 0; i < podCount; i++ {
|
||||
- key := fmt.Sprintf("/one-level/pod-%d", i)
|
||||
+ key := basePath + fmt.Sprintf("/one-level/pod-%d", i)
|
||||
obj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("pod-%d", i)}}
|
||||
storedObj := &example.Pod{}
|
||||
err := store.Create(ctx, key, obj, storedObj, 0)
|
||||
@@ -711,7 +712,7 @@ func TestListPaginationRareObject(t *testing.T) {
|
||||
},
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get initial list: %v", err)
|
||||
}
|
||||
if len(out.Continue) != 0 {
|
||||
@@ -782,7 +783,7 @@ func TestListContinuationWithFilter(t *testing.T) {
|
||||
|
||||
for i, ps := range preset {
|
||||
preset[i].storedObj = &example.Pod{}
|
||||
- err := store.Create(ctx, ps.key, ps.obj, preset[i].storedObj, 0)
|
||||
+ err := store.Create(ctx, basePath+ps.key, ps.obj, preset[i].storedObj, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Set failed: %v", err)
|
||||
}
|
||||
@@ -810,7 +811,7 @@ func TestListContinuationWithFilter(t *testing.T) {
|
||||
Predicate: pred(2, ""),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Errorf("Unable to get initial list: %v", err)
|
||||
}
|
||||
if len(out.Continue) == 0 {
|
||||
@@ -842,7 +843,7 @@ func TestListContinuationWithFilter(t *testing.T) {
|
||||
Predicate: pred(2, cont),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Errorf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) != 0 {
|
||||
@@ -863,7 +864,7 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
ctx, store, client := testSetup(t)
|
||||
|
||||
// Setup storage with the following structure:
|
||||
- // /
|
||||
+ // /keybase/
|
||||
// - one-level/
|
||||
// | - test
|
||||
// |
|
||||
@@ -880,15 +881,15 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
storedObj *example.Pod
|
||||
}{
|
||||
{
|
||||
- key: "/one-level/test",
|
||||
+ key: basePath + "/one-level/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/1/test",
|
||||
+ key: basePath + "/two-level/1/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/2/test",
|
||||
+ key: basePath + "/two-level/2/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
|
||||
},
|
||||
}
|
||||
@@ -920,7 +921,7 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
Predicate: pred(1, ""),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get initial list: %v", err)
|
||||
}
|
||||
if len(out.Continue) == 0 {
|
||||
@@ -964,7 +965,7 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
Predicate: pred(0, continueFromSecondItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- err = store.GetList(ctx, "/", options, out)
|
||||
+ err = store.GetList(ctx, basePath, options, out)
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected no error")
|
||||
}
|
||||
@@ -986,7 +987,7 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
Predicate: pred(1, inconsistentContinueFromSecondItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) == 0 {
|
||||
@@ -1005,7 +1006,7 @@ func TestListInconsistentContinuation(t *testing.T) {
|
||||
Predicate: pred(1, continueFromThirdItem),
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, out); err != nil {
|
||||
t.Fatalf("Unable to get second page: %v", err)
|
||||
}
|
||||
if len(out.Continue) != 0 {
|
||||
@@ -1127,9 +1128,9 @@ func testSetup(t *testing.T, opts ...setupOption) (context.Context, *store, *cli
|
||||
|
||||
func TestPrefix(t *testing.T) {
|
||||
testcases := map[string]string{
|
||||
- "custom/prefix": "/custom/prefix",
|
||||
- "/custom//prefix//": "/custom/prefix",
|
||||
- "/registry": "/registry",
|
||||
+ "custom/prefix": "/custom/prefix/",
|
||||
+ "/custom//prefix//": "/custom/prefix/",
|
||||
+ "/registry": "/registry/",
|
||||
}
|
||||
for configuredPrefix, effectivePrefix := range testcases {
|
||||
_, store, _ := testSetup(t, withPrefix(configuredPrefix))
|
||||
@@ -1302,7 +1303,7 @@ func TestConsistentList(t *testing.T) {
|
||||
Predicate: predicate,
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/", options, &result1); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, &result1); err != nil {
|
||||
t.Fatalf("failed to list objects: %v", err)
|
||||
}
|
||||
|
||||
@@ -1315,7 +1316,7 @@ func TestConsistentList(t *testing.T) {
|
||||
}
|
||||
|
||||
result2 := example.PodList{}
|
||||
- if err := store.GetList(ctx, "/", options, &result2); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, &result2); err != nil {
|
||||
t.Fatalf("failed to list objects: %v", err)
|
||||
}
|
||||
|
||||
@@ -1325,7 +1326,7 @@ func TestConsistentList(t *testing.T) {
|
||||
options.ResourceVersionMatch = metav1.ResourceVersionMatchNotOlderThan
|
||||
|
||||
result3 := example.PodList{}
|
||||
- if err := store.GetList(ctx, "/", options, &result3); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, &result3); err != nil {
|
||||
t.Fatalf("failed to list objects: %v", err)
|
||||
}
|
||||
|
||||
@@ -1333,7 +1334,7 @@ func TestConsistentList(t *testing.T) {
|
||||
options.ResourceVersionMatch = metav1.ResourceVersionMatchExact
|
||||
|
||||
result4 := example.PodList{}
|
||||
- if err := store.GetList(ctx, "/", options, &result4); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, options, &result4); err != nil {
|
||||
t.Fatalf("failed to list objects: %v", err)
|
||||
}
|
||||
|
||||
@@ -1384,3 +1385,78 @@ func TestLeaseMaxObjectCount(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+
|
||||
+func TestValidateKey(t *testing.T) {
|
||||
+ validKeys := []string{
|
||||
+ "/foo/bar/baz/a.b.c/",
|
||||
+ "/foo",
|
||||
+ "foo/bar/baz",
|
||||
+ "/foo/bar..baz/",
|
||||
+ "/foo/bar..",
|
||||
+ "foo",
|
||||
+ "foo/bar",
|
||||
+ "/foo/bar/",
|
||||
+ }
|
||||
+ invalidKeys := []string{
|
||||
+ "/foo/bar/../a.b.c/",
|
||||
+ "..",
|
||||
+ "/..",
|
||||
+ "../",
|
||||
+ "/foo/bar/..",
|
||||
+ "../foo/bar",
|
||||
+ "/../foo",
|
||||
+ "/foo/bar/../",
|
||||
+ ".",
|
||||
+ "/.",
|
||||
+ "./",
|
||||
+ "/./",
|
||||
+ "/foo/.",
|
||||
+ "./bar",
|
||||
+ "/foo/./bar/",
|
||||
+ }
|
||||
+ const (
|
||||
+ pathPrefix = "/first/second"
|
||||
+ expectPrefix = pathPrefix + "/"
|
||||
+ )
|
||||
+ _, store, _ := testSetup(t, withPrefix(pathPrefix))
|
||||
+
|
||||
+ for _, key := range validKeys {
|
||||
+ k, err := store.prepareKey(key)
|
||||
+ if err != nil {
|
||||
+ t.Errorf("key %q should be valid; unexpected error: %v", key, err)
|
||||
+ } else if !strings.HasPrefix(k, expectPrefix) {
|
||||
+ t.Errorf("key %q should have prefix %q", k, expectPrefix)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for _, key := range invalidKeys {
|
||||
+ _, err := store.prepareKey(key)
|
||||
+ if err == nil {
|
||||
+ t.Errorf("key %q should be invalid", key)
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+func TestInvalidKeys(t *testing.T) {
|
||||
+ const invalidKey = "/foo/bar/../baz"
|
||||
+ expectedError := fmt.Sprintf("invalid key: %q", invalidKey)
|
||||
+
|
||||
+ expectInvalidKey := func(methodName string, err error) {
|
||||
+ if err == nil {
|
||||
+ t.Errorf("[%s] expected invalid key error; got nil", methodName)
|
||||
+ } else if err.Error() != expectedError {
|
||||
+ t.Errorf("[%s] expected invalid key error; got %v", methodName, err)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ ctx, store, _ := testSetup(t)
|
||||
+ expectInvalidKey("Create", store.Create(ctx, invalidKey, nil, nil, 0))
|
||||
+ expectInvalidKey("Delete", store.Delete(ctx, invalidKey, nil, nil, nil, nil))
|
||||
+ _, watchErr := store.Watch(ctx, invalidKey, storage.ListOptions{})
|
||||
+ expectInvalidKey("Watch", watchErr)
|
||||
+ expectInvalidKey("Get", store.Get(ctx, invalidKey, storage.GetOptions{}, nil))
|
||||
+ expectInvalidKey("GetList", store.GetList(ctx, invalidKey, storage.ListOptions{}, nil))
|
||||
+ expectInvalidKey("GuaranteedUpdate", store.GuaranteedUpdate(ctx, invalidKey, nil, true, nil, nil, nil))
|
||||
+ _, countErr := store.Count(invalidKey)
|
||||
+ expectInvalidKey("Count", countErr)
|
||||
+}
|
||||
diff --git a/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go b/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go
|
||||
index c83ee833ee6..df7078f6bd9 100644
|
||||
--- a/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go
|
||||
+++ b/staging/src/k8s.io/apiserver/pkg/storage/testing/store_tests.go
|
||||
@@ -42,6 +42,8 @@ import (
|
||||
|
||||
type KeyValidation func(ctx context.Context, t *testing.T, key string)
|
||||
|
||||
+const basePath = "/keybase"
|
||||
+
|
||||
func RunTestCreate(ctx context.Context, t *testing.T, store storage.Interface, validation KeyValidation) {
|
||||
key := "/testkey"
|
||||
out := &example.Pod{}
|
||||
@@ -445,11 +447,11 @@ func RunTestList(ctx context.Context, t *testing.T, store storage.Interface) {
|
||||
Predicate: storage.Everything,
|
||||
Recursive: true,
|
||||
}
|
||||
- if err := store.GetList(ctx, "/two-level", storageOpts, list); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath+"/two-level", storageOpts, list); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
continueRV, _ := strconv.Atoi(list.ResourceVersion)
|
||||
- secondContinuation, err := storage.EncodeContinue("/two-level/2", "/two-level/", int64(continueRV))
|
||||
+ secondContinuation, err := storage.EncodeContinue(basePath+"/two-level/2", basePath+"/two-level/", int64(continueRV))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -827,7 +829,7 @@ func RunTestList(ctx context.Context, t *testing.T, store storage.Interface) {
|
||||
Predicate: tt.pred,
|
||||
Recursive: true,
|
||||
}
|
||||
- err = store.GetList(ctx, tt.prefix, storageOpts, out)
|
||||
+ err = store.GetList(ctx, basePath+tt.prefix, storageOpts, out)
|
||||
if tt.expectRVTooLarge {
|
||||
if err == nil || !storage.IsTooLargeResourceVersion(err) {
|
||||
t.Fatalf("expecting resource version too high error, but get: %s", err)
|
||||
@@ -926,7 +928,7 @@ func RunTestListWithoutPaging(ctx context.Context, t *testing.T, store storage.I
|
||||
Recursive: true,
|
||||
}
|
||||
|
||||
- if err := store.GetList(ctx, tt.prefix, storageOpts, out); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath+tt.prefix, storageOpts, out); err != nil {
|
||||
t.Fatalf("GetList failed: %v", err)
|
||||
return
|
||||
}
|
||||
@@ -952,7 +954,7 @@ func RunTestListWithoutPaging(ctx context.Context, t *testing.T, store storage.I
|
||||
// from before any were created along with the full set of objects that were persisted
|
||||
func seedMultiLevelData(ctx context.Context, store storage.Interface) (string, []*example.Pod, error) {
|
||||
// Setup storage with the following structure:
|
||||
- // /
|
||||
+ // /keybase/
|
||||
// - one-level/
|
||||
// | - test
|
||||
// |
|
||||
@@ -975,30 +977,30 @@ func seedMultiLevelData(ctx context.Context, store storage.Interface) (string, [
|
||||
storedObj *example.Pod
|
||||
}{
|
||||
{
|
||||
- key: "/one-level/test",
|
||||
+ key: basePath + "/one-level/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/1/test",
|
||||
+ key: basePath + "/two-level/1/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
|
||||
},
|
||||
{
|
||||
- key: "/two-level/2/test",
|
||||
+ key: basePath + "/two-level/2/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
|
||||
},
|
||||
{
|
||||
- key: "/z-level/3/test",
|
||||
+ key: basePath + "/z-level/3/test",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "fourth"}},
|
||||
},
|
||||
{
|
||||
- key: "/z-level/3/test-2",
|
||||
+ key: basePath + "/z-level/3/test-2",
|
||||
obj: &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "bar"}},
|
||||
},
|
||||
}
|
||||
|
||||
// we want to figure out the resourceVersion before we create anything
|
||||
initialList := &example.PodList{}
|
||||
- if err := store.GetList(ctx, "/", storage.ListOptions{Predicate: storage.Everything, Recursive: true}, initialList); err != nil {
|
||||
+ if err := store.GetList(ctx, basePath, storage.ListOptions{Predicate: storage.Everything, Recursive: true}, initialList); err != nil {
|
||||
return "", nil, fmt.Errorf("failed to determine starting resourceVersion: %w", err)
|
||||
}
|
||||
initialRV := initialList.ResourceVersion
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -1,245 +0,0 @@
|
||||
From f85027fcc12d02f79a0649115bdbaf7c9d8aab17 Mon Sep 17 00:00:00 2001
|
||||
From: Andrew Sy Kim <andrewsy@google.com>
|
||||
Date: Mon, 7 Nov 2022 10:22:44 -0500
|
||||
Subject: [PATCH] fix node address validation
|
||||
|
||||
Signed-off-by: Andrew Sy Kim <andrewsy@google.com>
|
||||
---
|
||||
.../core/node/storage/storage_test.go | 171 +++++++++++++++++-
|
||||
pkg/registry/core/node/strategy.go | 8 +-
|
||||
2 files changed, 174 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/pkg/registry/core/node/storage/storage_test.go b/pkg/registry/core/node/storage/storage_test.go
|
||||
index af228b8a43a..89df2edf16b 100644
|
||||
--- a/pkg/registry/core/node/storage/storage_test.go
|
||||
+++ b/pkg/registry/core/node/storage/storage_test.go
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package storage
|
||||
|
||||
import (
|
||||
+ "fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@@ -24,11 +25,14 @@ import (
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
+ genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
|
||||
+ "k8s.io/apiserver/pkg/registry/rest"
|
||||
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeletclient "k8s.io/kubernetes/pkg/kubelet/client"
|
||||
+ proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||
"k8s.io/kubernetes/pkg/registry/registrytest"
|
||||
)
|
||||
|
||||
@@ -40,7 +44,10 @@ func newStorage(t *testing.T) (*REST, *etcd3testing.EtcdTestServer) {
|
||||
DeleteCollectionWorkers: 1,
|
||||
ResourcePrefix: "nodes",
|
||||
}
|
||||
- storage, err := NewStorage(restOptions, kubeletclient.KubeletClientConfig{}, nil)
|
||||
+ storage, err := NewStorage(restOptions, kubeletclient.KubeletClientConfig{
|
||||
+ Port: 10250,
|
||||
+ PreferredAddressTypes: []string{string(api.NodeInternalIP)},
|
||||
+ }, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -156,3 +163,165 @@ func TestShortNames(t *testing.T) {
|
||||
expected := []string{"no"}
|
||||
registrytest.AssertShortNames(t, storage, expected)
|
||||
}
|
||||
+
|
||||
+func TestResourceLocation(t *testing.T) {
|
||||
+ testCases := []struct {
|
||||
+ name string
|
||||
+ node api.Node
|
||||
+ query string
|
||||
+ location string
|
||||
+ err error
|
||||
+ }{
|
||||
+ {
|
||||
+ name: "proxyable hostname with default port",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "10.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0",
|
||||
+ location: "10.0.0.1:10250",
|
||||
+ err: nil,
|
||||
+ },
|
||||
+ {
|
||||
+ name: "proxyable hostname with kubelet port in query",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "10.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0:5000",
|
||||
+ location: "10.0.0.1:5000",
|
||||
+ err: nil,
|
||||
+ },
|
||||
+ {
|
||||
+ name: "proxyable hostname with kubelet port in status",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "10.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ DaemonEndpoints: api.NodeDaemonEndpoints{
|
||||
+ KubeletEndpoint: api.DaemonEndpoint{
|
||||
+ Port: 5000,
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0",
|
||||
+ location: "10.0.0.1:5000",
|
||||
+ err: nil,
|
||||
+ },
|
||||
+ {
|
||||
+ name: "non-proxyable hostname with default port",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "127.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0",
|
||||
+ location: "",
|
||||
+ err: proxyutil.ErrAddressNotAllowed,
|
||||
+ },
|
||||
+ {
|
||||
+ name: "non-proxyable hostname with kubelet port in query",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "127.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0:5000",
|
||||
+ location: "",
|
||||
+ err: proxyutil.ErrAddressNotAllowed,
|
||||
+ },
|
||||
+ {
|
||||
+ name: "non-proxyable hostname with kubelet port in status",
|
||||
+ node: api.Node{
|
||||
+ ObjectMeta: metav1.ObjectMeta{Name: "node0"},
|
||||
+ Status: api.NodeStatus{
|
||||
+ Addresses: []api.NodeAddress{
|
||||
+ {
|
||||
+ Type: api.NodeInternalIP,
|
||||
+ Address: "127.0.0.1",
|
||||
+ },
|
||||
+ },
|
||||
+ DaemonEndpoints: api.NodeDaemonEndpoints{
|
||||
+ KubeletEndpoint: api.DaemonEndpoint{
|
||||
+ Port: 443,
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ query: "node0",
|
||||
+ location: "",
|
||||
+ err: proxyutil.ErrAddressNotAllowed,
|
||||
+ },
|
||||
+ }
|
||||
+
|
||||
+ for _, testCase := range testCases {
|
||||
+ t.Run(testCase.name, func(t *testing.T) {
|
||||
+ storage, server := newStorage(t)
|
||||
+ defer server.Terminate(t)
|
||||
+ defer storage.Store.DestroyFunc()
|
||||
+
|
||||
+ ctx := genericapirequest.WithNamespace(genericapirequest.NewDefaultContext(), fmt.Sprintf("namespace-%s", testCase.name))
|
||||
+ key, _ := storage.KeyFunc(ctx, testCase.node.Name)
|
||||
+ if err := storage.Storage.Create(ctx, key, &testCase.node, nil, 0, false); err != nil {
|
||||
+ t.Fatalf("unexpected error: %v", err)
|
||||
+ }
|
||||
+
|
||||
+ redirector := rest.Redirector(storage)
|
||||
+ location, _, err := redirector.ResourceLocation(ctx, testCase.query)
|
||||
+
|
||||
+ if err != nil && testCase.err != nil {
|
||||
+ if err.Error() != testCase.err.Error() {
|
||||
+ t.Fatalf("Unexpected error: %v, expected: %v", err, testCase.err)
|
||||
+ }
|
||||
+
|
||||
+ return
|
||||
+ }
|
||||
+
|
||||
+ if err != nil && testCase.err == nil {
|
||||
+ t.Fatalf("Unexpected error: %v, expected: %v", err, testCase.err)
|
||||
+ } else if err == nil && testCase.err != nil {
|
||||
+ t.Fatalf("Expected error but got none, err: %v", testCase.err)
|
||||
+ }
|
||||
+
|
||||
+ if location == nil {
|
||||
+ t.Errorf("Unexpected nil resource location: %v", location)
|
||||
+ }
|
||||
+
|
||||
+ if location.Host != testCase.location {
|
||||
+ t.Errorf("Unexpected host: expected %v, but got %v", testCase.location, location.Host)
|
||||
+ }
|
||||
+ })
|
||||
+ }
|
||||
+}
|
||||
diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go
|
||||
index bf6b6a57708..f879f327279 100644
|
||||
--- a/pkg/registry/core/node/strategy.go
|
||||
+++ b/pkg/registry/core/node/strategy.go
|
||||
@@ -249,6 +249,10 @@ func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGet
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
+ if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
|
||||
+ return nil, nil, errors.NewBadRequest(err.Error())
|
||||
+ }
|
||||
+
|
||||
// We check if we want to get a default Kubelet's transport. It happens if either:
|
||||
// - no port is specified in request (Kubelet's port is default)
|
||||
// - the requested port matches the kubelet port for this node
|
||||
@@ -261,10 +265,6 @@ func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGet
|
||||
nil
|
||||
}
|
||||
|
||||
- if err := proxyutil.IsProxyableHostname(ctx, &net.Resolver{}, info.Hostname); err != nil {
|
||||
- return nil, nil, errors.NewBadRequest(err.Error())
|
||||
- }
|
||||
-
|
||||
// Otherwise, return the requested scheme and port, and the proxy transport
|
||||
return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil
|
||||
}
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -1,554 +0,0 @@
|
||||
From 8781d503a08ff8a1a29179c80252470ac77b3bd5 Mon Sep 17 00:00:00 2001
|
||||
From: Rita Zhang <rita.z.zhang@gmail.com>
|
||||
Date: Tue, 30 May 2023 20:35:33 +0000
|
||||
Subject: [PATCH] Add ephemeralcontainer to imagepolicy securityaccount
|
||||
admission plugin
|
||||
|
||||
Signed-off-by: Rita Zhang <rita.z.zhang@gmail.com>
|
||||
---
|
||||
plugin/pkg/admission/imagepolicy/admission.go | 28 ++--
|
||||
.../admission/imagepolicy/admission_test.go | 148 +++++++++++++++++-
|
||||
.../pkg/admission/serviceaccount/admission.go | 57 ++++++-
|
||||
.../serviceaccount/admission_test.go | 88 +++++++++++
|
||||
4 files changed, 297 insertions(+), 24 deletions(-)
|
||||
|
||||
diff --git a/plugin/pkg/admission/imagepolicy/admission.go b/plugin/pkg/admission/imagepolicy/admission.go
|
||||
index 6fd7f0dfad7..f1f88fef3b0 100644
|
||||
--- a/plugin/pkg/admission/imagepolicy/admission.go
|
||||
+++ b/plugin/pkg/admission/imagepolicy/admission.go
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
diff --git a/plugin/pkg/admission/imagepolicy/admission_test.go b/plugin/pkg/admission/imagepolicy/admission_test.go
|
||||
index d1f81d51950..a9188462fb9 100644
|
||||
--- a/plugin/pkg/admission/imagepolicy/admission_test.go
|
||||
+++ b/plugin/pkg/admission/imagepolicy/admission_test.go
|
||||
@@ -37,7 +37,6 @@ import (
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"fmt"
|
||||
- "io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
@@ -67,7 +66,7 @@ imagePolicy:
|
||||
`
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
- dir, err := ioutil.TempDir("", "")
|
||||
+ dir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -92,7 +91,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)
|
||||
}
|
||||
}
|
||||
@@ -196,7 +195,7 @@ 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
|
||||
}
|
||||
@@ -211,7 +210,7 @@ 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
|
||||
}
|
||||
@@ -359,7 +358,7 @@ 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(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
|
||||
- tempfile, err := ioutil.TempFile("", "")
|
||||
+ tempfile, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -381,7 +380,7 @@ func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
- tempconfigfile, err := ioutil.TempFile("", "")
|
||||
+ tempconfigfile, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -595,17 +594,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",
|
||||
@@ -627,6 +632,8 @@ func TestContainerCombinations(t *testing.T) {
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
+ subresource: "",
|
||||
+ operation: admission.Create,
|
||||
},
|
||||
{
|
||||
test: "Multiple good containers",
|
||||
@@ -648,6 +655,8 @@ func TestContainerCombinations(t *testing.T) {
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
+ subresource: "",
|
||||
+ operation: admission.Create,
|
||||
},
|
||||
{
|
||||
test: "Multiple bad containers",
|
||||
@@ -669,6 +678,8 @@ func TestContainerCombinations(t *testing.T) {
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
+ subresource: "",
|
||||
+ operation: admission.Create,
|
||||
},
|
||||
{
|
||||
test: "Good container, bad init container",
|
||||
@@ -692,6 +703,8 @@ func TestContainerCombinations(t *testing.T) {
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
+ subresource: "",
|
||||
+ operation: admission.Create,
|
||||
},
|
||||
{
|
||||
test: "Bad container, good init container",
|
||||
@@ -715,6 +728,8 @@ func TestContainerCombinations(t *testing.T) {
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
+ subresource: "",
|
||||
+ operation: admission.Create,
|
||||
},
|
||||
{
|
||||
test: "Good container, good init container",
|
||||
@@ -738,6 +753,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 {
|
||||
@@ -759,7 +891,7 @@ func TestContainerCombinations(t *testing.T) {
|
||||
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 {
|
||||
diff --git a/plugin/pkg/admission/serviceaccount/admission.go b/plugin/pkg/admission/serviceaccount/admission.go
|
||||
index 769a115aef5..c844a051c24 100644
|
||||
--- a/plugin/pkg/admission/serviceaccount/admission.go
|
||||
+++ b/plugin/pkg/admission/serviceaccount/admission.go
|
||||
@@ -99,7 +99,7 @@ var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
|
||||
// 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers
|
||||
func NewServiceAccount() *Plugin {
|
||||
return &Plugin{
|
||||
- Handler: admission.NewHandler(admission.Create),
|
||||
+ Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
// 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
|
||||
@@ -139,7 +139,10 @@ func (s *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.
|
||||
if shouldIgnore(a) {
|
||||
return nil
|
||||
}
|
||||
-
|
||||
+ if a.GetOperation() != admission.Create {
|
||||
+ // we only mutate pods during create requests
|
||||
+ return nil
|
||||
+ }
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
|
||||
// Don't modify the spec of mirror pods.
|
||||
@@ -156,7 +159,7 @@ func (s *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.
|
||||
|
||||
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))
|
||||
+ return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %w", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
|
||||
}
|
||||
if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
|
||||
s.mountServiceAccountToken(serviceAccount, pod)
|
||||
@@ -179,6 +182,15 @@ func (s *Plugin) Validate(ctx context.Context, a admission.Attributes, o admissi
|
||||
|
||||
pod := a.GetObject().(*api.Pod)
|
||||
|
||||
+ if a.GetOperation() == admission.Update && a.GetSubresource() == "ephemeralcontainers" {
|
||||
+ return s.limitEphemeralContainerSecretReferences(pod, a)
|
||||
+ }
|
||||
+
|
||||
+ if a.GetOperation() != admission.Create {
|
||||
+ // we only validate pod specs during create requests
|
||||
+ return nil
|
||||
+ }
|
||||
+
|
||||
// Mirror pods have restrictions on what they can reference
|
||||
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
|
||||
if len(pod.Spec.ServiceAccountName) != 0 {
|
||||
@@ -204,6 +216,10 @@ func (s *Plugin) Validate(ctx context.Context, a admission.Attributes, o admissi
|
||||
return nil
|
||||
}
|
||||
|
||||
+ // Require container pods to have service accounts
|
||||
+ if len(pod.Spec.ServiceAccountName) == 0 {
|
||||
+ return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name))
|
||||
+ }
|
||||
// Ensure the referenced service account exists
|
||||
serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
|
||||
if err != nil {
|
||||
@@ -220,10 +236,7 @@ func (s *Plugin) Validate(ctx context.Context, a admission.Attributes, o admissi
|
||||
}
|
||||
|
||||
func shouldIgnore(a admission.Attributes) bool {
|
||||
- if a.GetResource().GroupResource() != api.Resource("pods") {
|
||||
- return true
|
||||
- }
|
||||
- if a.GetSubresource() != "" {
|
||||
+ if a.GetResource().GroupResource() != api.Resource("pods") || (a.GetSubresource() != "" && a.GetSubresource() != "ephemeralcontainers") {
|
||||
return true
|
||||
}
|
||||
obj := a.GetObject()
|
||||
@@ -349,6 +362,36 @@ func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, po
|
||||
return nil
|
||||
}
|
||||
|
||||
+func (s *Plugin) limitEphemeralContainerSecretReferences(pod *api.Pod, a admission.Attributes) error {
|
||||
+ // Require ephemeral container pods to have service accounts
|
||||
+ if len(pod.Spec.ServiceAccountName) == 0 {
|
||||
+ return admission.NewForbidden(a, fmt.Errorf("no service account specified for pod %s/%s", a.GetNamespace(), pod.Name))
|
||||
+ }
|
||||
+ // 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: %w", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
|
||||
+ }
|
||||
+ if !s.enforceMountableSecrets(serviceAccount) {
|
||||
+ return nil
|
||||
+ }
|
||||
+ // Ensure all secrets the ephemeral containers reference are allowed by the service account
|
||||
+ mountableSecrets := sets.NewString()
|
||||
+ for _, s := range serviceAccount.Secrets {
|
||||
+ mountableSecrets.Insert(s.Name)
|
||||
+ }
|
||||
+ for _, container := range pod.Spec.EphemeralContainers {
|
||||
+ for _, env := range container.Env {
|
||||
+ if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
|
||||
+ if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
|
||||
+ return fmt.Errorf("ephemeral 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)
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ return nil
|
||||
+}
|
||||
+
|
||||
func (s *Plugin) mountServiceAccountToken(serviceAccount *corev1.ServiceAccount, pod *api.Pod) {
|
||||
// Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
|
||||
tokenVolumeName := ""
|
||||
diff --git a/plugin/pkg/admission/serviceaccount/admission_test.go b/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
index d50f321a8be..bf15f870d75 100644
|
||||
--- a/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
+++ b/plugin/pkg/admission/serviceaccount/admission_test.go
|
||||
@@ -544,6 +544,34 @@ func TestAllowsReferencedSecret(t *testing.T) {
|
||||
if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
+
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ EphemeralContainers: []api.EphemeralContainer{
|
||||
+ {
|
||||
+ EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
+ Name: "container-2",
|
||||
+ Env: []api.EnvVar{
|
||||
+ {
|
||||
+ Name: "env-1",
|
||||
+ ValueFrom: &api.EnvVarSource{
|
||||
+ SecretKeyRef: &api.SecretKeySelector{
|
||||
+ LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ // validate enforces restrictions on secret mounts when operation==create and subresource=='' or operation==update and subresource==ephemeralcontainers"
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("Unexpected error: %v", err)
|
||||
+ }
|
||||
}
|
||||
|
||||
func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
@@ -621,6 +649,66 @@ func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
|
||||
if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
+
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ InitContainers: []api.Container{
|
||||
+ {
|
||||
+ Name: "container-1",
|
||||
+ Env: []api.EnvVar{
|
||||
+ {
|
||||
+ Name: "env-1",
|
||||
+ ValueFrom: &api.EnvVarSource{
|
||||
+ SecretKeyRef: &api.SecretKeySelector{
|
||||
+ LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("admit only enforces restrictions on secret mounts when operation==create. Unexpected error: %v", err)
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") {
|
||||
+ t.Errorf("validate only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err)
|
||||
+ }
|
||||
+
|
||||
+ pod2 = &api.Pod{
|
||||
+ Spec: api.PodSpec{
|
||||
+ ServiceAccountName: DefaultServiceAccountName,
|
||||
+ EphemeralContainers: []api.EphemeralContainer{
|
||||
+ {
|
||||
+ EphemeralContainerCommon: api.EphemeralContainerCommon{
|
||||
+ Name: "container-2",
|
||||
+ Env: []api.EnvVar{
|
||||
+ {
|
||||
+ Name: "env-1",
|
||||
+ ValueFrom: &api.EnvVarSource{
|
||||
+ SecretKeyRef: &api.SecretKeySelector{
|
||||
+ LocalObjectReference: api.LocalObjectReference{Name: "foo"},
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ },
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admissiontesting.WithReinvocationTesting(t, admit).Admit(context.TODO(), attrs, nil); err != nil {
|
||||
+ t.Errorf("admit only enforces restrictions on secret mounts when operation==create and subresource==''. Unexpected error: %v", err)
|
||||
+ }
|
||||
+ attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "ephemeralcontainers", admission.Update, &metav1.UpdateOptions{}, false, nil)
|
||||
+ if err := admit.Validate(context.TODO(), attrs, nil); err == nil || !strings.Contains(err.Error(), "with envVar") {
|
||||
+ t.Errorf("validate enforces restrictions on secret mounts when operation==update and subresource==ephemeralcontainers. Unexpected error: %v", err)
|
||||
+ }
|
||||
}
|
||||
|
||||
func TestAllowUnreferencedSecretVolumesForPermissiveSAs(t *testing.T) {
|
||||
--
|
||||
2.25.1
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
Name: kubernetes
|
||||
Version: 1.29.1
|
||||
Release: 2
|
||||
Release: 4
|
||||
Summary: Container cluster management
|
||||
License: ASL 2.0
|
||||
URL: https://k8s.io/kubernetes
|
||||
@ -24,6 +24,10 @@ Source13: kubernetes-accounting.conf
|
||||
Source14: kubeadm.conf
|
||||
Source15: kubernetes.conf
|
||||
|
||||
Patch0001: 0001-fix-compile-options.patch
|
||||
Patch0002: 0002-kubelet-support-exec-and-attach-websocket-protocol.patch
|
||||
Patch0003: 0003-Add-envFrom-to-serviceaccount-admission-plugin.patch
|
||||
|
||||
%description
|
||||
Container cluster management.
|
||||
|
||||
@ -261,6 +265,18 @@ getent passwd kube >/dev/null || useradd -r -g kube -d / -s /sbin/nologin \
|
||||
%systemd_postun kubelet kube-proxy
|
||||
|
||||
%changelog
|
||||
* Fri Apr 26 2024 liuxu <liuxu156@huawei.com> - 1.29.1-4
|
||||
- Type:bugfix
|
||||
- CVE:NA
|
||||
- SUG:NA
|
||||
- DESC:fix CVE-2024-3177
|
||||
|
||||
* Fri Apr 26 2024 liuxu <liuxu156@huawei.com> - 1.29.1-3
|
||||
- Type:bugfix
|
||||
- CVE:NA
|
||||
- SUG:NA
|
||||
- DESC:adapt old patch
|
||||
|
||||
* Tue Apr 23 2024 liuxu <liuxu156@huawei.com> - 1.29.1-2
|
||||
- Type:bugfix
|
||||
- CVE:NA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user