diff --git a/.reuse/dep5 b/.reuse/dep5
index 3bb44faa..6d13eba7 100644
--- a/.reuse/dep5
+++ b/.reuse/dep5
@@ -49,14 +49,6 @@ Copyright: Copyright 2021 The Sigstore Authors.
Modifications Copyright 2022 SAP SE or an SAP affiliate company and Gardener contributors
License: Apache-2.0
-Files:
- pkg/admission/admission_suite_test.go
- pkg/admission/http.go
- pkg/admission/http_test.go
-Copyright: Copyright 2018 The Kubernetes Authors.
- Modifications Copyright 2022 SAP SE or an SAP affiliate company and Gardener contributors
-License: Apache-2.0
-
Files:
hack/cherry-pick-pull.sh
Copyright: Copyright 2015 The Kubernetes Authors.
diff --git a/charts/gardener-extension-shoot-lakom-service/templates/configmap-extension-config.yaml b/charts/gardener-extension-shoot-lakom-service/templates/configmap-extension-config.yaml
index 247bbc1e..d5bff3e3 100644
--- a/charts/gardener-extension-shoot-lakom-service/templates/configmap-extension-config.yaml
+++ b/charts/gardener-extension-shoot-lakom-service/templates/configmap-extension-config.yaml
@@ -14,10 +14,10 @@ data:
kind: Configuration
cosignPublicKeys:
{{ toYaml .Values.controllers.cosignPublicKeys | indent 6 }}
- failurePolicy: {{ .Values.controllers.failurePolicy }}
seedBootstrap:
ownerNamespace: {{ .Release.Namespace }}
useOnlyImagePullSecrets: {{ .Values.controllers.useOnlyImagePullSecrets }}
+ allowUntrustedImages: {{ .Values.controllers.allowUntrustedImages }}
debugConfig:
enableProfiling: {{ .Values.controllers.debugConfig.enableProfiling | default false }}
enableContentionProfiling: {{ .Values.controllers.debugConfig.enableContentionProfiling | default false }}
\ No newline at end of file
diff --git a/charts/gardener-extension-shoot-lakom-service/values.yaml b/charts/gardener-extension-shoot-lakom-service/values.yaml
index 03832a4d..2e05a47b 100644
--- a/charts/gardener-extension-shoot-lakom-service/values.yaml
+++ b/charts/gardener-extension-shoot-lakom-service/values.yaml
@@ -46,10 +46,10 @@ controllers:
# -----BEGIN PUBLIC KEY-----
# abcd
# -----END PUBLIC KEY-----
- failurePolicy: Fail
healthPort: 8080
metricsPort: 8081
useOnlyImagePullSecrets: false
+ allowUntrustedImages: false
debugConfig:
enableProfiling: false
enableContentionProfiling: false
diff --git a/charts/lakom/templates/deployment.yaml b/charts/lakom/templates/deployment.yaml
index d6fba566..2b95c6da 100644
--- a/charts/lakom/templates/deployment.yaml
+++ b/charts/lakom/templates/deployment.yaml
@@ -82,6 +82,7 @@ spec:
- --kubeconfig=/etc/lakom/client/kubeconfig
{{- end }}
- --use-only-image-pull-secrets={{ .Values.useOnlyImagePullSecrets }}
+ - --insecure-allow-untrusted-images={{ .Values.allowUntrustedImages }}
{{- if .Values.resources }}
resources:
{{ toYaml .Values.resources | trim | indent 10 }}
diff --git a/charts/lakom/templates/mutatingwebhookconfiguration.yaml b/charts/lakom/templates/mutatingwebhookconfiguration.yaml
index 2ae3ef57..e2f59e8e 100644
--- a/charts/lakom/templates/mutatingwebhookconfiguration.yaml
+++ b/charts/lakom/templates/mutatingwebhookconfiguration.yaml
@@ -24,7 +24,7 @@ webhooks:
namespace: {{ .Release.Namespace }}
path: /lakom/resolve-tag-to-digest
{{- end }}
- failurePolicy: {{ .Values.admissionConfig.failurePolicy }}
+ failurePolicy: Fail
matchPolicy: Equivalent
name: resolve-tag.lakom.service.gardener.cloud
{{- if .Values.admissionConfig.namespaceSelector }}
diff --git a/charts/lakom/templates/validatingwebhookconfiguration.yaml b/charts/lakom/templates/validatingwebhookconfiguration.yaml
index ede031ee..5888210a 100644
--- a/charts/lakom/templates/validatingwebhookconfiguration.yaml
+++ b/charts/lakom/templates/validatingwebhookconfiguration.yaml
@@ -24,7 +24,7 @@ webhooks:
namespace: {{ .Release.Namespace }}
path: /lakom/verify-cosign-signature
{{- end }}
- failurePolicy: {{ .Values.admissionConfig.failurePolicy }}
+ failurePolicy: Fail
matchPolicy: Equivalent
name: verify-signature.lakom.service.gardener.cloud
{{- if .Values.admissionConfig.namespaceSelector }}
diff --git a/charts/lakom/values.yaml b/charts/lakom/values.yaml
index 87b8ce18..921f80a2 100644
--- a/charts/lakom/values.yaml
+++ b/charts/lakom/values.yaml
@@ -31,6 +31,7 @@ cosign:
# abcd
# -----END PUBLIC KEY-----
useOnlyImagePullSecrets: false
+allowUntrustedImages: false
kubeconfig: {}
admissionConfig:
objectSelector: {}
@@ -41,7 +42,6 @@ admissionConfig:
values:
- "kube-system"
- "lakom-system"
- failurePolicy: Fail
clientConfig:
caBundle: foo
urlHostname: ""
diff --git a/cmd/lakom/app/app.go b/cmd/lakom/app/app.go
index 450e9782..d927eb59 100644
--- a/cmd/lakom/app/app.go
+++ b/cmd/lakom/app/app.go
@@ -13,7 +13,6 @@ import (
goruntime "runtime"
"time"
- "github.com/gardener/gardener-extension-shoot-lakom-service/pkg/admission"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/constants"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/lakom/resolvetag"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/lakom/verifysignature"
@@ -99,6 +98,9 @@ type Options struct {
// UseOnlyImagePullSecrets sets only the image pull secrets of the pod to be used to access the OCI registry.
// Otherwise, also the node identity and docker config file are used.
UseOnlyImagePullSecrets bool
+ // AllowUntrustedImages configures the webhook to allow images without trusted signature.
+ // Instead to deny the request, the webhook will allow it with a warning.
+ AllowUntrustedImages bool
}
// AddFlags adds lakom admission controller's flags to the specified FlagSet.
@@ -114,6 +116,7 @@ func (o *Options) AddFlags(fs *pflag.FlagSet) {
fs.DurationVar(&o.CacheTTL, "cache-ttl", time.Minute*10, "TTL for the cached objects. Set to 0, if cache has to be disabled")
fs.DurationVar(&o.CacheRefreshInterval, "cache-refresh-interval", time.Second*30, "Refresh interval for the cached objects")
fs.BoolVar(&o.UseOnlyImagePullSecrets, "use-only-image-pull-secrets", false, "If set, only the credentials from the image pull secrets of the pod are used to access the OCI registry. Otherwise, the node identity and docker config are also used.")
+ fs.BoolVar(&o.AllowUntrustedImages, "insecure-allow-untrusted-images", false, "If set, the webhook will just return warning for the images without trusted signatures.")
}
// validate validates all the required options.
@@ -230,6 +233,7 @@ func (o *Options) Run(ctx context.Context) error {
WithCacheTTL(o.CacheTTL).
WithCacheRefreshInterval(o.CacheRefreshInterval).
WithUseOnlyImagePullSecrets(o.UseOnlyImagePullSecrets).
+ WithAllowUntrustedImages(o.AllowUntrustedImages).
Build()
if err != nil {
return err
@@ -237,16 +241,14 @@ func (o *Options) Run(ctx context.Context) error {
server.Register(
constants.LakomResolveTagPath,
- &admission.Server{
- Webhook: webhook.Admission{Handler: imageTagResolverHandler},
- Log: imageTagResolverHandler.GetLogger(),
+ &webhook.Admission{
+ Handler: imageTagResolverHandler,
},
)
server.Register(
constants.LakomVerifyCosignSignaturePath,
- &admission.Server{
- Webhook: webhook.Admission{Handler: cosignSignatureVerifyHandler},
- Log: cosignSignatureVerifyHandler.GetLogger(),
+ &webhook.Admission{
+ Handler: cosignSignatureVerifyHandler,
},
)
diff --git a/example/00-config.yaml b/example/00-config.yaml
index e8329260..6ca143fc 100644
--- a/example/00-config.yaml
+++ b/example/00-config.yaml
@@ -10,10 +10,10 @@ cosignPublicKeys:
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyLVOS/TWANf6sZJPDzogodvDz8NT
hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
-----END PUBLIC KEY-----
-failurePolicy: Fail
seedBootstrap:
ownerNamespace: lakom-extension
useOnlyImagePullSecrets: true
+allowUntrustedImages: false
debugConfig:
enableProfiling: false
enableContentionProfiling: false
diff --git a/example/controller-registration.yaml b/example/controller-registration.yaml
index 1873bfd2..05652f44 100644
--- a/example/controller-registration.yaml
+++ b/example/controller-registration.yaml
@@ -5,7 +5,7 @@ metadata:
name: shoot-lakom-service
type: helm
providerConfig:
- chart: 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
+ chart: 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
values:
image:
tag: v0.12.0-dev
diff --git a/hack/api-reference/config.md b/hack/api-reference/config.md
index d68d048d..4360be43 100644
--- a/hack/api-reference/config.md
+++ b/hack/api-reference/config.md
@@ -67,18 +67,6 @@ github.com/gardener/gardener/extensions/pkg/apis/config/v1alpha1.HealthCheckConf
-failurePolicy
-
-string
-
- |
-
-(Optional)
- FailurePolicy is the failure policy used to configure the failurePolicy of the lakom admission webhooks.
- |
-
-
-
debugConfig
@@ -116,6 +104,18 @@ bool
Otherwise, also the node identity and docker config file are used.
|
+
+
+allowUntrustedImages
+
+bool
+
+ |
+
+ AllowUntrustedImages sets lakom webhook to allow images without trusted signature.
+Instead to deny the request, the webhook will allow it with a warning.
+ |
+
DebugConfig
diff --git a/pkg/admission/admission_suite_test.go b/pkg/admission/admission_suite_test.go
deleted file mode 100644
index 7048c026..00000000
--- a/pkg/admission/admission_suite_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-Copyright 2018 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.
-
-This file was copied and modified from the kubernetes-sigs/controller-runtime project
-https://github.com/kubernetes-sigs/controller-runtime/blob/4c9c9564e4652bbdec14a602d6196d8622500b51/pkg/webhook/admission/admission_suite_test.go
-
-Modifications Copyright 2022 SAP SE or an SAP affiliate company and Gardener contributors
-*/
-
-package admission
-
-import (
- "testing"
-
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- "sigs.k8s.io/controller-runtime/pkg/log/zap"
-)
-
-func TestAdmissionWebhook(t *testing.T) {
- RegisterFailHandler(Fail)
- RunSpecs(t, "Admission Webhook Suite")
-}
-
-var _ = BeforeSuite(func() {
- logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
-})
diff --git a/pkg/admission/http.go b/pkg/admission/http.go
deleted file mode 100644
index 382f8095..00000000
--- a/pkg/admission/http.go
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
-Copyright 2018 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.
-
-This file was copied and modified from the kubernetes-sigs/controller-runtime project
-https://github.com/kubernetes-sigs/controller-runtime/blob/4c9c9564e4652bbdec14a602d6196d8622500b51/pkg/webhook/admission/http.go
-
-Modifications Copyright 2022 SAP SE or an SAP affiliate company and Gardener contributors
-*/
-
-package admission
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
-
- "github.com/go-logr/logr"
- v1 "k8s.io/api/admission/v1"
- "k8s.io/api/admission/v1beta1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/runtime/serializer"
- utilruntime "k8s.io/apimachinery/pkg/util/runtime"
- "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-)
-
-var admissionScheme = runtime.NewScheme()
-var admissionCodecs = serializer.NewCodecFactory(admissionScheme)
-
-func init() {
- utilruntime.Must(v1.AddToScheme(admissionScheme))
- utilruntime.Must(v1beta1.AddToScheme(admissionScheme))
-}
-
-var _ http.Handler = &Server{}
-
-// Server extends sigs.k8s.io/controller-runtime/pkg/webhook/admission.Webhook with custom ServeHTTP method
-// so that the admission response code is also written in the HTTP status header.
-// This allows the admission controller to run with failurePolicy=Ignore.
-type Server struct {
- admission.Webhook
-
- Log logr.Logger
-}
-
-func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- var body []byte
- var err error
- ctx := r.Context()
- if s.Webhook.WithContextFunc != nil {
- ctx = s.Webhook.WithContextFunc(ctx, r)
- }
-
- var reviewResponse admission.Response
- if r.Body == nil {
- err = errors.New("request body is empty")
- s.Log.Error(err, "bad request")
- reviewResponse = admission.Errored(http.StatusBadRequest, err)
- s.writeResponse(w, reviewResponse)
- return
- }
-
- defer func() {
- _ = r.Body.Close()
- }()
-
- if body, err = io.ReadAll(r.Body); err != nil {
- s.Log.Error(err, "unable to read the body from the incoming request")
- reviewResponse = admission.Errored(http.StatusBadRequest, err)
- s.writeResponse(w, reviewResponse)
- return
- }
-
- // verify the content type is accurate
- if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
- err = fmt.Errorf("contentType=%s, expected application/json", contentType)
- s.Log.Error(err, "unable to process a request with an unknown content type", "content type", contentType)
- reviewResponse = admission.Errored(http.StatusBadRequest, err)
- s.writeResponse(w, reviewResponse)
- return
- }
-
- // Both v1 and v1beta1 AdmissionReview types are exactly the same, so the v1beta1 type can
- // be decoded into the v1 type. However the runtime codec's decoder guesses which type to
- // decode into by type name if an Object's TypeMeta isn't set. By setting TypeMeta of an
- // unregistered type to the v1 GVK, the decoder will coerce a v1beta1 AdmissionReview to v1.
- // The actual AdmissionReview GVK will be used to write a typed response in case the
- // webhook config permits multiple versions, otherwise this response will fail.
- req := admission.Request{}
- ar := unversionedAdmissionReview{}
- // avoid an extra copy
- ar.Request = &req.AdmissionRequest
- ar.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("AdmissionReview"))
- _, actualAdmRevGVK, err := admissionCodecs.UniversalDeserializer().Decode(body, nil, &ar)
- if err != nil {
- s.Log.Error(err, "unable to decode the request")
- reviewResponse = admission.Errored(http.StatusBadRequest, err)
- s.writeResponse(w, reviewResponse)
- return
- }
- s.Log.V(1).Info("received request", "UID", req.UID, "kind", req.Kind, "resource", req.Resource)
-
- reviewResponse = s.Webhook.Handle(ctx, req)
- s.writeResponseTyped(w, reviewResponse, actualAdmRevGVK)
-}
-
-// writeResponse writes response to w generically, i.e. without encoding GVK information.
-func (s *Server) writeResponse(w http.ResponseWriter, response admission.Response) {
- s.writeAdmissionResponse(w, v1.AdmissionReview{Response: &response.AdmissionResponse})
-}
-
-// writeResponseTyped writes response to w with GVK set to admRevGVK, which is necessary
-// if multiple AdmissionReview versions are permitted by the webhook.
-func (s *Server) writeResponseTyped(w http.ResponseWriter, response admission.Response, admRevGVK *schema.GroupVersionKind) {
- ar := v1.AdmissionReview{
- Response: &response.AdmissionResponse,
- }
- // Default to a v1 AdmissionReview, otherwise the API server may not recognize the request
- // if multiple AdmissionReview versions are permitted by the webhook config.
- // TODO(estroz): this should be configurable since older API servers won't know about v1.
- if admRevGVK == nil || *admRevGVK == (schema.GroupVersionKind{}) {
- ar.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("AdmissionReview"))
- } else {
- ar.SetGroupVersionKind(*admRevGVK)
- }
- s.writeAdmissionResponse(w, ar)
-}
-
-// writeAdmissionResponse writes ar to w.
-func (s *Server) writeAdmissionResponse(w http.ResponseWriter, ar v1.AdmissionReview) {
- w.WriteHeader(int(ar.Response.Result.Code))
- if err := json.NewEncoder(w).Encode(ar); err != nil {
- s.Log.Error(err, "unable to encode and write the response")
- // Since the `ar v1.AdmissionReview` is a clear and legal object,
- // it should not have problem to be marshalled into bytes.
- // The error here is probably caused by the abnormal HTTP connection,
- // e.g., broken pipe, so we can only write the error response once,
- // to avoid endless circular calling.
- serverError := admission.Errored(http.StatusInternalServerError, err)
- w.WriteHeader(int(serverError.Result.Code))
- if err = json.NewEncoder(w).Encode(v1.AdmissionReview{Response: &serverError.AdmissionResponse}); err != nil {
- s.Log.Error(err, "still unable to encode and write the InternalServerError response")
- }
- } else {
- res := ar.Response
- if log := s.Log; log.V(1).Enabled() {
- if res.Result != nil {
- log = log.WithValues("code", res.Result.Code, "reason", res.Result.Reason)
- }
- log.V(1).Info("wrote response", "UID", res.UID, "allowed", res.Allowed)
- }
- }
-}
-
-// unversionedAdmissionReview is used to decode both v1 and v1beta1 AdmissionReview types.
-type unversionedAdmissionReview struct {
- v1.AdmissionReview
-}
-
-var _ runtime.Object = &unversionedAdmissionReview{}
diff --git a/pkg/admission/http_test.go b/pkg/admission/http_test.go
deleted file mode 100644
index 1e9619b6..00000000
--- a/pkg/admission/http_test.go
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
-Copyright 2018 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.
-
-This file was copied and modified from the kubernetes-sigs/controller-runtime project
-https://github.com/kubernetes-sigs/controller-runtime/blob/4c9c9564e4652bbdec14a602d6196d8622500b51/pkg/webhook/admission/http_test.go
-
-Modifications Copyright 2022 SAP SE or an SAP affiliate company and Gardener contributors
-*/
-
-package admission_test
-
-import (
- "bytes"
- "context"
- "fmt"
- "io"
- "net/http"
- "net/http/httptest"
- "time"
-
- "github.com/gardener/gardener-extension-shoot-lakom-service/pkg/admission"
-
- "github.com/go-logr/logr"
- . "github.com/onsi/ginkgo/v2"
- . "github.com/onsi/gomega"
- admissionv1 "k8s.io/api/admission/v1"
- logf "sigs.k8s.io/controller-runtime/pkg/log"
- cradmission "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
-)
-
-var _ = Describe("Admission Webhooks", func() {
-
- const (
- gvkJSONv1 = `"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1"`
- gvkJSONv1beta1 = `"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1beta1"`
- )
-
- Describe("HTTP Handler", func() {
- var (
- logger logr.Logger
- respRecorder *httptest.ResponseRecorder
- server *admission.Server
- )
-
- BeforeEach(func() {
- logger = logf.Log
- respRecorder = &httptest.ResponseRecorder{
- Body: bytes.NewBuffer(nil),
- }
-
- server = &admission.Server{
- Log: logger,
- }
- })
-
- It("should return bad-request when given an empty body", func() {
- req := &http.Request{Body: nil}
-
- expected := `{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"request body is empty","code":400}}}
-`
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusBadRequest))
- })
-
- It("should return bad-request when given the wrong content-type", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/foo"}},
- Body: nopCloser{Reader: bytes.NewBuffer(nil)},
- }
-
- expected :=
- `{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"contentType=application/foo, expected application/json","code":400}}}
-`
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusBadRequest))
- })
-
- It("should return bad-request when given an undecodable body", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString("{")},
- }
-
- expected :=
- `{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"couldn't get version/kind; json parse error: unexpected end of JSON input","code":400}}}
-`
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusBadRequest))
- })
-
- It("should return the response given by the handler with version defaulted to v1", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(`{"request":{}}`)},
- }
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{},
- },
- Log: logger.WithName("server"),
- }
-
- expected := fmt.Sprintf(`{%s,"response":{"uid":"","allowed":true,"status":{"metadata":{},"code":200}}}
-`, gvkJSONv1)
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusOK))
- })
-
- It("should return the v1 response given by the handler", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(fmt.Sprintf(`{%s,"request":{}}`, gvkJSONv1))},
- }
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{},
- },
- Log: logger.WithName("server"),
- }
-
- expected := fmt.Sprintf(`{%s,"response":{"uid":"","allowed":true,"status":{"metadata":{},"code":200}}}
-`, gvkJSONv1)
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusOK))
- })
-
- It("should return the v1beta1 response given by the handler", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(fmt.Sprintf(`{%s,"request":{}}`, gvkJSONv1beta1))},
- }
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{},
- },
- Log: logger.WithName("server"),
- }
-
- expected := fmt.Sprintf(`{%s,"response":{"uid":"","allowed":true,"status":{"metadata":{},"code":200}}}
-`, gvkJSONv1beta1)
- server.ServeHTTP(respRecorder, req)
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusOK))
- })
-
- It("should present the Context from the HTTP request, if any", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(`{"request":{}}`)},
- }
- type ctxkey int
- const key ctxkey = 1
- const value = "from-ctx"
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{
- fn: func(ctx context.Context, req cradmission.Request) cradmission.Response {
- <-ctx.Done()
- return cradmission.Allowed(ctx.Value(key).(string))
- },
- },
- },
- Log: logger.WithName("server"),
- }
-
- expected := fmt.Sprintf(`{%s,"response":{"uid":"","allowed":true,"status":{"metadata":{},"message":%q,"code":200}}}
-`, gvkJSONv1, value)
-
- ctx, cancel := context.WithCancel(context.WithValue(context.Background(), key, value))
- cancel()
- server.ServeHTTP(respRecorder, req.WithContext(ctx))
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusOK))
- })
-
- It("should mutate the Context from the HTTP request, if func supplied", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(`{"request":{}}`)},
- }
- type ctxkey int
- const key ctxkey = 1
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{
- fn: func(ctx context.Context, req cradmission.Request) cradmission.Response {
- return cradmission.Allowed(ctx.Value(key).(string))
- },
- },
- WithContextFunc: func(ctx context.Context, r *http.Request) context.Context {
- return context.WithValue(ctx, key, r.Header["Content-Type"][0])
- },
- },
- Log: logger.WithName("server"),
- }
-
- expected := fmt.Sprintf(`{%s,"response":{"uid":"","allowed":true,"status":{"metadata":{},"message":%q,"code":200}}}
-`, gvkJSONv1, "application/json")
-
- ctx, cancel := context.WithCancel(context.Background())
- cancel()
- server.ServeHTTP(respRecorder, req.WithContext(ctx))
- Expect(respRecorder.Body.String()).To(Equal(expected))
- Expect(respRecorder.Code).To(Equal(http.StatusOK))
- })
-
- It("should never run into circular calling if the writer has broken", func() {
- req := &http.Request{
- Header: http.Header{"Content-Type": []string{"application/json"}},
- Body: nopCloser{Reader: bytes.NewBufferString(fmt.Sprintf(`{%s,"request":{}}`, gvkJSONv1))},
- }
- server := admission.Server{
- Webhook: cradmission.Webhook{
- Handler: &fakeHandler{},
- },
- Log: logger.WithName("server"),
- }
-
- bw := &brokenWriter{ResponseWriter: respRecorder}
- Eventually(func() int {
- // This should not be blocked by the circular calling of writeResponse and writeAdmissionResponse
- server.ServeHTTP(bw, req)
- return respRecorder.Body.Len()
- }, time.Second*3).Should(Equal(0))
- })
- })
-})
-
-type nopCloser struct {
- io.Reader
-}
-
-func (nopCloser) Close() error { return nil }
-
-type fakeHandler struct {
- invoked bool
- fn func(context.Context, cradmission.Request) cradmission.Response
- decoder *cradmission.Decoder
- injectedString string
-}
-
-func (h *fakeHandler) InjectDecoder(d *cradmission.Decoder) error {
- h.decoder = d
- return nil
-}
-
-func (h *fakeHandler) InjectString(s string) error {
- h.injectedString = s
- return nil
-}
-
-func (h *fakeHandler) Handle(ctx context.Context, req cradmission.Request) cradmission.Response {
- h.invoked = true
- if h.fn != nil {
- return h.fn(ctx, req)
- }
- return cradmission.Response{AdmissionResponse: admissionv1.AdmissionResponse{
- Allowed: true,
- }}
-}
-
-type brokenWriter struct {
- http.ResponseWriter
-}
-
-func (bw *brokenWriter) Write(_ []byte) (int, error) {
- return 0, fmt.Errorf("mock: write: broken pipe")
-}
diff --git a/pkg/apis/config/types.go b/pkg/apis/config/types.go
index 797fe706..1c16331c 100644
--- a/pkg/apis/config/types.go
+++ b/pkg/apis/config/types.go
@@ -19,8 +19,6 @@ type Configuration struct {
HealthCheckConfig *healthcheckconfig.HealthCheckConfig
// CosignPublicKeys is the cosign public keys used to verify image signatures.
CosignPublicKeys []string
- // FailurePolicy is the failure policy used to configure the failurePolicy of the lakom admission webhooks.
- FailurePolicy *string
// DebugConfig contains debug configurations for the controller.
DebugConfig *DebugConfig
// SeedBootstrap configures the seed bootstrap controller.
@@ -28,6 +26,9 @@ type Configuration struct {
// UseOnlyImagePullSecrets sets lakom to use only the image pull secrets of the pod to access the OCI registry.
// Otherwise, also the node identity and docker config file are used.
UseOnlyImagePullSecrets bool
+ // AllowUntrustedImages sets lakom webhook to allow images without trusted signature.
+ // Instead to deny the request, the webhook will allow it with a warning.
+ AllowUntrustedImages bool
}
// DebugConfig contains debug configurations for the controller.
diff --git a/pkg/apis/config/v1alpha1/types.go b/pkg/apis/config/v1alpha1/types.go
index 176b7bce..8a03e760 100644
--- a/pkg/apis/config/v1alpha1/types.go
+++ b/pkg/apis/config/v1alpha1/types.go
@@ -21,9 +21,6 @@ type Configuration struct {
HealthCheckConfig *healthcheckconfigv1alpha1.HealthCheckConfig `json:"healthCheckConfig,omitempty"`
// CosignPublicKeys is the cosign public keys used to verify image signatures.
CosignPublicKeys []string `json:"cosignPublicKeys,omitempty"`
- // FailurePolicy is the failure policy used to configure the failurePolicy of the lakom admission webhooks.
- // +optional
- FailurePolicy *string `json:"failurePolicy,omitempty"`
// DebugConfig contains debug configurations for the controller.
// +optional
DebugConfig *DebugConfig `json:"debugConfig,omitempty"`
@@ -32,6 +29,9 @@ type Configuration struct {
// UseOnlyImagePullSecrets sets lakom to use only the image pull secrets of the pod to access the OCI registry.
// Otherwise, also the node identity and docker config file are used.
UseOnlyImagePullSecrets bool `json:"useOnlyImagePullSecrets"`
+ // AllowUntrustedImages sets lakom webhook to allow images without trusted signature.
+ // Instead to deny the request, the webhook will allow it with a warning.
+ AllowUntrustedImages bool `json:"allowUntrustedImages"`
}
// DebugConfig contains debug configurations for the controller.
diff --git a/pkg/apis/config/v1alpha1/zz_generated.conversion.go b/pkg/apis/config/v1alpha1/zz_generated.conversion.go
index 4d685360..c29b0695 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.conversion.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.conversion.go
@@ -62,12 +62,12 @@ func RegisterConversions(s *runtime.Scheme) error {
func autoConvert_v1alpha1_Configuration_To_config_Configuration(in *Configuration, out *config.Configuration, s conversion.Scope) error {
out.HealthCheckConfig = (*apisconfig.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig))
out.CosignPublicKeys = *(*[]string)(unsafe.Pointer(&in.CosignPublicKeys))
- out.FailurePolicy = (*string)(unsafe.Pointer(in.FailurePolicy))
out.DebugConfig = (*config.DebugConfig)(unsafe.Pointer(in.DebugConfig))
if err := Convert_v1alpha1_SeedBootstrap_To_config_SeedBootstrap(&in.SeedBootstrap, &out.SeedBootstrap, s); err != nil {
return err
}
out.UseOnlyImagePullSecrets = in.UseOnlyImagePullSecrets
+ out.AllowUntrustedImages = in.AllowUntrustedImages
return nil
}
@@ -79,12 +79,12 @@ func Convert_v1alpha1_Configuration_To_config_Configuration(in *Configuration, o
func autoConvert_config_Configuration_To_v1alpha1_Configuration(in *config.Configuration, out *Configuration, s conversion.Scope) error {
out.HealthCheckConfig = (*configv1alpha1.HealthCheckConfig)(unsafe.Pointer(in.HealthCheckConfig))
out.CosignPublicKeys = *(*[]string)(unsafe.Pointer(&in.CosignPublicKeys))
- out.FailurePolicy = (*string)(unsafe.Pointer(in.FailurePolicy))
out.DebugConfig = (*DebugConfig)(unsafe.Pointer(in.DebugConfig))
if err := Convert_config_SeedBootstrap_To_v1alpha1_SeedBootstrap(&in.SeedBootstrap, &out.SeedBootstrap, s); err != nil {
return err
}
out.UseOnlyImagePullSecrets = in.UseOnlyImagePullSecrets
+ out.AllowUntrustedImages = in.AllowUntrustedImages
return nil
}
diff --git a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
index b03a4a7b..fdc4bbd7 100644
--- a/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/config/v1alpha1/zz_generated.deepcopy.go
@@ -28,11 +28,6 @@ func (in *Configuration) DeepCopyInto(out *Configuration) {
*out = make([]string, len(*in))
copy(*out, *in)
}
- if in.FailurePolicy != nil {
- in, out := &in.FailurePolicy, &out.FailurePolicy
- *out = new(string)
- **out = **in
- }
if in.DebugConfig != nil {
in, out := &in.DebugConfig, &out.DebugConfig
*out = new(DebugConfig)
diff --git a/pkg/apis/config/zz_generated.deepcopy.go b/pkg/apis/config/zz_generated.deepcopy.go
index 0246891f..44b74555 100644
--- a/pkg/apis/config/zz_generated.deepcopy.go
+++ b/pkg/apis/config/zz_generated.deepcopy.go
@@ -28,11 +28,6 @@ func (in *Configuration) DeepCopyInto(out *Configuration) {
*out = make([]string, len(*in))
copy(*out, *in)
}
- if in.FailurePolicy != nil {
- in, out := &in.FailurePolicy, &out.FailurePolicy
- *out = new(string)
- **out = **in
- }
if in.DebugConfig != nil {
in, out := &in.DebugConfig, &out.DebugConfig
*out = new(DebugConfig)
diff --git a/pkg/controller/lifecycle/actuator.go b/pkg/controller/lifecycle/actuator.go
index 5952dab5..7561dfcc 100644
--- a/pkg/controller/lifecycle/actuator.go
+++ b/pkg/controller/lifecycle/actuator.go
@@ -11,12 +11,12 @@ import (
"strings"
"time"
- "github.com/Masterminds/semver/v3"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/apis/config"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/constants"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/imagevector"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/secrets"
+ "github.com/Masterminds/semver/v3"
"github.com/gardener/gardener/extensions/pkg/controller"
"github.com/gardener/gardener/extensions/pkg/controller/extension"
extensionssecretsmanager "github.com/gardener/gardener/extensions/pkg/util/secret/manager"
@@ -44,7 +44,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/apimachinery/pkg/util/sets"
vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
"k8s.io/client-go/rest"
"k8s.io/component-base/version"
@@ -148,26 +147,17 @@ func (a *actuator) Reconcile(ctx context.Context, logger logr.Logger, ex *extens
a.serviceConfig.CosignPublicKeys,
image.String(),
a.serviceConfig.UseOnlyImagePullSecrets,
+ a.serviceConfig.AllowUntrustedImages,
seedK8sSemverVersion,
)
if err != nil {
return err
}
- var (
- failurePolicy = admissionregistration.Fail
- allowedFailurePolicies = sets.NewString(string(admissionregistration.Fail), string(admissionregistration.Ignore))
- )
-
- if a.serviceConfig.FailurePolicy != nil && allowedFailurePolicies.Has(*a.serviceConfig.FailurePolicy) {
- failurePolicy = admissionregistration.FailurePolicyType(*a.serviceConfig.FailurePolicy)
- }
-
shootResources, err := getShootResources(
caBundleSecret.Data[secretutils.DataKeyCertificateBundle],
namespace,
lakomShootAccessSecret.ServiceAccountName,
- failurePolicy,
)
if err != nil {
@@ -275,7 +265,7 @@ func getLabels() map[string]string {
}
}
-func getSeedResources(lakomReplicas *int32, namespace, genericKubeconfigName, shootAccessSecretName, serverTLSSecretName string, cosignPublicKeys []string, image string, useOnlyImagePullSecrets bool, k8sVersion *semver.Version) (map[string][]byte, error) {
+func getSeedResources(lakomReplicas *int32, namespace, genericKubeconfigName, shootAccessSecretName, serverTLSSecretName string, cosignPublicKeys []string, image string, useOnlyImagePullSecrets, allowUntrustedImages bool, k8sVersion *semver.Version) (map[string][]byte, error) {
var (
tcpProto = corev1.ProtocolTCP
serverPort = intstr.FromInt(10250)
@@ -365,6 +355,7 @@ func getSeedResources(lakomReplicas *int32, namespace, genericKubeconfigName, sh
"--port=" + serverPort.String(),
"--kubeconfig=" + gutil.PathGenericKubeconfig,
"--use-only-image-pull-secrets=" + strconv.FormatBool(useOnlyImagePullSecrets),
+ "--insecure-allow-untrusted-images=" + strconv.FormatBool(allowUntrustedImages),
},
Ports: []corev1.ContainerPort{
{
@@ -576,10 +567,11 @@ func getSeedResources(lakomReplicas *int32, namespace, genericKubeconfigName, sh
return resources, nil
}
-func getShootResources(webhookCaBundle []byte, namespace, shootAccessServiceAccountName string, failurePolicy admissionregistration.FailurePolicyType) (map[string][]byte, error) {
+func getShootResources(webhookCaBundle []byte, namespace, shootAccessServiceAccountName string) (map[string][]byte, error) {
var (
matchPolicy = admissionregistration.Equivalent
sideEffectClass = admissionregistration.SideEffectClassNone
+ failurePolicy = admissionregistration.Fail
timeOutSeconds = ptr.To[int32](25)
webhookHost = fmt.Sprintf("https://%s.%s", constants.ExtensionServiceName, namespace)
validatingWebhookURL = webhookHost + constants.LakomVerifyCosignSignaturePath
diff --git a/pkg/controller/lifecycle/actuator_test.go b/pkg/controller/lifecycle/actuator_test.go
index cc0510ae..98048dd6 100644
--- a/pkg/controller/lifecycle/actuator_test.go
+++ b/pkg/controller/lifecycle/actuator_test.go
@@ -7,13 +7,13 @@ package lifecycle
import (
b64 "encoding/base64"
"fmt"
+ "strconv"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/gardener/gardener/pkg/resourcemanager/controller/garbagecollector/references"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
- admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
)
var _ = Describe("Actuator", func() {
@@ -49,7 +49,6 @@ var _ = Describe("Actuator", func() {
const (
namespace = "shoot--for--bar"
shootAccessServiceAccountName = "extension-shoot-lakom-service-access"
- failurePolicy = admissionregistrationv1.Ignore
validatingWebhookKey = "validatingwebhookconfiguration____gardener-extension-shoot-lakom-service-shoot.yaml"
mutatingWebhookKey = "mutatingwebhookconfiguration____gardener-extension-shoot-lakom-service-shoot.yaml"
roleKey = "role__kube-system__gardener-extension-shoot-lakom-service-resource-reader.yaml"
@@ -61,47 +60,47 @@ var _ = Describe("Actuator", func() {
It("Should ensure the correct shoot resources are created", func() {
- resources, err := getShootResources(caBundle, namespace, shootAccessServiceAccountName, failurePolicy)
+ resources, err := getShootResources(caBundle, namespace, shootAccessServiceAccountName)
Expect(err).ToNot(HaveOccurred())
Expect(resources).To(HaveLen(4))
Expect(resources).To(Equal(map[string][]byte{
- validatingWebhookKey: []byte(expectedSeedValidatingWebhook(caBundle, namespace, failurePolicy)),
- mutatingWebhookKey: []byte(expectedShootMutatingWebhook(caBundle, namespace, failurePolicy)),
+ validatingWebhookKey: []byte(expectedSeedValidatingWebhook(caBundle, namespace)),
+ mutatingWebhookKey: []byte(expectedShootMutatingWebhook(caBundle, namespace)),
roleKey: []byte(expectedShootRole()),
roleBindingKey: []byte(expectedShootRoleBinding(shootAccessServiceAccountName)),
}))
})
DescribeTable("Should ensure the mutating webhook config is correctly set",
- func(ca []byte, ns string, fp admissionregistrationv1.FailurePolicyType) {
- resources, err := getShootResources(ca, ns, shootAccessServiceAccountName, fp)
+ func(ca []byte, ns string) {
+ resources, err := getShootResources(ca, ns, shootAccessServiceAccountName)
Expect(err).ToNot(HaveOccurred())
mutatingWebhook, ok := resources[mutatingWebhookKey]
Expect(ok).To(BeTrue())
- Expect(string(mutatingWebhook)).To(Equal(expectedShootMutatingWebhook(ca, ns, fp)))
+ Expect(string(mutatingWebhook)).To(Equal(expectedShootMutatingWebhook(ca, ns)))
},
- Entry("Failure policy Fail", caBundle, namespace, admissionregistrationv1.Fail),
- Entry("Failure policy Ignore", []byte("anotherCABundle"), "different-namespace", admissionregistrationv1.Ignore),
+ Entry("Global CA bundle and namespace name", caBundle, namespace),
+ Entry("Custom CA bundle and namespace name", []byte("anotherCABundle"), "different-namespace"),
)
DescribeTable("Should ensure the validating webhook config is correctly set",
- func(ca []byte, ns string, fp admissionregistrationv1.FailurePolicyType) {
- resources, err := getShootResources(ca, ns, shootAccessServiceAccountName, fp)
+ func(ca []byte, ns string) {
+ resources, err := getShootResources(ca, ns, shootAccessServiceAccountName)
Expect(err).ToNot(HaveOccurred())
validatingWebhook, ok := resources[validatingWebhookKey]
Expect(ok).To(BeTrue())
- Expect(string(validatingWebhook)).To(Equal(expectedSeedValidatingWebhook(ca, ns, fp)))
+ Expect(string(validatingWebhook)).To(Equal(expectedSeedValidatingWebhook(ca, ns)))
},
- Entry("Failure policy Fail", caBundle, namespace, admissionregistrationv1.Fail),
- Entry("Failure policy Ignore", []byte("anotherCABundle"), "different-namespace", admissionregistrationv1.Ignore),
+ Entry("Global CA bundle and namespace name", caBundle, namespace),
+ Entry("Custom CA bundle and namespace name", []byte("anotherCABundle"), "different-namespace"),
)
DescribeTable("Should ensure the rolebinding is correctly set",
func(saName string) {
- resources, err := getShootResources(caBundle, namespace, saName, failurePolicy)
+ resources, err := getShootResources(caBundle, namespace, saName)
Expect(err).ToNot(HaveOccurred())
roleBinding, ok := resources[roleBindingKey]
@@ -121,7 +120,6 @@ var _ = Describe("Actuator", func() {
shootAccessServiceAccountName = "extension-shoot-lakom-service"
serverTLSSecretName = "shoot-lakom-service-tls" //#nosec G101 -- this is false positive
image = "europe-docker.pkg.dev/gardener-project/releases/gardener/extensions/lakom:v0.0.0"
- useOnlyImagePullSecrets = true
cosignSecretName = "extension-shoot-lakom-service-cosign-public-keys-e3b0c442"
cosignSecretNameKey = "secret__" + namespace + "__" + cosignSecretName + ".yaml"
@@ -155,7 +153,7 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
})
DescribeTable("Should ensure resources are correctly created for different Kubernetes versions",
- func(k8sVersion *semver.Version, withUnhealthyPodEvictionPolicy bool) {
+ func(k8sVersion *semver.Version, withUnhealthyPodEvictionPolicy, useOnlyImagePullSecrets, allowUntrustedImages bool) {
resources, err := getSeedResources(
&replicas,
namespace,
@@ -165,6 +163,7 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
cosignPublicKeys,
image,
useOnlyImagePullSecrets,
+ allowUntrustedImages,
k8sVersion,
)
Expect(err).ToNot(HaveOccurred())
@@ -172,7 +171,7 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
expectedResources := map[string]string{
configMapKey: expectedSeedConfigMap(namespace),
- deploymentKey: expectedSeedDeployment(replicas, namespace, genericKubeconfigName, shootAccessServiceAccountName, image, cosignSecretName, serverTLSSecretName),
+ deploymentKey: expectedSeedDeployment(replicas, namespace, genericKubeconfigName, shootAccessServiceAccountName, image, cosignSecretName, serverTLSSecretName, strconv.FormatBool(useOnlyImagePullSecrets), strconv.FormatBool(allowUntrustedImages)),
pdbKey: expectedSeedPDB(namespace, withUnhealthyPodEvictionPolicy),
cosignSecretNameKey: expectedSeedSecretCosign(namespace, cosignSecretName, cosignPublicKeys),
serviceKey: expectedSeedService(namespace),
@@ -188,17 +187,16 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
Expect(strResource).To(Equal(expectedResource), key)
}
},
- Entry("Kubernetes version < 1.26", semver.MustParse("1.25.0"), false),
- Entry("Kubernetes version >= 1.26", semver.MustParse("1.26.0"), true),
+ Entry("Kubernetes version < 1.26", semver.MustParse("1.25.0"), false, false, false),
+ Entry("Kubernetes version >= 1.26", semver.MustParse("1.26.0"), true, false, false),
+ Entry("Use only image pull secrets", semver.MustParse("1.27.0"), true, true, false),
+ Entry("Allow untrusted images", semver.MustParse("1.28.0"), true, false, true),
)
})
})
-func expectedShootMutatingWebhook(caBundle []byte, namespace string, failurePolicy admissionregistrationv1.FailurePolicyType) string {
- var (
- caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
- strFailurePolicy = string(failurePolicy)
- )
+func expectedShootMutatingWebhook(caBundle []byte, namespace string) string {
+ caBundleEncoded := b64.StdEncoding.EncodeToString(caBundle)
return `apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
@@ -215,7 +213,7 @@ webhooks:
clientConfig:
caBundle: ` + caBundleEncoded + `
url: https://extension-shoot-lakom-service.` + namespace + `/lakom/resolve-tag-to-digest
- failurePolicy: ` + strFailurePolicy + `
+ failurePolicy: Fail
matchPolicy: Equivalent
name: resolve-tag.lakom.service.extensions.gardener.cloud
namespaceSelector:
@@ -246,11 +244,9 @@ webhooks:
`
}
-func expectedSeedValidatingWebhook(caBundle []byte, namespace string, failurePolicy admissionregistrationv1.FailurePolicyType) string {
- var (
- caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
- strFailurePolicy = string(failurePolicy)
- )
+func expectedSeedValidatingWebhook(caBundle []byte, namespace string) string {
+ caBundleEncoded := b64.StdEncoding.EncodeToString(caBundle)
+
return `apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
@@ -266,7 +262,7 @@ webhooks:
clientConfig:
caBundle: ` + caBundleEncoded + `
url: https://extension-shoot-lakom-service.` + namespace + `/lakom/verify-cosign-signature
- failurePolicy: ` + strFailurePolicy + `
+ failurePolicy: Fail
matchPolicy: Equivalent
name: verify-signature.lakom.service.extensions.gardener.cloud
namespaceSelector:
@@ -377,7 +373,7 @@ metadata:
`
}
-func expectedSeedDeployment(replicas int32, namespace, genericKubeconfigSecretName, shootAccessSecretName, image, cosignPublicKeysSecretName, serverTLSSecretName string) string {
+func expectedSeedDeployment(replicas int32, namespace, genericKubeconfigSecretName, shootAccessSecretName, image, cosignPublicKeysSecretName, serverTLSSecretName, useOnlyImagePullSecrets, allowUntrustedImages string) string {
var (
genericKubeconfigSecretNameAnnotationKey = references.AnnotationKey("secret", genericKubeconfigSecretName)
shootAccessSecretNameAnnotationKey = references.AnnotationKey("secret", shootAccessSecretName)
@@ -451,7 +447,8 @@ spec:
- --metrics-bind-address=:8080
- --port=10250
- --kubeconfig=/var/run/secrets/gardener.cloud/shoot/generic-kubeconfig/kubeconfig
- - --use-only-image-pull-secrets=true
+ - --use-only-image-pull-secrets=` + useOnlyImagePullSecrets + `
+ - --insecure-allow-untrusted-images=` + allowUntrustedImages + `
image: ` + image + `
imagePullPolicy: IfNotPresent
livenessProbe:
@@ -518,7 +515,12 @@ status: {}
}
func expectedSeedPDB(namespace string, withUnhealthyPodEvictionPolicy bool) string {
- out := `apiVersion: policy/v1
+ unhealthyPodEvictionPolicyStr := ""
+ if withUnhealthyPodEvictionPolicy {
+ unhealthyPodEvictionPolicyStr = ` unhealthyPodEvictionPolicy: AlwaysAllow
+`
+ }
+ return `apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
creationTimestamp: null
@@ -533,18 +535,12 @@ spec:
matchLabels:
app.kubernetes.io/name: lakom
app.kubernetes.io/part-of: shoot-lakom-service
-`
- if withUnhealthyPodEvictionPolicy {
- out += ` unhealthyPodEvictionPolicy: AlwaysAllow
-`
- }
- out += `status:
+` + unhealthyPodEvictionPolicyStr + `status:
currentHealthy: 0
desiredHealthy: 0
disruptionsAllowed: 0
expectedPods: 0
`
- return out
}
func expectedSeedSecretCosign(namespace, cosignSecretName string, cosignPublicKeys []string) string {
diff --git a/pkg/controller/seed/add.go b/pkg/controller/seed/add.go
index f3c85017..c7c7209f 100644
--- a/pkg/controller/seed/add.go
+++ b/pkg/controller/seed/add.go
@@ -8,9 +8,9 @@ import (
"context"
"fmt"
- "github.com/Masterminds/semver/v3"
controllerconfig "github.com/gardener/gardener-extension-shoot-lakom-service/pkg/controller/config"
+ "github.com/Masterminds/semver/v3"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
diff --git a/pkg/controller/seed/reconciler.go b/pkg/controller/seed/reconciler.go
index 997ef56f..74207f7b 100644
--- a/pkg/controller/seed/reconciler.go
+++ b/pkg/controller/seed/reconciler.go
@@ -11,11 +11,11 @@ import (
"strings"
"time"
- "github.com/Masterminds/semver/v3"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/apis/config"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/constants"
"github.com/gardener/gardener-extension-shoot-lakom-service/pkg/imagevector"
+ "github.com/Masterminds/semver/v3"
extensionssecretsmanager "github.com/gardener/gardener/extensions/pkg/util/secret/manager"
v1beta1constants "github.com/gardener/gardener/pkg/apis/core/v1beta1/constants"
resourcesv1alpha1 "github.com/gardener/gardener/pkg/apis/resources/v1alpha1"
@@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
- "k8s.io/apimachinery/pkg/util/sets"
vpaautoscalingv1 "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/apis/autoscaling.k8s.io/v1"
"k8s.io/component-base/version"
"k8s.io/utils/clock"
@@ -104,21 +103,13 @@ func (kcr *kubeSystemReconciler) reconcile(ctx context.Context, logger logr.Logg
image.Tag = ptr.To[string](version.Get().GitVersion)
}
- var (
- failurePolicy = admissionregistration.Fail
- allowedFailurePolicies = sets.NewString(string(admissionregistration.Fail), string(admissionregistration.Ignore))
- )
- if kcr.serviceConfig.FailurePolicy != nil && allowedFailurePolicies.Has(*kcr.serviceConfig.FailurePolicy) {
- failurePolicy = admissionregistration.FailurePolicyType(*kcr.serviceConfig.FailurePolicy)
- }
-
resources, err := getResources(
generatedSecrets[constants.SeedWebhookTLSSecretName].Name,
image.String(),
kcr.serviceConfig.CosignPublicKeys,
caBundleSecret.Data[secretutils.DataKeyCertificateBundle],
- failurePolicy,
kcr.serviceConfig.UseOnlyImagePullSecrets,
+ kcr.serviceConfig.AllowUntrustedImages,
kcr.seedK8sVersion,
)
if err != nil {
@@ -183,7 +174,7 @@ func (kcr *kubeSystemReconciler) setOwnerReferenceToSecrets(ctx context.Context,
return nil
}
-func getResources(serverTLSSecretName, image string, cosignPublicKeys []string, webhookCaBundle []byte, failurePolicy admissionregistration.FailurePolicyType, useOnlyImagePullSecrets bool, k8sVersion *semver.Version) (map[string][]byte, error) {
+func getResources(serverTLSSecretName, image string, cosignPublicKeys []string, webhookCaBundle []byte, useOnlyImagePullSecrets, allowUntrustedImages bool, k8sVersion *semver.Version) (map[string][]byte, error) {
var (
tcpProto = corev1.ProtocolTCP
serverPort = intstr.FromInt(10250)
@@ -201,6 +192,7 @@ func getResources(serverTLSSecretName, image string, cosignPublicKeys []string,
kubeSystemNamespace = metav1.NamespaceSystem
matchPolicy = admissionregistration.Equivalent
sideEffectClass = admissionregistration.SideEffectClassNone
+ failurePolicy = admissionregistration.Fail
timeOutSeconds = ptr.To[int32](25)
namespaceSelector = metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
@@ -293,6 +285,7 @@ func getResources(serverTLSSecretName, image string, cosignPublicKeys []string,
"--metrics-bind-address=:" + metricsPort.String(),
"--port=" + serverPort.String(),
"--use-only-image-pull-secrets=" + strconv.FormatBool(useOnlyImagePullSecrets),
+ "--insecure-allow-untrusted-images=" + strconv.FormatBool(allowUntrustedImages),
},
Ports: []corev1.ContainerPort{
{
diff --git a/pkg/controller/seed/reconciler_test.go b/pkg/controller/seed/reconciler_test.go
index 4c8b65dc..2dcc84a0 100644
--- a/pkg/controller/seed/reconciler_test.go
+++ b/pkg/controller/seed/reconciler_test.go
@@ -6,13 +6,13 @@ package seed
import (
b64 "encoding/base64"
+ "strconv"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/gardener/gardener/pkg/resourcemanager/controller/garbagecollector/references"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
- admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
)
var _ = Describe("Reconciler", func() {
@@ -34,11 +34,11 @@ var _ = Describe("Reconciler", func() {
const (
namespace = "kube-system"
ownerNamespace = "garden"
- failurePolicy = admissionregistrationv1.Ignore
cosignSecretName = "extension-shoot-lakom-service-seed-cosign-public-keys-e3b0c442"
serverTLSSecretName = "shoot-lakom-service-seed-tls" //#nosec G101 -- this is false positive
image = "europe-docker.pkg.dev/gardener-project/releases/gardener/extensions/lakom:v0.0.0"
useOnlyImagePullSecrets = true
+ allowUntrustedImages = false
validatingWebhookKey = "validatingwebhookconfiguration____gardener-extension-shoot-lakom-service-seed.yaml"
mutatingWebhookKey = "mutatingwebhookconfiguration____gardener-extension-shoot-lakom-service-seed.yaml"
@@ -75,14 +75,14 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
})
DescribeTable("Should ensure resources are correctly created for different Kubernetes versions",
- func(k8sVersion string, withUnhealthyPodEvictionPolicy bool) {
+ func(k8sVersion string, withUnhealthyPodEvictionPolicy, onlyImagePullSecrets, untrustedImages bool) {
resources, err := getResources(
serverTLSSecretName,
image,
cosignPublicKeys,
caBundle,
- failurePolicy,
- useOnlyImagePullSecrets,
+ onlyImagePullSecrets,
+ untrustedImages,
semver.MustParse(k8sVersion),
)
@@ -90,11 +90,11 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
Expect(resources).To(HaveLen(10))
expectedResources := map[string]string{
- validatingWebhookKey: expectedValidatingWebhook(caBundle, failurePolicy),
- mutatingWebhookKey: expectedMutatingWebhook(caBundle, failurePolicy),
+ validatingWebhookKey: expectedValidatingWebhook(caBundle),
+ mutatingWebhookKey: expectedMutatingWebhook(caBundle),
clusterRoleKey: expectedClusterRole(),
clusterRoleBindingKey: expectedClusterRoleBinding(),
- deploymentKey: expectedDeployment(namespace, image, cosignSecretName, serverTLSSecretName),
+ deploymentKey: expectedDeployment(namespace, image, cosignSecretName, serverTLSSecretName, strconv.FormatBool(onlyImagePullSecrets), strconv.FormatBool(untrustedImages)),
pdbKey: expectedPDB(namespace, withUnhealthyPodEvictionPolicy),
cosignSecretNameKey: expectedSecretCosign(namespace, cosignSecretName, cosignPublicKeys),
serviceKey: expectedService(namespace),
@@ -110,50 +110,52 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
Expect(strResource).To(Equal(expectedResource), key, string(resource))
}
},
- Entry("Kubernetes version < 1.26", "1.25.0", false),
- Entry("Kubernetes version >= 1.26", "1.26.0", true),
+ Entry("Kubernetes version < 1.26", "1.25.0", false, false, false),
+ Entry("Kubernetes version >= 1.26", "1.26.0", true, false, false),
+ Entry("Use only image pull secrets", "1.27.0", true, true, false),
+ Entry("Allow untrusted images", "1.28.0", true, false, true),
)
DescribeTable("Should ensure the mutating webhook config is correctly set",
- func(ca []byte, fp admissionregistrationv1.FailurePolicyType) {
+ func(ca []byte) {
resources, err := getResources(
serverTLSSecretName,
image,
cosignPublicKeys,
ca,
- fp,
useOnlyImagePullSecrets,
+ allowUntrustedImages,
k8sVersion,
)
Expect(err).ToNot(HaveOccurred())
mutatingWebhook, ok := resources[mutatingWebhookKey]
Expect(ok).To(BeTrue())
- Expect(string(mutatingWebhook)).To(Equal(expectedMutatingWebhook(ca, fp)))
+ Expect(string(mutatingWebhook)).To(Equal(expectedMutatingWebhook(ca)))
},
- Entry("Failure policy Fail", caBundle, admissionregistrationv1.Fail),
- Entry("Failure policy Ignore", []byte("anotherCABundle"), admissionregistrationv1.Ignore),
+ Entry("Global CA bundle", caBundle),
+ Entry("Custom CA bundle", []byte("anotherCABundle")),
)
DescribeTable("Should ensure the validating webhook config is correctly set",
- func(ca []byte, fp admissionregistrationv1.FailurePolicyType) {
+ func(ca []byte) {
resources, err := getResources(
serverTLSSecretName,
image,
cosignPublicKeys,
ca,
- fp,
useOnlyImagePullSecrets,
+ allowUntrustedImages,
k8sVersion,
)
Expect(err).ToNot(HaveOccurred())
validatingWebhook, ok := resources[validatingWebhookKey]
Expect(ok).To(BeTrue())
- Expect(string(validatingWebhook)).To(Equal(expectedValidatingWebhook(ca, fp)))
+ Expect(string(validatingWebhook)).To(Equal(expectedValidatingWebhook(ca)))
},
- Entry("Failure policy Fail", caBundle, admissionregistrationv1.Fail),
- Entry("Failure policy Ignore", []byte("anotherCABundle"), admissionregistrationv1.Ignore),
+ Entry("Global CA bundle", caBundle),
+ Entry("Custom ca bundle", []byte("anotherCABundle")),
)
It("Should ensure the clusterrolebinding is correctly set", func() {
@@ -162,8 +164,8 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
image,
cosignPublicKeys,
caBundle,
- failurePolicy,
useOnlyImagePullSecrets,
+ allowUntrustedImages,
k8sVersion,
)
@@ -176,10 +178,9 @@ hjZVcW2ygAvImCAULGph2fqGkNUszl7ycJH/Dntw4wMLSbstUZomqPuIVQ==
})
})
-func expectedMutatingWebhook(caBundle []byte, failurePolicy admissionregistrationv1.FailurePolicyType) string {
+func expectedMutatingWebhook(caBundle []byte) string {
var (
- caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
- strFailurePolicy = string(failurePolicy)
+ caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
)
return `apiVersion: admissionregistration.k8s.io/v1
@@ -200,7 +201,7 @@ webhooks:
name: extension-shoot-lakom-service-seed
namespace: kube-system
path: /lakom/resolve-tag-to-digest
- failurePolicy: ` + strFailurePolicy + `
+ failurePolicy: Fail
matchPolicy: Equivalent
name: resolve-tag.seed.lakom.service.extensions.gardener.cloud
namespaceSelector:
@@ -225,10 +226,9 @@ webhooks:
`
}
-func expectedValidatingWebhook(caBundle []byte, failurePolicy admissionregistrationv1.FailurePolicyType) string {
+func expectedValidatingWebhook(caBundle []byte) string {
var (
- caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
- strFailurePolicy = string(failurePolicy)
+ caBundleEncoded = b64.StdEncoding.EncodeToString(caBundle)
)
return `apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
@@ -248,7 +248,7 @@ webhooks:
name: extension-shoot-lakom-service-seed
namespace: kube-system
path: /lakom/verify-cosign-signature
- failurePolicy: ` + strFailurePolicy + `
+ failurePolicy: Fail
matchPolicy: Equivalent
name: verify-signature.seed.lakom.service.extensions.gardener.cloud
namespaceSelector:
@@ -312,7 +312,7 @@ subjects:
`
}
-func expectedDeployment(namespace, image, cosignPublicKeysSecretName, serverTLSSecretName string) string {
+func expectedDeployment(namespace, image, cosignPublicKeysSecretName, serverTLSSecretName, useOnlyImagePullSecrets, allowUntrustedImages string) string {
var (
serverTLSSecretNameAnnotationKey = references.AnnotationKey("secret", serverTLSSecretName)
cosignPublicKeysSecretNameAnnotationKey = references.AnnotationKey("secret", cosignPublicKeysSecretName)
@@ -380,7 +380,8 @@ spec:
- --health-bind-address=:8081
- --metrics-bind-address=:8080
- --port=10250
- - --use-only-image-pull-secrets=true
+ - --use-only-image-pull-secrets=` + useOnlyImagePullSecrets + `
+ - --insecure-allow-untrusted-images=` + allowUntrustedImages + `
image: ` + image + `
imagePullPolicy: IfNotPresent
livenessProbe:
diff --git a/pkg/lakom/verifysignature/admission.go b/pkg/lakom/verifysignature/admission.go
index 888eb4e3..ded01bdd 100644
--- a/pkg/lakom/verifysignature/admission.go
+++ b/pkg/lakom/verifysignature/admission.go
@@ -35,6 +35,7 @@ type HandleBuilder struct {
cacheTTL time.Duration
cacheRefreshInterval time.Duration
useOnlyImagePullSecrets bool
+ allowUntrustedImages bool
}
// NewHandleBuilder returns new handle builder.
@@ -55,6 +56,12 @@ func (hb HandleBuilder) WithUseOnlyImagePullSecrets(useOnlyImagePullSecrets bool
return hb
}
+// WithAllowUntrustedImages configures the webhook to allow images without trusted signature.
+func (hb HandleBuilder) WithAllowUntrustedImages(allowUntrustedImages bool) HandleBuilder {
+ hb.allowUntrustedImages = allowUntrustedImages
+ return hb
+}
+
// WithCosignPublicKeysReader sets the reader with the cosign public keys.
func (hb HandleBuilder) WithCosignPublicKeysReader(cosignPublicKeysReader io.Reader) HandleBuilder {
hb.cosignPublicKeysReader = cosignPublicKeysReader
@@ -87,6 +94,7 @@ func (hb HandleBuilder) Build() (*handler, error) {
reader: hb.mgr.GetAPIReader(),
decoder: admission.NewDecoder(hb.mgr.GetScheme()),
useOnlyImagePullSecrets: hb.useOnlyImagePullSecrets,
+ allowUntrustedImages: hb.allowUntrustedImages,
}
verifier Verifier
)
@@ -121,6 +129,7 @@ type handler struct {
verifier Verifier
useOnlyImagePullSecrets bool
+ allowUntrustedImages bool
}
var (
@@ -160,6 +169,14 @@ func (h *handler) Handle(ctx context.Context, request admission.Request) admissi
logger := h.logger.WithValues("pod", client.ObjectKeyFromObject(pod))
if err := h.validatePod(ctx, logger, pod); err != nil {
+ if h.allowUntrustedImages {
+ logger.Info("pod validation failed but untrusted images are allowed", "error", err.Error())
+ warningsResponse := admission.Allowed("untrusted images are allowed")
+ warningsResponse.Warnings = []string{
+ fmt.Sprintf("Failed to admit pod with error: %q", err.Error()),
+ }
+ return warningsResponse
+ }
logger.Error(err, "pod validation failed")
return admission.Denied(err.Error())
}
diff --git a/pkg/lakom/verifysignature/admission_test.go b/pkg/lakom/verifysignature/admission_test.go
index 544b4217..77d6fd7a 100644
--- a/pkg/lakom/verifysignature/admission_test.go
+++ b/pkg/lakom/verifysignature/admission_test.go
@@ -103,10 +103,9 @@ var _ = Describe("Admission Handler", func() {
WithCosignPublicKeysReader(reader).
WithCacheTTL(time.Minute * 10).
WithCacheRefreshInterval(time.Second * 30).
+ WithAllowUntrustedImages(false).
Build()
-
Expect(err).ToNot(HaveOccurred())
-
handler = h
})
@@ -137,6 +136,39 @@ var _ = Describe("Admission Handler", func() {
Expect(response.Result.Code).To(BeEquivalentTo(http.StatusOK))
})
+ It("Should allow untrusted images", func() {
+ mgr.EXPECT().GetAPIReader().Return(apiReader)
+ mgr.EXPECT().GetScheme().Return(scheme)
+ reader := strings.NewReader(cosignPublicKey)
+
+ allowUntrustedHandler, err := verifysignature.
+ NewHandleBuilder().
+ WithManager(mgr).
+ WithLogger(logger.WithName("test-cosign-untrusted-handler")).
+ WithCosignPublicKeysReader(reader).
+ WithCacheTTL(time.Minute * 10).
+ WithCacheRefreshInterval(time.Second * 30).
+ WithAllowUntrustedImages(true).
+ Build()
+ Expect(err).ToNot(HaveOccurred())
+
+ req := admissionRequestBuilder{
+ gvk: podGVK,
+ operation: admissionv1.Update,
+ object: podWithImage(pod, "alpine@sha256:11e21d7b981a59554b3f822c49f6e9f57b6068bb74f49c4cd5cc4c663c7e5160"),
+ }.Build()
+ ar := allowUntrustedHandler.Handle(ctx, req)
+ Expect(ar.Allowed).To(BeTrue())
+ Expect(ar.Result.Code).To(BeEquivalentTo(http.StatusOK))
+ Expect(ar.Warnings).To(ContainElement(ContainSubstring("Failed to admit pod with error")))
+ Expect(ar.Warnings).To(ContainElement(ContainSubstring("Forbidden: no valid signature found for image")))
+ Expect(ar.Result.Message).To(ContainSubstring("untrusted images are allowed"))
+
+ ar = handler.Handle(ctx, req)
+ Expect(ar.Allowed).To(BeFalse())
+ Expect(ar.Result.Code).To(Satisfy(isHTTPError))
+ Expect(ar.Result.Message).To(ContainSubstring("Forbidden: no valid signature found for image"))
+ })
})
func isHTTPError(code int32) bool {