Skip to content
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: add cosign keyless support to trust policy #1503

Merged
merged 21 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,7 @@ e2e-helm-deploy-ratify:
--set notationCerts[0]="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \
--set cosignKeys[0]="$$(cat .staging/cosign/cosign.pub)" \
--set cosign.key="$$(cat .staging/cosign/cosign.pub)" \
--set cosign.tLogVerify=false \
susanshi marked this conversation as resolved.
Show resolved Hide resolved
--set oras.useHttp=true \
--set-file dockerConfig="mount_config.json" \
--set logger.level=debug
Expand All @@ -611,6 +612,7 @@ e2e-helm-deploy-ratify-without-tls-certs:
--set notaryCert="$$(cat ~/.config/notation/localkeys/ratify-bats-test.crt)" \
--set cosign.key="$$(cat .staging/cosign/cosign.pub)" \
--set cosignKeys[0]="$$(cat .staging/cosign/cosign.pub)" \
--set cosign.tLogVerify=false \
--set oras.useHttp=true \
--set-file dockerConfig="mount_config.json" \
--set logger.level=debug
Expand Down
7 changes: 7 additions & 0 deletions charts/ratify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ Values marked `# DEPRECATED` in the `values.yaml` as well as **DEPRECATED** in t
| cosignKeys | An array of public keys used to create inline key management providers used by Cosign verifier | `[]` |
| cosign.enabled | Enables/disables cosign tag-based signature lookup in ORAS store. MUST be set to true for cosign verification. | `true` |
| cosign.scopes | An array of scopes relevant to the single trust policy configured in Cosign verifier. A scope of '*' is a global wildcard character to represent all images apply. | `["*"]` |
| cosign.rekorURL | URL string reference to remote rekor server. If not specified, implementation will default to use Rekor public good instance `https://rekor.sigstore.dev`. | `` |
| cosign.tLogVerify | Enables/disables verification of presence of signature in Transparency log. | `true` |
| cosign.keyless.ctLogVerify | Enables/disables verification of presence of Secure Certificate Timestamp (SCT) in transparency log | `true` |
| cosign.keyless.certificateIdentity | String certificate identity used for exact identity match during verification. Either `certificateIdentity` or `certificateIdentityRegExp` MUST be defined, but both cannot be defined at together | `` |
| cosign.keyless.certificateIdentityRegExp | String certificate identity regular expression for identity matching during verification. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either `certificateIdentity` or `certificateIdentityRegExp` MUST be defined, but both cannot be defined together | `` |
| cosign.keyless.certificateOIDCIssuer | String certificate OIDC issuer for exact issuer matching during verification. Either `certificateOIDCIssuer` or `certificateOIDCIssuerRegExp` MUST be defined, but both cannot be defined together | `` |
| cosign.keyless.certificateOIDCIssuerRegExp | String certificate OIDC issuer regular expression for issuer matching during verification. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either `certificateOIDCIssuer` or `certificateOIDCIssuerRegExp` MUST be defined, but both cannot be defined together | `` |
| vulnerabilityreport.enabled | Enables/disables installation of vulnerability report verifier | `false` |
| vulnerabilityreport.passthrough | Enables/disables passthrough. All validation except `maximumAge` are disregarded and report content is added to verifier report | `false` |
| vulnerabilityreport.schemaURL | URL for JSON schema to validate report against | `` |
Expand Down
11 changes: 11 additions & 0 deletions charts/ratify/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,15 @@ Set the namespace exclusions for Assign
{{- if and (ne .Release.Namespace $gkNamespace) (ne .Release.Namespace "kube-system") }}
- {{ .Release.Namespace | quote}}
{{- end }}
{{- end }}

