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"
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) {

View File

@ -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)
})
}
}

View File

@ -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")
)

View File

@ -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