-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(docker): add verification of image digest(s) #227
Conversation
473d629
to
55b2bab
Compare
4971735
to
78117b5
Compare
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
78117b5
to
8ad516f
Compare
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
@carolynvs @radu-matei @silvin-lubecki if any of y'all have a spare moment, it would be great to get feedback on this PR, as it addresses an area where cnab-go isn't in adherence to the spec. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I've provided some suggestions on how to use the docker library for the parsing.
./e2e-kind.sh | ||
|
||
.PHONY: compile-integration-tests | ||
compile-integration-tests: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I've had this bite me a few times. Now I just need to remember to run coverage before pushing the PR. 😊
driver/docker/docker.go
Outdated
// validateImageDigest validates the operation image digest, if exists, against | ||
// the supplied repoDigests | ||
func (d *Driver) validateImageDigest(image bundle.InvocationImage, repoDigests []string) error { | ||
if image.Digest != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: swap the check and do an immediate return at the top instead. It's a bit easier to follow and the majority of the code in the function isn't nested in an if statement, e.g.
if image.Digest == "" {
return nil
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I feel like I always miss this one; please keep reminding me! Definitely like the clarity/readability improvement.
driver/docker/docker.go
Outdated
|
||
repoDigest := repoDigests[0] | ||
// RepoDigests are of the form 'imageName@sha256:<sha256>'; we parse out the digest itself for comparison | ||
// TODO: is there a better (i.e. via docker api) way to extract the digest? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's something
// RepoDigests are of the form 'imageName@sha256:<sha256>'; we parse out the digest itself for comparison
repoDigest := repoDigests[0]
ref, err := reference.ParseNormalizedNamed(repoDigest)
if err != nil {
return err
}
digestRef, ok := ref.(reference.Digested)
if !ok {
return fmt.Errorf("unable to parse repo digest %s", repoDigest)
}
digest := digestRef.Digest().String()
This is the updated unit test I used to verify
func TestDriver_ValidateImageDigest(t *testing.T) {
repoDigests := []string{
"myreg/myimg@sha256:9d4178a3b6059e46a02e4c5b39ac26d51d91046faeb7bb9f3c3825f92adafc5a",
}
t.Run("no image digest", func(t *testing.T) {
d := &Driver{}
image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
err := d.validateImageDigest(image, repoDigests)
assert.NoError(t, err)
})
t.Run("image digest exists - no match exists", func(t *testing.T) {
d := &Driver{}
image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Digest = "sha256:8b92b7269f59e3ed824e811a1ff1ee64f0d44c0218efefada57a4bebc2d7ef6f"
err := d.validateImageDigest(image, repoDigests)
assert.NotNil(t, err, "expected an error")
assert.Contains(t, err.Error(), "content digest mismatch")
})
t.Run("image digest exists - a match exists", func(t *testing.T) {
d := &Driver{}
image := bundle.InvocationImage{}
image.Image = "myreg/myimg"
image.Digest = "sha256:9d4178a3b6059e46a02e4c5b39ac26d51d91046faeb7bb9f3c3825f92adafc5a"
err := d.validateImageDigest(image, repoDigests)
assert.NoError(t, err)
})
}
I had to switch to full digests because the docker library doesn't parse short digests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, I've incorporated these changes. Changing to full digests in the tests is also an improvement.
Also added a test case around an image having multiple repo digests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was sad to see livebeef/deadbeef go as test values. 😂
driver/docker/docker.go
Outdated
} | ||
// TODO: if digest empty, do we want to provide a warning somehow? Spec says: | ||
// If the contentDigest is not present, the runtime SHOULD report an error so the user is aware that there is no contentDigest provided. | ||
return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is something that the the tool should do. In developer loops, we fully expect the digest to be left off until it has stabilized. So they (tools like porter or duffle) are in a better position to bubble up and make that warning proper.
For example, allowing it to be empty when running from a local bundle (and only displaying a warning) but perhaps displaying a stronger error if there is no digest in a published bundle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I've removed this TODO and we'll leave implementation up to tools.
Co-authored-by: Carolyn Van Slyck <me@carolynvanslyck.com> Signed-off-by: Vaughn Dice <vadice@microsoft.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM just one nit to resolve if you don't mind
driver/docker/docker.go
Outdated
repoDigest := repoDigests[0] | ||
ref, err := reference.ParseNormalizedNamed(repoDigest) | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I gave you informal code. We should probably wrap this error and provide a better error message with context. Otherwise it won't be clear what reference is invalid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated! And deadbeef returns in a test for this scenario! 🎉
Signed-off-by: Vaughn Dice <vadice@microsoft.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for reals this time
|
||
err := d.validateImageDigest(image, repoDigests) | ||
assert.NoError(t, err) | ||
badRepoDigests := []string{"myreg/myimg@sha256:deadbeef"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THE RETURN OF DEADBEEFS 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
github is not liking my approvals
/force LGTM dagnabbit
The specific part of the spec that this PR currently implements is the following line:
If a contentDigest field is present, a runtime MUST validate the image digest prior to executing an action.
I didn't see how this would be done generically, e.g. outside of specific driver implementations, hence implementing it on the Docker driver only. Am I right here? If so, perhaps a follow-up could implement similar in the kubernetes driver.
Note some remaining TODOs/Qs inline. Feel free to respond/answer in the form of code review comments.