-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -146,13 +146,25 @@ This requirement requires an image to be signed with an expected identity, or ac | |
| "keyType": "GPGKeys", /* The only currently supported value */ | ||
| "keyPath": "/path/to/local/keyring/file", | ||
| "keyData": "base64-encoded-keyring-data", | ||
| "signedIdentity": identity_requirement | ||
| "signedIdentity": identity_requirement, | ||
| "referencesByDigest": refs_by_digest_requirement | ||
| } | ||
| ``` | ||
| <!-- Later: other keyType values --> | ||
|
|
||
| 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. | ||
| One of the following alternatives are supported: | ||
|
|
||
| - 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"} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| ``` | ||
| - References by digest are rejected and no signature verification is performed. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| ```json | ||
| {"referencesByDigest: reject"} | ||
| ``` | ||
| The `signedIdentity` field, a JSON object, specifies what image identity the signature claims about the image. | ||
| One of the following alternatives are supported: | ||
|
|
||
|
|
@@ -257,27 +269,3 @@ selectively allow individual transports and scopes as desired. | |
| "default": [{"type": "insecureAcceptAnything"}] | ||
| } | ||
| ``` | ||
|
|
||
| ### A _temporary_ work-around to allow accessing any image by digest | ||
|
|
||
| Usually, identities in signatures use the _repository_`:`_tag_ format, | ||
| which is not matched when pulling a specific image using a digest. | ||
| To allow such operations, a policy may set `signedIdentity` to `matchRepository`, similar | ||
| to the following fragment: | ||
|
|
||
| ```json | ||
| "hostname:5000/allow/pull/by/tag": [ | ||
| { | ||
| "type": "signedBy", | ||
| "keyType": "GPGKeys", | ||
| "keyPath": "/path/to/some.gpg" | ||
| "signedIdentity": { | ||
| "type": "matchRepository" | ||
| } | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| *Warning*: This completely turns off tag matching for the signature check in question, | ||
| allowing also pulls by tag to accept signatures for any other tag. | ||
| A more granular solution for this situation will be provided in the future ( https://github.com/containers/image/issues/99 ). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -313,7 +313,7 @@ func (pr *prReject) UnmarshalJSON(data []byte) error { | |
| } | ||
|
|
||
| // newPRSignedBy returns a new prSignedBy if parameters are valid. | ||
| func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) { | ||
| func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIdentity PolicyReferenceMatch, referencesByDigest sbUntaggedReferenceMatch) (*prSignedBy, error) { | ||
| if !keyType.IsValid() { | ||
| return nil, InvalidPolicyFormatError(fmt.Sprintf("invalid keyType \"%s\"", keyType)) | ||
| } | ||
|
|
@@ -323,33 +323,37 @@ func newPRSignedBy(keyType sbKeyType, keyPath string, keyData []byte, signedIden | |
| if signedIdentity == nil { | ||
| return nil, InvalidPolicyFormatError("signedIdentity not specified") | ||
| } | ||
| if referencesByDigest != SBKeyTypeUntaggedReferenceAllow && referencesByDigest != SBKeyTypeUntaggedReferenceReject { | ||
| return nil, InvalidPolicyFormatError("referencesByDigest accepts either allow or reject") | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test, please. |
||
| return &prSignedBy{ | ||
| prCommon: prCommon{Type: prTypeSignedBy}, | ||
| KeyType: keyType, | ||
| KeyPath: keyPath, | ||
| KeyData: keyData, | ||
| SignedIdentity: signedIdentity, | ||
| prCommon: prCommon{Type: prTypeSignedBy}, | ||
| KeyType: keyType, | ||
| KeyPath: keyPath, | ||
| KeyData: keyData, | ||
| SignedIdentity: signedIdentity, | ||
| ReferencesByDigest: referencesByDigest, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test, please. (I would expect |
||
| }, nil | ||
| } | ||
|
|
||
| // newPRSignedByKeyPath is NewPRSignedByKeyPath, except it returns the private type. | ||
| func newPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) { | ||
| return newPRSignedBy(keyType, keyPath, nil, signedIdentity) | ||
| func newPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch, referencesByDigest sbUntaggedReferenceMatch) (*prSignedBy, error) { | ||
| return newPRSignedBy(keyType, keyPath, nil, signedIdentity, referencesByDigest) | ||
| } | ||
|
|
||
| // NewPRSignedByKeyPath returns a new "signedBy" PolicyRequirement using a KeyPath | ||
| func NewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) { | ||
| return newPRSignedByKeyPath(keyType, keyPath, signedIdentity) | ||
| func NewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch, referencesByDigest sbUntaggedReferenceMatch) (PolicyRequirement, error) { | ||
| return newPRSignedByKeyPath(keyType, keyPath, signedIdentity, referencesByDigest) | ||
| } | ||
|
|
||
| // newPRSignedByKeyData is NewPRSignedByKeyData, except it returns the private type. | ||
| func newPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (*prSignedBy, error) { | ||
| return newPRSignedBy(keyType, "", keyData, signedIdentity) | ||
| func newPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch, referencesByDigest sbUntaggedReferenceMatch) (*prSignedBy, error) { | ||
| return newPRSignedBy(keyType, "", keyData, signedIdentity, referencesByDigest) | ||
| } | ||
|
|
||
| // NewPRSignedByKeyData returns a new "signedBy" PolicyRequirement using a KeyData | ||
| func NewPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) (PolicyRequirement, error) { | ||
| return newPRSignedByKeyData(keyType, keyData, signedIdentity) | ||
| func NewPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch, referencesByDigest sbUntaggedReferenceMatch) (PolicyRequirement, error) { | ||
| return newPRSignedByKeyData(keyType, keyData, signedIdentity, referencesByDigest) | ||
| } | ||
|
|
||
| // Compile-time check that prSignedBy implements json.Unmarshaler. | ||
|
|
@@ -375,6 +379,8 @@ func (pr *prSignedBy) UnmarshalJSON(data []byte) error { | |
| return &tmp.KeyData | ||
| case "signedIdentity": | ||
| return &signedIdentity | ||
| case "referencesByDigest": | ||
| return &tmp.ReferencesByDigest | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test, please. (Invalid value, duplicated value) |
||
| default: | ||
| return nil | ||
| } | ||
|
|
@@ -395,15 +401,19 @@ func (pr *prSignedBy) UnmarshalJSON(data []byte) error { | |
| tmp.SignedIdentity = si | ||
| } | ||
|
|
||
| if tmp.ReferencesByDigest == "" { | ||
| tmp.ReferencesByDigest = SBKeyTypeUntaggedReferenceAllow | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A test, please. |
||
|
|
||
| var res *prSignedBy | ||
| var err error | ||
| switch { | ||
| case gotKeyPath && gotKeyData: | ||
| return InvalidPolicyFormatError("keyPath and keyData cannot be used simultaneously") | ||
| case gotKeyPath && !gotKeyData: | ||
| res, err = newPRSignedByKeyPath(tmp.KeyType, tmp.KeyPath, tmp.SignedIdentity) | ||
| res, err = newPRSignedByKeyPath(tmp.KeyType, tmp.KeyPath, tmp.SignedIdentity, tmp.ReferencesByDigest) | ||
| case !gotKeyPath && gotKeyData: | ||
| res, err = newPRSignedByKeyData(tmp.KeyType, tmp.KeyData, tmp.SignedIdentity) | ||
| res, err = newPRSignedByKeyData(tmp.KeyType, tmp.KeyData, tmp.SignedIdentity, tmp.ReferencesByDigest) | ||
| case !gotKeyPath && !gotKeyData: | ||
| return InvalidPolicyFormatError("At least one of keyPath and keyData mus be specified") | ||
| default: // Coverage: This should never happen | ||
|
|
@@ -501,7 +511,7 @@ func (pr *prSignedBaseLayer) UnmarshalJSON(data []byte) error { | |
| return nil | ||
| } | ||
|
|
||
| // newPolicyRequirementFromJSON parses JSON data into a PolicyReferenceMatch implementation. | ||
| // newPolicyReferenceFromJSON parses JSON data into a PolicyReferenceMatch implementation. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better (thanks!), but still misses a |
||
| func newPolicyReferenceMatchFromJSON(data []byte) (PolicyReferenceMatch, error) { | ||
| var typeField prmCommon | ||
| if err := json.Unmarshal(data, &typeField); err != nil { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,7 +70,10 @@ type PolicyReferenceMatch interface { | |
| // matchesDockerReference decides whether a specific image identity is accepted for an image | ||
| // (or, usually, for the image's Reference().DockerReference()). Note that | ||
| // image.Reference().DockerReference() may be nil. | ||
| matchesDockerReference(image types.UnparsedImage, signatureDockerReference string) bool | ||
| // | ||
| // TODO(runcom): document byDigest | ||
| // TODO(runcom): document digest | ||
| matchesDockerReference(image types.UnparsedImage, signatureDockerReference, signatureManifstDigest string, byDigest bool) bool | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| } | ||
|
|
||
| // PolicyContext encapsulates a policy and possible cached state | ||
|
|
||
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