{{/*
Choose cosign legacy or not. Determined by if cosignKeys are provided or not OR if azurekeyvault is enabled and keys are provided OR if keyless is enabled and certificateIdentity, certificateIdentityRegExp, certificateOIDCIssuer, or certificateOIDCIssuerExp are provided
*/}}
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
{{- define "ratify.cosignLegacy" -}}
{{- if or (gt (len .Values.cosignKeys) 0) (and .Values.azurekeyvault.enabled (gt (len .Values.azurekeyvault.keys) 0)) .Values.cosign.keyless.certificateIdentity .Values.cosign.keyless.certificateIdentityRegExp .Values.cosign.keyless.certificateOIDCIssuer .Values.cosign.keyless.certificateOIDCIssuerExp -}}
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
false
{{- else }}
true
{{- end }}
{{- end }}
12 changes: 11 additions & 1 deletion charts/ratify/templates/verifier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ spec:
name: cosign
artifactTypes: application/vnd.dev.cosign.artifact.sig.v1+json
parameters:
{{- if or (gt (len .Values.cosignKeys) 0) (and .Values.azurekeyvault.enabled (gt (len .Values.azurekeyvault.keys) 0)) }}
{{- if (eq (include "ratify.cosignLegacy" .) "false") }}
trustPolicies:
- name: default
version: 1.0.0
Expand All @@ -65,6 +65,16 @@ spec:
{{- if and .Values.azurekeyvault.enabled (gt (len .Values.azurekeyvault.keys) 0) }}
- provider: kmprovider-akv
{{- end }}
tLogVerify: {{ .Values.cosign.tLogVerify }}
rekorURL: {{ .Values.cosign.rekorURL }}
{{- if or .Values.cosign.keyless.certificateIdentity .Values.cosign.keyless.certificateIdentityRegExp .Values.cosign.keyless.certificateOIDCIssuer .Values.cosign.keyless.certificateOIDCIssuerRegExp }}
keyless:
ctLogVerify: {{ .Values.cosign.keyless.ctLogVerify }}
certificateIdentity: {{ .Values.cosign.keyless.certificateIdentity }}
certificateIdentityRegExp: {{ .Values.cosign.keyless.certificateIdentityRegExp }}
certificateOIDCIssuer: {{ .Values.cosign.keyless.certificateOIDCIssuer }}
certificateOIDCIssuerRegExp: {{ .Values.cosign.keyless.certificateOIDCIssuerRegExp }}
{{- end }}
{{- else }}
key: /usr/local/ratify-certs/cosign/cosign.pub
{{- end }}
Expand Down
9 changes: 9 additions & 0 deletions charts/ratify/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ cosign:
enabled: true
scopes: ["*"] # corresponds to a single trust policy
key: "" # DEPRECATED: Use cosignKeys instead
rekorURL: ""
tLogVerify: true
keyless:
ctLogVerify: true
certificateIdentity: ""
certificateIdentityRegExp: ""
certificateOIDCIssuer: ""
certificateOIDCIssuerRegExp: ""

vulnerabilityreport:
enabled: false
passthrough: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ spec:
scopes:
- "*"
keys:
- provider: ratify-cosign-inline-key-0
- provider: ratify-cosign-inline-key-0
tLogVerify: false
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ spec:
- "*"
keys:
- provider: default/ratify-cosign-inline-key-0
tLogVerify: false
73 changes: 63 additions & 10 deletions pkg/verifier/cosign/cosign.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
// where each entry corresponds to a single signature verified
type Extension struct {
SignatureExtension []cosignExtensionList `json:"signatures,omitempty"`
TrustPolicy string `json:"trustPolicy,omitempty"`
}

// cosignExtensionList is the structure verifications performed
Expand All @@ -97,6 +98,7 @@
BundleVerified bool `json:"bundleVerified"`
Err string `json:"error,omitempty"`
KeyInformation PKKey `json:"keyInformation,omitempty"`
Summary []string `json:"summary,omitempty"`
}

type cosignVerifier struct {
Expand All @@ -119,7 +121,16 @@
// used for mocking purposes
var getKeysMaps = getKeysMapsDefault

const verifierType string = "cosign"
const (
verifierType string = "cosign"
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
annotationMessage string = "The specified annotations were verified."
claimsMessage string = "The cosign claims were validated."
offlineBundleMessage string = "Existence of the claims in the transparency log was verified offline."
rekorClaimsMessage string = "The claims were present in the transparency log."
rekorSigMessage string = "The signatures were integrated into the transparency log when the certificate was valid."
sigVerifierMessage string = "The signatures were verified against the specified public key."
certVerifierMessage string = "The code-signing certificate was verified using trusted certificate authority certificates."
)

// init() registers the cosign verifier with the factory
func init() {
Expand Down Expand Up @@ -148,6 +159,7 @@
legacy := true
// if trustPolicies are provided and non-legacy, create the trust policies
if config.KeyRef == "" && config.RekorURL == "" && len(config.TrustPolicies) > 0 {
logger.GetLogger(context.Background(), logOpt).Debugf("legacy cosign verifier configuration not found, creating trust policies")
trustPolicies, err = CreateTrustPolicies(config.TrustPolicies, verifierName)
if err != nil {
return nil, err
Expand Down Expand Up @@ -196,7 +208,7 @@

func (v *cosignVerifier) verifyInternal(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (verifier.VerifierResult, error) {
// get the map of keys and relevant cosign options for that reference
keysMap, cosignOpts, err := getKeysMaps(ctx, v.trustPolicies, subjectReference.Original, v.namespace)
keysMap, cosignOpts, trustPolicy, err := getKeysMaps(ctx, v.trustPolicies, subjectReference.Original, v.namespace)
if err != nil {
return errorToVerifyResult(v.name, v.verifierType, err), nil
}
Expand Down Expand Up @@ -275,12 +287,29 @@
extension.IsSuccess = false
extension.Err = err.Error()
} else {
extension.Summary = verificationMessage(bundleVerified, &cosignOpts)
hasValidSignature = true
}
extensionListEntry.Verifications = append(extensionListEntry.Verifications, extension)
}

// TODO: perform keyless verification instead if no keys are found
// if no keys are found, perform keyless verification
if len(keysMap) == 0 {
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
// verify signature with cosign options + perform bundle verification
bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, &cosignOpts)
extension := cosignExtension{
IsSuccess: true,
BundleVerified: bundleVerified,

Check warning on line 302 in pkg/verifier/cosign/cosign.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/cosign/cosign.go#L299-L302

Added lines #L299 - L302 were not covered by tests
}
if err != nil {
extension.IsSuccess = false
extension.Err = err.Error()
} else {
extension.Summary = verificationMessage(bundleVerified, &cosignOpts)
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
hasValidSignature = true

Check warning on line 309 in pkg/verifier/cosign/cosign.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/cosign/cosign.go#L304-L309

Added lines #L304 - L309 were not covered by tests
}
extensionListEntry.Verifications = append(extensionListEntry.Verifications, extension)

Check warning on line 311 in pkg/verifier/cosign/cosign.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/cosign/cosign.go#L311

Added line #L311 was not covered by tests
}
sigExtensions = append(sigExtensions, extensionListEntry)
}

Expand All @@ -290,12 +319,12 @@
Type: v.verifierType,
IsSuccess: true,
Message: "cosign verification success. valid signatures found. please refer to extensions field for verifications performed.",
Extensions: Extension{SignatureExtension: sigExtensions},
Extensions: Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()},
}, nil
}

errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid signatures found"))
errorResult.Extensions = Extension{SignatureExtension: sigExtensions}
errorResult.Extensions = Extension{SignatureExtension: sigExtensions, TrustPolicy: trustPolicy.GetName()}

Check warning on line 327 in pkg/verifier/cosign/cosign.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/cosign/cosign.go#L327

Added line #L327 was not covered by tests
return errorResult, nil
}

Expand Down Expand Up @@ -521,27 +550,27 @@
}

// getKeysMapsDefault returns the map of keys and cosign options for the reference
func getKeysMapsDefault(ctx context.Context, trustPolicies *TrustPolicies, reference string, namespace string) (map[PKKey]keymanagementprovider.PublicKey, cosign.CheckOpts, error) {
func getKeysMapsDefault(ctx context.Context, trustPolicies *TrustPolicies, reference string, namespace string) (map[PKKey]keymanagementprovider.PublicKey, cosign.CheckOpts, TrustPolicy, error) {
// get the trust policy for the reference
trustPolicy, err := trustPolicies.GetScopedPolicy(reference)
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, cosign.CheckOpts{}, err
return nil, cosign.CheckOpts{}, nil, err
}
logger.GetLogger(ctx, logOpt).Debugf("selected trust policy %s for reference %s", trustPolicy.GetName(), reference)

// get the map of keys for that reference
keysMap, err := trustPolicy.GetKeys(ctx, namespace)
if err != nil {
return nil, cosign.CheckOpts{}, err
return nil, cosign.CheckOpts{}, nil, err
}

// get the cosign options for that trust policy
cosignOpts, err := trustPolicy.GetCosignOpts(ctx)
if err != nil {
return nil, cosign.CheckOpts{}, err
return nil, cosign.CheckOpts{}, nil, err

Check warning on line 570 in pkg/verifier/cosign/cosign.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/cosign/cosign.go#L570

Added line #L570 was not covered by tests
}

return keysMap, cosignOpts, nil
return keysMap, cosignOpts, trustPolicy, nil
}

// processAKVSignature processes the AKV signature and returns the hash type, signature and error
Expand Down Expand Up @@ -594,3 +623,27 @@
}
return hashType, staticSig, nil
}

// verificationMessage returns a string list of all verifications performed
// based on https://github.com/sigstore/cosign/blob/5ae2e31c30ee87e035cc57ebbbe2ecf3b6549ff5/cmd/cosign/cli/verify/verify.go#L318
func verificationMessage(bundleVerified bool, co *cosign.CheckOpts) []string {
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
var messages []string
if co.ClaimVerifier != nil {
if co.Annotations != nil {
messages = append(messages, annotationMessage)
}
messages = append(messages, claimsMessage)
}
if bundleVerified {
messages = append(messages, offlineBundleMessage)
} else if co.RekorClient != nil {
messages = append(messages, rekorClaimsMessage)
messages = append(messages, rekorSigMessage)
}
if co.SigVerifier != nil {
messages = append(messages, sigVerifierMessage)
} else {
messages = append(messages, certVerifierMessage)
akashsinghal marked this conversation as resolved.
Show resolved Hide resolved
}
return messages
}
Loading
Loading