From 63b69dd50ce806a2ae0413432ef47be77ac5fe10 Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Tue, 2 May 2023 09:40:06 +0200 Subject: [PATCH] Add support for CRI `ErrSignatureValidationFailed` This allows container runtimes to propagate an image signature verification error through the CRI and display that to the end user during image pull. There is no other behavioral difference compared to a regular image pull failure. Signed-off-by: Sascha Grunert --- pkg/kubelet/images/image_manager.go | 28 ++++++++---- pkg/kubelet/images/image_manager_test.go | 44 +++++++++++++++++++ pkg/kubelet/images/types.go | 3 -- .../src/k8s.io/cri-api/pkg/errors/errors.go | 10 +++++ 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/pkg/kubelet/images/image_manager.go b/pkg/kubelet/images/image_manager.go index 8ec8fb67a52..8910f64097f 100644 --- a/pkg/kubelet/images/image_manager.go +++ b/pkg/kubelet/images/image_manager.go @@ -29,6 +29,7 @@ import ( "k8s.io/klog/v2" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" + crierrors "k8s.io/cri-api/pkg/errors" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/events" ) @@ -158,14 +159,8 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, conta m.logIt(ref, v1.EventTypeWarning, events.FailedToPullImage, logPrefix, fmt.Sprintf("Failed to pull image %q: %v", container.Image, imagePullResult.err), klog.Warning) m.backOff.Next(backOffKey, m.backOff.Clock.Now()) - // Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now. - // See https://github.com/grpc/grpc-go/issues/3616 - if imagePullResult.err.Error() == ErrRegistryUnavailable.Error() { - msg := fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) - return "", msg, imagePullResult.err - } - - return "", imagePullResult.err.Error(), ErrImagePull + msg, err := evalCRIPullErr(container, imagePullResult.err) + return "", msg, err } m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID) m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting)", @@ -174,6 +169,23 @@ func (m *imageManager) EnsureImageExists(ctx context.Context, pod *v1.Pod, conta return imagePullResult.imageRef, "", nil } +func evalCRIPullErr(container *v1.Container, err error) (errMsg string, errRes error) { + // Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now. + // See https://github.com/grpc/grpc-go/issues/3616 + if err.Error() == crierrors.ErrRegistryUnavailable.Error() { + errMsg = fmt.Sprintf("image pull failed for %s because the registry is unavailable.", container.Image) + return errMsg, crierrors.ErrRegistryUnavailable + } + + if err.Error() == crierrors.ErrSignatureValidationFailed.Error() { + errMsg = fmt.Sprintf("image pull failed for %s because the signature validation failed.", container.Image) + return errMsg, crierrors.ErrSignatureValidationFailed + } + + // Fallback for no specific error + return err.Error(), ErrImagePull +} + // applyDefaultImageTag parses a docker image string, if it doesn't contain any tag or digest, // a default tag will be applied. func applyDefaultImageTag(image string) (string, error) { diff --git a/pkg/kubelet/images/image_manager_test.go b/pkg/kubelet/images/image_manager_test.go index 03d83db8e90..0e04c39e907 100644 --- a/pkg/kubelet/images/image_manager_test.go +++ b/pkg/kubelet/images/image_manager_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" + crierrors "k8s.io/cri-api/pkg/errors" . "k8s.io/kubernetes/pkg/kubelet/container" ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" testingclock "k8s.io/utils/clock/testing" @@ -413,3 +414,46 @@ func TestMaxParallelImagePullsLimit(t *testing.T) { wg.Wait() fakeRuntime.AssertCallCounts("PullImage", 7) } + +func TestEvalCRIPullErr(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + input error + assert func(string, error) + }{ + { + name: "fallback error", + input: errors.New("test"), + assert: func(msg string, err error) { + assert.ErrorIs(t, err, ErrImagePull) + assert.Contains(t, msg, "test") + }, + }, + { + name: "registry is unavailable", + input: crierrors.ErrRegistryUnavailable, + assert: func(msg string, err error) { + assert.ErrorIs(t, err, crierrors.ErrRegistryUnavailable) + assert.Contains(t, msg, "registry is unavailable") + }, + }, + { + name: "signature is invalid", + input: crierrors.ErrSignatureValidationFailed, + assert: func(msg string, err error) { + assert.ErrorIs(t, err, crierrors.ErrSignatureValidationFailed) + assert.Contains(t, msg, "signature validation failed") + }, + }, + } { + testInput := tc.input + testAssert := tc.assert + + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + msg, err := evalCRIPullErr(&v1.Container{}, testInput) + testAssert(msg, err) + }) + } +} diff --git a/pkg/kubelet/images/types.go b/pkg/kubelet/images/types.go index 3b0397faad4..52342b28ec1 100644 --- a/pkg/kubelet/images/types.go +++ b/pkg/kubelet/images/types.go @@ -37,9 +37,6 @@ var ( // ErrImageNeverPull - Required Image is absent on host and PullPolicy is NeverPullImage ErrImageNeverPull = errors.New("ErrImageNeverPull") - // ErrRegistryUnavailable - Get http error when pulling image from registry - ErrRegistryUnavailable = errors.New("RegistryUnavailable") - // ErrInvalidImageName - Unable to parse the image name. ErrInvalidImageName = errors.New("InvalidImageName") ) diff --git a/staging/src/k8s.io/cri-api/pkg/errors/errors.go b/staging/src/k8s.io/cri-api/pkg/errors/errors.go index 41d7b92466d..a4538669122 100644 --- a/staging/src/k8s.io/cri-api/pkg/errors/errors.go +++ b/staging/src/k8s.io/cri-api/pkg/errors/errors.go @@ -17,10 +17,20 @@ limitations under the License. package errors import ( + "errors" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) +var ( + // ErrRegistryUnavailable - Get http error on the PullImage RPC call. + ErrRegistryUnavailable = errors.New("RegistryUnavailable") + + // ErrSignatureValidationFailed - Unable to validate the image signature on the PullImage RPC call. + ErrSignatureValidationFailed = errors.New("SignatureValidationFailed") +) + // IsNotFound returns a boolean indicating whether the error // is grpc not found error. // See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md