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 <sgrunert@redhat.com>
This commit is contained in:
Sascha Grunert 2023-05-02 09:40:06 +02:00
parent 19830bf51b
commit 63b69dd50c
No known key found for this signature in database
GPG Key ID: 09D97D153EF94D93
4 changed files with 74 additions and 11 deletions

View File

@ -29,6 +29,7 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
crierrors "k8s.io/cri-api/pkg/errors"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/events" "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.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()) m.backOff.Next(backOffKey, m.backOff.Clock.Now())
// Error assertions via errors.Is is not supported by gRPC (remote runtime) errors right now. msg, err := evalCRIPullErr(container, imagePullResult.err)
// See https://github.com/grpc/grpc-go/issues/3616 return "", msg, err
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
} }
m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID) m.podPullingTimeRecorder.RecordImageFinishedPulling(pod.UID)
m.logIt(ref, v1.EventTypeNormal, events.PulledImage, logPrefix, fmt.Sprintf("Successfully pulled image %q in %v (%v including waiting)", 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 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, // applyDefaultImageTag parses a docker image string, if it doesn't contain any tag or digest,
// a default tag will be applied. // a default tag will be applied.
func applyDefaultImageTag(image string) (string, error) { func applyDefaultImageTag(image string) (string, error) {

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
"k8s.io/client-go/util/flowcontrol" "k8s.io/client-go/util/flowcontrol"
crierrors "k8s.io/cri-api/pkg/errors"
. "k8s.io/kubernetes/pkg/kubelet/container" . "k8s.io/kubernetes/pkg/kubelet/container"
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing" ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
testingclock "k8s.io/utils/clock/testing" testingclock "k8s.io/utils/clock/testing"
@ -413,3 +414,46 @@ func TestMaxParallelImagePullsLimit(t *testing.T) {
wg.Wait() wg.Wait()
fakeRuntime.AssertCallCounts("PullImage", 7) 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)
})
}
}

View File

@ -37,9 +37,6 @@ var (
// ErrImageNeverPull - Required Image is absent on host and PullPolicy is NeverPullImage // ErrImageNeverPull - Required Image is absent on host and PullPolicy is NeverPullImage
ErrImageNeverPull = errors.New("ErrImageNeverPull") 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 - Unable to parse the image name.
ErrInvalidImageName = errors.New("InvalidImageName") ErrInvalidImageName = errors.New("InvalidImageName")
) )

View File

@ -17,10 +17,20 @@ limitations under the License.
package errors package errors
import ( import (
"errors"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "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 // IsNotFound returns a boolean indicating whether the error
// is grpc not found error. // is grpc not found error.
// See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md // See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md