-
Notifications
You must be signed in to change notification settings - Fork 395
[WIP] verify signature for untagged references #129
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
Conversation
|
@mtrmac PTAL even if this is still WIP |
signature/policy_eval_signedby.go
Outdated
| validateSignedDockerReference: func(ref string) error { | ||
| r := image.Reference().DockerReference() | ||
| if _, isCanonical := r.(reference.Canonical); isCanonical || reference.IsNameOnly(r) { | ||
| if !pr.UntaggedReference.matchesDockerReference(image, ref) { |
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.
not sure...if it's a canonical reference, should we match the digest in the signature with the image's manifest digest? /cc @mtrmac
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.
That definitely needs to happen somewhere, and right now it doesn’t. This is a bad fit, though, we should be verifying the user-specified digest even if no signatures exist or are required. So far I was thinking that the individual ImageSources which support digest references should do that in their GetManifests; it could probably be shared in copy.Image, OTOH it seems reasonable that this should be rejected for any use of that ImageSource.
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.
That definitely needs to happen somewhere, and right now it doesn’t. This is a bad fit, though, we should be verifying the user-specified digest even if no signatures exist or are required. So far I was thinking that the individual ImageSources which support digest references should do that in their GetManifests; it could probably be shared in copy.Image, OTOH it seems reasonable that this should be rejected for any use of that ImageSource.
isn't this a separate issue though? I agree we need to verify the user provided digest against what we retrieve from the network though.
I was more talking about this new UntaggedReference match. So the default will be matchAnyTag which means an user is trying to pull testimage@sha256:HEX_1 but the user has only signed testimage:latest and latest's manifest happens to be sha256:HEX_1.
Question, how do we handle this for matchAnyTag? The signature is valid for the tag and the user requests the digest for that tag so we allow that pull since the tag's manifest digest == user's pull reference digest. What happen otherwise? Shall we error out? Anyhow, I think this confusion come from matchAnyTag since it will match only the tag whose manifest's digest == user's pull reference digsest and not really any tag for an untagged reference.
Also, why do we call it UntaggedReference? an user can't initiate a pull w/o a valid tag or reference. We should call it CanonicalReference maybe. The pull all tags case in docker isn't different since what it does underneath is to append every tag to the image name and download the image:tag (hence it's not untagged)
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.
That definitely needs to happen somewhere, and right now it doesn’t. This is a bad fit, though, we should be verifying the user-specified digest even if no signatures exist or are required. So far I was thinking that the individual ImageSources which support digest references should do that in their GetManifests; it could probably be shared in copy.Image, OTOH it seems reasonable that this should be rejected for any use of that ImageSource.
I don't agree GetManifest(s) should do this check though. GetManifest just returns raw data, better to have callers verify digests, image sources are pretty low level, I wouldn't want to clutter them.
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.
isn't this a separate issue though? I agree we need to verify the user provided digest against what we retrieve from the network though.
Yes, it is orthogonal to this PR.
Question, how do we handle this for matchAnyTag?
Well, the signature doesn’t just contain a tag which we happily apply to any manifest; it also contains a manifest digest. We will accept a signature which says sha256:HEX_1 and testimage:anythingwedontcare. The “anythingwedontcare” is the “any tag” in this terminology.
What happen otherwise?
What is the “otherwise” case?
Also, why do we call it UntaggedReference? an user can't initiate a pull w/o a valid tag or reference.
Well, it is about a reference, and the reference in this case doesn’t have a tag. (I guess atomic pull doesn’t work right now if there isn’t a tag, if that’s what you mean, but things like skopeo copy docker:// docker:// generally do.) I am definitely open to other suggestions.
We should call it CanonicalReference maybe.
Canonical is jargon, and more importantly it is not necessarily obvious to everyone that it means “with a @digest".signedIdentityWhenReferencingByDigest`…no.
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.
can we assume people always want to pull-by-digest to match the tag in the signature and just have an "referencesByDigest = prohibit"?
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.
pull-by-digest to match the tag in the signature
= that the signature should be also with a digest, or = to match any tag?
The latter would override effects ofsignedIdentity:exactReference.
Not naming the default behavior is an nice dodge :) Well, it could be prohibit | allow… and then we can just use true/false with a field name something like allowDigestReferences.
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 like reject|allow 😄
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.
No, then we'll have the same issue that we want to allow pull-by-digest and we still need referencesByDigest <- if allow this will of course verify user's digest == sig's digest as usual and then signedIdentity can kick in nicely after this right?matchRepository...
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.
From IRC chat
matchExact(which is the defaultsignedIdentity) could turn intomatchRepositoryif pull-by-digest and check that user's requested digest == digest in signature. We may probably still needreferencesByDigest = reject|allowto allow admins to opt out of digets references and enforcing pulls by tag. See #129 (comment) also.
| if !reference.IsNameOnly(intended) { | ||
| return true | ||
| } | ||
| return signature.String() == intended.String() |
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.
This does not actually Prohibit?
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.
nah, this was meant to be the matchPrecisely case
|
I didn’t review this line by line yet, but structurally this seems reasonable. I can’t immediately see why there needs to be a separate Anyway, I’ll return to this for a more detailed look later. |
yup, I'm fine with that, I started coding w/o thinking about that just to have a working PR to test projectatomic/docker#200. Thanks, I'll fix this up and re-ping you. |
b2cd10e to
d2c406e
Compare
signature/policy_eval_signedby.go
Outdated
| if !pr.UntaggedReference.matchesDockerReference(image, ref) { | ||
| return PolicyRequirementError(fmt.Sprintf("Signature for identity %s is not accepted", ref)) | ||
| } | ||
| 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.
Hum, this is awkward. If signedIdentity is exactSomething (i.e. overrides the repo name being matched), we would want to reuse this in the pull-by-digest case as well so that the user only needs to specify the name override once.
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.
mmm but w/o this one would still add "matchRepository" to pull-by-digest which is awkward as well no?
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.
Perhaps, for the UntaggedReference, or whatever it ends up being called, don’t reuse PolicyReferenceMatch but define an entirely new enum. This validateSignedDockerReference callback would then call pr.SignedIdentity.matchesDockerReference with an extra parameter, that enum value.
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.
right, not sure I fully understand what you want to see though, matchesDockerReference(image types.UnparsedImage, signatureDockerReference string, untaggedPolicy PolicyUntaggedReferenceMatch)? where PolicyUntaggedReferenceMatch:
type PolicyUntaggedReferenceMatch interface {
// ???
}
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.
type sbUntaggedReferenceMatch stringmirroring how sbKeyType does it I think.
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.
(policy_types.go somewhat mixes public types, with public members and public constructors, and the like, for manually creating policies in code, and internal implementation details, like the interfaces which are really private to policy_eval* and friends.)
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.
alright, clear, so, when the user requests an image by digest this callback is called and matchesDockerReference is called with that sb string and do an if/else based on whether the image requested is canonical or not (I don't think it can happen to receive a request for reference.IsNameOnly right? even if I see in prmMatchExact you check that...)
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.
It is not captured in the type system, but yes, types.ImageReference.DockerReference documents !reference.IsNameOnly.
|
BTW I am a bit tempted to make the default I realize this is probably bordering on crazily unusable… |
b58f688 to
dbc0bda
Compare
|
@mtrmac PTAL as soon as you can, this version of the patch based on our discussions above works great with docker (except I need to polish, document and test the code :)) |
1e87956 to
5dcfae5
Compare
Signed-off-by: Antonio Murdaca <[email protected]>
5dcfae5 to
066c523
Compare
mtrmac
left a comment
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.
So, for now:
- The semantics of
referencesByDigestis unclear (documentation does not match code) - The semantics of the new parameters of
matchesDockerReferenceis undocumented. - Basically every single line, and every decision point (whether it is an extra
ifline or a comparison) should have a test, if at all possible).
I can help with the tests once the semantics and the approach are clear, to some extent; but the intent of the code is not immediately clear now.
| } | ||
|
|
||
| // newPolicyRequirementFromJSON parses JSON data into a PolicyReferenceMatch implementation. | ||
| // newPolicyReferenceFromJSON parses JSON data into a PolicyReferenceMatch implementation. |
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.
Better (thanks!), but still misses a Match
| } | ||
| if referencesByDigest != SBKeyTypeUntaggedReferenceAllow && referencesByDigest != SBKeyTypeUntaggedReferenceReject { | ||
| return nil, InvalidPolicyFormatError("referencesByDigest accepts either allow or reject") | ||
| } |
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.
A test, please.
| KeyPath: keyPath, | ||
| KeyData: keyData, | ||
| SignedIdentity: signedIdentity, | ||
| ReferencesByDigest: referencesByDigest, |
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.
A test, please. (I would expect TestNewPRSignedBy to fail right now.)
| case "signedIdentity": | ||
| return &signedIdentity | ||
| case "referencesByDigest": | ||
| return &tmp.ReferencesByDigest |
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.
A test, please. (Invalid value, duplicated value)
|
|
||
| if tmp.ReferencesByDigest == "" { | ||
| tmp.ReferencesByDigest = SBKeyTypeUntaggedReferenceAllow | ||
| } |
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.
A test, please.
| SignedIdentity PolicyReferenceMatch `json:"signedIdentity"` | ||
|
|
||
| // ReferencesByDigest specifies whether the signature is valid when the reference isn't tagged (e.g. canonical). | ||
| ReferencesByDigest sbUntaggedReferenceMatch `json:"referencesByDigest"` |
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 field name and the type name and the enum values all need to be named consistently.
|
|
||
| Exactly one of `keyPath` and `keyData` must be present, containing a GPG keyring of one or more public keys. Only signatures made by these keys are accepted. | ||
|
|
||
| The `referencesByDigest` field controls whether an reference with a digest should be taken into consideration when checking signatures or be rejected altogheter. |
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.
This seems unclear to me, the user does not have the context of our PR. What is “reference” and what is “taken into consideration”?
Perhaps something like
The
referencesByDigestfield controls treatment of images which are referred to (pulled, named, etc.) using a digest reference (e.g.busybox@sha256:…instead of a tag (busybox:latest).
| ```json | ||
| {"referencesByDigest: allow"} | ||
| ``` | ||
| - References by digest are rejected and no signature verification is performed. |
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.
s,and no signature verification is preformed,regardless of the existence and/or contents of signatures, if any; users must refer to images using a tag, ? “no signature verification is performed” could be misinterpreted to mean that something is let through.
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.
Nothing actually seems to implement it that way???! This ends up being used in the various if byDigest conditionals, at the end of both branches there is always an extra comparison. (BTW I’d expect a single enforcement point in prSignedBy.isSignatureAuthorAccepted, perhaps even before doing any cryptography.)
|
|
||
| - References by digest are allowed (default). In this case, the digest should match the one from the signature for the verification to be successful. | ||
| ```json | ||
| {"referencesByDigest: allow"} |
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 {…} form for signedIdentity is used because a signedIdentity value is such an object; but the above is not a valid "type":"signedBy" object. Perhaps, just use a bulleted list like - "allow": Reference by digest are …
| // | ||
| // TODO(runcom): document byDigest | ||
| // TODO(runcom): document digest | ||
| matchesDockerReference(image types.UnparsedImage, signatureDockerReference, signatureManifstDigest string, byDigest bool) bool |
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.
Yes, document. I’ll continue reading the code to figure this out, but the intent needs to be clearly stated in words as well.
|
Trying to document what this code does right now:
WIP I will continue the table, but right here when (Update: BTW: What happens if the used reference is both with a digest and tag? Can it happen? |
|
… actually, the So, @runcom, what about https://github.com/mtrmac/image/tree/untagged-reference-sig-check instead? Without (Doesn’t have tests, isn‘t production ready, yadda yadda. But it compiles and it could be enough to determine whether this would work for docker/docker or whether I am missing something.) |
|
@mtrmac I'll test that out as soon as I remove #129 from in projectatomic/docker#200 (or tomorrow at last) |
@mtrmac seems like you didn't forget anything and your branch seems to work correctly with docker! Let me know if I can grab it, add tests and whatnot and re-submit a PR here in containers/image (we'll likely fix pull-by-digest in atomic with this!) Here is the working docker code projectatomic/docker#209 @mtrmac pls check out the testing done so far in docker https://github.com/projectatomic/docker/blob/docker-1.12/integration-cli/docker_cli_pull_local_test.go#L635 |
Update for changed images.Type API
Fix #99
Hopefully the last piece needed for projectatomic/docker#200. This will allow us by default to pull an untagged reference (e.g. by digest) when the signature is valid for a tag.
This is still WIP - I would like feedback on the direction I'm going.
Signed-off-by: Antonio Murdaca [email protected]