Merge pull request #1250 from fluxcd/cosign-identity-matching
cosign: allow identity matching for keyless verification
This commit is contained in:
commit
a8a81965c7
|
|
@ -190,6 +190,28 @@ type OCIRepositoryVerification struct {
|
||||||
// trusted public keys.
|
// trusted public keys.
|
||||||
// +optional
|
// +optional
|
||||||
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
|
||||||
|
|
||||||
|
// MatchOIDCIdentity specifies the identity matching criteria to use
|
||||||
|
// while verifying an OCI artifact which was signed using Cosign keyless
|
||||||
|
// signing. The artifact's identity is deemed to be verified if any of the
|
||||||
|
// specified matchers match against the identity.
|
||||||
|
// +optional
|
||||||
|
MatchOIDCIdentity []OIDCIdentityMatch `json:"matchOIDCIdentity,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OIDCIdentityMatch specifies options for verifying the certificate identity,
|
||||||
|
// i.e. the issuer and the subject of the certificate.
|
||||||
|
type OIDCIdentityMatch struct {
|
||||||
|
// Issuer specifies the regex pattern to match against to verify
|
||||||
|
// the OIDC issuer in the Fulcio certificate. The pattern must be a
|
||||||
|
// valid Go regular expression.
|
||||||
|
// +required
|
||||||
|
Issuer string `json:"issuer"`
|
||||||
|
// Subject specifies the regex pattern to match against to verify
|
||||||
|
// the identity subject in the Fulcio certificate. The pattern must
|
||||||
|
// be a valid Go regular expression.
|
||||||
|
// +required
|
||||||
|
Subject string `json:"subject"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OCIRepositoryStatus defines the observed state of OCIRepository
|
// OCIRepositoryStatus defines the observed state of OCIRepository
|
||||||
|
|
|
||||||
|
|
@ -834,6 +834,11 @@ func (in *OCIRepositoryVerification) DeepCopyInto(out *OCIRepositoryVerification
|
||||||
*out = new(meta.LocalObjectReference)
|
*out = new(meta.LocalObjectReference)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
|
if in.MatchOIDCIdentity != nil {
|
||||||
|
in, out := &in.MatchOIDCIdentity, &out.MatchOIDCIdentity
|
||||||
|
*out = make([]OIDCIdentityMatch, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryVerification.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryVerification.
|
||||||
|
|
@ -845,3 +850,18 @@ func (in *OCIRepositoryVerification) DeepCopy() *OCIRepositoryVerification {
|
||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *OIDCIdentityMatch) DeepCopyInto(out *OIDCIdentityMatch) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OIDCIdentityMatch.
|
||||||
|
func (in *OIDCIdentityMatch) DeepCopy() *OIDCIdentityMatch {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(OIDCIdentityMatch)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,32 @@ spec:
|
||||||
Chart dependencies, which are not bundled in the umbrella chart
|
Chart dependencies, which are not bundled in the umbrella chart
|
||||||
artifact, are not verified.
|
artifact, are not verified.
|
||||||
properties:
|
properties:
|
||||||
|
matchOIDCIdentity:
|
||||||
|
description: MatchOIDCIdentity specifies the identity matching
|
||||||
|
criteria to use while verifying an OCI artifact which was signed
|
||||||
|
using Cosign keyless signing. The artifact's identity is deemed
|
||||||
|
to be verified if any of the specified matchers match against
|
||||||
|
the identity.
|
||||||
|
items:
|
||||||
|
description: OIDCIdentityMatch specifies options for verifying
|
||||||
|
the certificate identity, i.e. the issuer and the subject
|
||||||
|
of the certificate.
|
||||||
|
properties:
|
||||||
|
issuer:
|
||||||
|
description: Issuer specifies the regex pattern to match
|
||||||
|
against to verify the OIDC issuer in the Fulcio certificate.
|
||||||
|
The pattern must be a valid Go regular expression.
|
||||||
|
type: string
|
||||||
|
subject:
|
||||||
|
description: Subject specifies the regex pattern to match
|
||||||
|
against to verify the identity subject in the Fulcio certificate.
|
||||||
|
The pattern must be a valid Go regular expression.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- issuer
|
||||||
|
- subject
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
provider:
|
provider:
|
||||||
default: cosign
|
default: cosign
|
||||||
description: Provider specifies the technology used to sign the
|
description: Provider specifies the technology used to sign the
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,32 @@ spec:
|
||||||
public keys used to verify the signature and specifies which provider
|
public keys used to verify the signature and specifies which provider
|
||||||
to use to check whether OCI image is authentic.
|
to use to check whether OCI image is authentic.
|
||||||
properties:
|
properties:
|
||||||
|
matchOIDCIdentity:
|
||||||
|
description: MatchOIDCIdentity specifies the identity matching
|
||||||
|
criteria to use while verifying an OCI artifact which was signed
|
||||||
|
using Cosign keyless signing. The artifact's identity is deemed
|
||||||
|
to be verified if any of the specified matchers match against
|
||||||
|
the identity.
|
||||||
|
items:
|
||||||
|
description: OIDCIdentityMatch specifies options for verifying
|
||||||
|
the certificate identity, i.e. the issuer and the subject
|
||||||
|
of the certificate.
|
||||||
|
properties:
|
||||||
|
issuer:
|
||||||
|
description: Issuer specifies the regex pattern to match
|
||||||
|
against to verify the OIDC issuer in the Fulcio certificate.
|
||||||
|
The pattern must be a valid Go regular expression.
|
||||||
|
type: string
|
||||||
|
subject:
|
||||||
|
description: Subject specifies the regex pattern to match
|
||||||
|
against to verify the identity subject in the Fulcio certificate.
|
||||||
|
The pattern must be a valid Go regular expression.
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- issuer
|
||||||
|
- subject
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
provider:
|
provider:
|
||||||
default: cosign
|
default: cosign
|
||||||
description: Provider specifies the technology used to sign the
|
description: Provider specifies the technology used to sign the
|
||||||
|
|
|
||||||
|
|
@ -3319,6 +3319,71 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
|
||||||
trusted public keys.</p>
|
trusted public keys.</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>matchOIDCIdentity</code><br>
|
||||||
|
<em>
|
||||||
|
<a href="#source.toolkit.fluxcd.io/v1beta2.OIDCIdentityMatch">
|
||||||
|
[]OIDCIdentityMatch
|
||||||
|
</a>
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<em>(Optional)</em>
|
||||||
|
<p>MatchOIDCIdentity specifies the identity matching criteria to use
|
||||||
|
while verifying an OCI artifact which was signed using Cosign keyless
|
||||||
|
signing. The artifact’s identity is deemed to be verified if any of the
|
||||||
|
specified matchers match against the identity.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 id="source.toolkit.fluxcd.io/v1beta2.OIDCIdentityMatch">OIDCIdentityMatch
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
(<em>Appears on:</em>
|
||||||
|
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositoryVerification">OCIRepositoryVerification</a>)
|
||||||
|
</p>
|
||||||
|
<p>OIDCIdentityMatch specifies options for verifying the certificate identity,
|
||||||
|
i.e. the issuer and the subject of the certificate.</p>
|
||||||
|
<div class="md-typeset__scrollwrap">
|
||||||
|
<div class="md-typeset__table">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>issuer</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Issuer specifies the regex pattern to match against to verify
|
||||||
|
the OIDC issuer in the Fulcio certificate. The pattern must be a
|
||||||
|
valid Go regular expression.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<code>subject</code><br>
|
||||||
|
<em>
|
||||||
|
string
|
||||||
|
</em>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<p>Subject specifies the regex pattern to match against to verify
|
||||||
|
the identity subject in the Fulcio certificate. The pattern must
|
||||||
|
be a valid Go regular expression.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -253,11 +253,13 @@ For practical information, see
|
||||||
**Note:** This feature is available only for Helm charts fetched from an OCI Registry.
|
**Note:** This feature is available only for Helm charts fetched from an OCI Registry.
|
||||||
|
|
||||||
`.spec.verify` is an optional field to enable the verification of [Cosign](https://github.com/sigstore/cosign)
|
`.spec.verify` is an optional field to enable the verification of [Cosign](https://github.com/sigstore/cosign)
|
||||||
signatures. The field offers two subfields:
|
signatures. The field offers three subfields:
|
||||||
|
|
||||||
- `.provider`, to specify the verification provider. Only supports `cosign` at present.
|
- `.provider`, to specify the verification provider. Only supports `cosign` at present.
|
||||||
- `.secretRef.name`, to specify a reference to a Secret in the same namespace as
|
- `.secretRef.name`, to specify a reference to a Secret in the same namespace as
|
||||||
the HelmChart, containing the Cosign public keys of trusted authors.
|
the HelmChart, containing the Cosign public keys of trusted authors.
|
||||||
|
- `.matchOIDCIdentity`, to specify a list of OIDC identity matchers. Please see
|
||||||
|
[Keyless verification](#keyless-verification) for more details.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
|
@ -307,6 +309,18 @@ For publicly available HelmCharts, which are signed using the
|
||||||
[Cosign Keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) procedure,
|
[Cosign Keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) procedure,
|
||||||
you can enable the verification by omitting the `.verify.secretRef` field.
|
you can enable the verification by omitting the `.verify.secretRef` field.
|
||||||
|
|
||||||
|
To verify the identity's subject and the OIDC issuer present in the Fulcio
|
||||||
|
certificate, you can specify a list of OIDC identity matchers using
|
||||||
|
`.spec.verify.matchOIDCIdentity`. The matcher provides two required fields:
|
||||||
|
|
||||||
|
- `.issuer`, to specify a regexp that matches against the OIDC issuer.
|
||||||
|
- `.subject`, to specify a regexp that matches against the subject identity in
|
||||||
|
the certificate.
|
||||||
|
Both values should follow the [Go regular expression syntax](https://golang.org/s/re2syntax).
|
||||||
|
|
||||||
|
The matchers are evaluated in an OR fashion, i.e. the identity is deemed to be
|
||||||
|
verified if any one matcher successfully matches against the identity.
|
||||||
|
|
||||||
Example of verifying HelmCharts signed by the
|
Example of verifying HelmCharts signed by the
|
||||||
[Cosign GitHub Action](https://github.com/sigstore/cosign-installer) with GitHub OIDC Token:
|
[Cosign GitHub Action](https://github.com/sigstore/cosign-installer) with GitHub OIDC Token:
|
||||||
|
|
||||||
|
|
@ -325,6 +339,9 @@ spec:
|
||||||
version: ">=6.1.6"
|
version: ">=6.1.6"
|
||||||
verify:
|
verify:
|
||||||
provider: cosign
|
provider: cosign
|
||||||
|
matchOIDCIdentity:
|
||||||
|
- issuer: "^https://token.actions.githubusercontent.com$"
|
||||||
|
subject: "^https://github.com/stefanprodan/podinfo.*$"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
|
|
@ -501,11 +501,13 @@ for more information.
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
`.spec.verify` is an optional field to enable the verification of [Cosign](https://github.com/sigstore/cosign)
|
`.spec.verify` is an optional field to enable the verification of [Cosign](https://github.com/sigstore/cosign)
|
||||||
signatures. The field offers two subfields:
|
signatures. The field offers three subfields:
|
||||||
|
|
||||||
- `.provider`, to specify the verification provider. Only supports `cosign` at present.
|
- `.provider`, to specify the verification provider. Only supports `cosign` at present.
|
||||||
- `.secretRef.name`, to specify a reference to a Secret in the same namespace as
|
- `.secretRef.name`, to specify a reference to a Secret in the same namespace as
|
||||||
the OCIRepository, containing the Cosign public keys of trusted authors.
|
the OCIRepository, containing the Cosign public keys of trusted authors.
|
||||||
|
- `.matchOIDCIdentity`, to specify a list of OIDC identity matchers. Please see
|
||||||
|
[Keyless verification](#keyless-verification) for more details.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
|
|
@ -555,6 +557,18 @@ For publicly available OCI artifacts, which are signed using the
|
||||||
[Cosign Keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) procedure,
|
[Cosign Keyless](https://github.com/sigstore/cosign/blob/main/KEYLESS.md) procedure,
|
||||||
you can enable the verification by omitting the `.verify.secretRef` field.
|
you can enable the verification by omitting the `.verify.secretRef` field.
|
||||||
|
|
||||||
|
To verify the identity's subject and the OIDC issuer present in the Fulcio
|
||||||
|
certificate, you can specify a list of OIDC identity matchers using
|
||||||
|
`.spec.verify.matchOIDCIdentity`. The matcher provides two required fields:
|
||||||
|
|
||||||
|
- `.issuer`, to specify a regexp that matches against the OIDC issuer.
|
||||||
|
- `.subject`, to specify a regexp that matches against the subject identity in
|
||||||
|
the certificate.
|
||||||
|
Both values should follow the [Go regular expression syntax](https://golang.org/s/re2syntax).
|
||||||
|
|
||||||
|
The matchers are evaluated in an OR fashion, i.e. the identity is deemed to be
|
||||||
|
verified if any one matcher successfully matches against the identity.
|
||||||
|
|
||||||
Example of verifying artifacts signed by the
|
Example of verifying artifacts signed by the
|
||||||
[Cosign GitHub Action](https://github.com/sigstore/cosign-installer) with GitHub OIDC Token:
|
[Cosign GitHub Action](https://github.com/sigstore/cosign-installer) with GitHub OIDC Token:
|
||||||
|
|
||||||
|
|
@ -568,6 +582,9 @@ spec:
|
||||||
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
||||||
verify:
|
verify:
|
||||||
provider: cosign
|
provider: cosign
|
||||||
|
matchOIDCIdentity:
|
||||||
|
- issuer: "^https://token.actions.githubusercontent.com$"
|
||||||
|
subject: "^https://github.com/stefanprodan/podinfo.*$"
|
||||||
```
|
```
|
||||||
|
|
||||||
The controller verifies the signatures using the Fulcio root CA and the Rekor
|
The controller verifies the signatures using the Fulcio root CA and the Rekor
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/sigstore/cosign/v2/pkg/cosign"
|
||||||
helmgetter "helm.sh/helm/v3/pkg/getter"
|
helmgetter "helm.sh/helm/v3/pkg/getter"
|
||||||
helmreg "helm.sh/helm/v3/pkg/registry"
|
helmreg "helm.sh/helm/v3/pkg/registry"
|
||||||
helmrepo "helm.sh/helm/v3/pkg/repo"
|
helmrepo "helm.sh/helm/v3/pkg/repo"
|
||||||
|
|
@ -1338,6 +1339,15 @@ func (r *HelmChartReconciler) makeVerifiers(ctx context.Context, obj *helmv1.Hel
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no secret is provided, add a keyless verifier
|
// if no secret is provided, add a keyless verifier
|
||||||
|
var identities []cosign.Identity
|
||||||
|
for _, match := range obj.Spec.Verify.MatchOIDCIdentity {
|
||||||
|
identities = append(identities, cosign.Identity{
|
||||||
|
IssuerRegExp: match.Issuer,
|
||||||
|
SubjectRegExp: match.Subject,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defaultCosignOciOpts = append(defaultCosignOciOpts, soci.WithIdentities(identities))
|
||||||
|
|
||||||
verifier, err := soci.NewCosignVerifier(ctx, defaultCosignOciOpts...)
|
verifier, err := soci.NewCosignVerifier(ctx, defaultCosignOciOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -2533,6 +2533,181 @@ func TestHelmChartReconciler_reconcileSourceFromOCI_authStrategy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelmChartRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
want sreconcile.Result
|
||||||
|
wantErr bool
|
||||||
|
beforeFunc func(obj *helmv1.HelmChart)
|
||||||
|
assertConditions []metav1.Condition
|
||||||
|
revision string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "signed image with no identity matching specified should pass verification",
|
||||||
|
version: "6.5.1",
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of version <version>"),
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:af589b918022cd8d85a4543312d28170c2e894ccab8484050ff4bdefdde30b4e",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with correct subject and issuer should pass verification",
|
||||||
|
version: "6.5.1",
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []helmv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
|
||||||
|
Subject: "^https://github.com/stefanprodan/podinfo.*$",
|
||||||
|
Issuer: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of version <version>"),
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:af589b918022cd8d85a4543312d28170c2e894ccab8484050ff4bdefdde30b4e",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with incorrect and correct identity matchers should pass verification",
|
||||||
|
version: "6.5.1",
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []helmv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
Subject: "intruder",
|
||||||
|
Issuer: "^https://honeypot.com$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
Subject: "^https://github.com/stefanprodan/podinfo.*$",
|
||||||
|
Issuer: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of version <version>"),
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: pulled '<name>' chart with version '<version>'"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:af589b918022cd8d85a4543312d28170c2e894ccab8484050ff4bdefdde30b4e",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with incorrect subject and issuer should not pass verification",
|
||||||
|
version: "6.5.1",
|
||||||
|
wantErr: true,
|
||||||
|
want: sreconcile.ResultEmpty,
|
||||||
|
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []helmv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
Subject: "intruder",
|
||||||
|
Issuer: "^https://honeypot.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures: none of the expected identities matched what was in the certificate"),
|
||||||
|
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "chart verification error: failed to verify <url>: no matching signatures"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:af589b918022cd8d85a4543312d28170c2e894ccab8484050ff4bdefdde30b4e",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsigned image should not pass verification",
|
||||||
|
version: "6.1.0",
|
||||||
|
wantErr: true,
|
||||||
|
want: sreconcile.ResultEmpty,
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(sourcev1.BuildFailedCondition, "ChartVerificationError", "chart verification error: failed to verify <url>: no matching signatures"),
|
||||||
|
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "chart verification error: failed to verify <url>: no matching signatures"),
|
||||||
|
},
|
||||||
|
revision: "6.1.0@sha256:642383f56ccb529e3f658d40312d01b58d9bc6caeef653da43e58d1afe88982a",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
clientBuilder := fakeclient.NewClientBuilder()
|
||||||
|
|
||||||
|
repository := &helmv1.HelmRepository{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "helmrepository-",
|
||||||
|
},
|
||||||
|
Spec: helmv1.HelmRepositorySpec{
|
||||||
|
URL: "oci://ghcr.io/stefanprodan/charts",
|
||||||
|
Timeout: &metav1.Duration{Duration: timeout},
|
||||||
|
Provider: helmv1.GenericOCIProvider,
|
||||||
|
Type: helmv1.HelmRepositoryTypeOCI,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
clientBuilder.WithObjects(repository)
|
||||||
|
|
||||||
|
r := &HelmChartReconciler{
|
||||||
|
Client: clientBuilder.Build(),
|
||||||
|
EventRecorder: record.NewFakeRecorder(32),
|
||||||
|
Getters: testGetters,
|
||||||
|
Storage: testStorage,
|
||||||
|
RegistryClientGenerator: registry.ClientGenerator,
|
||||||
|
patchOptions: getPatchOptions(helmChartReadyCondition.Owned, "sc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
obj := &helmv1.HelmChart{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "helmchart-",
|
||||||
|
},
|
||||||
|
Spec: helmv1.HelmChartSpec{
|
||||||
|
SourceRef: helmv1.LocalHelmChartSourceReference{
|
||||||
|
Kind: helmv1.HelmRepositoryKind,
|
||||||
|
Name: repository.Name,
|
||||||
|
},
|
||||||
|
Version: tt.version,
|
||||||
|
Chart: "podinfo",
|
||||||
|
Verify: &helmv1.OCIRepositoryVerification{
|
||||||
|
Provider: "cosign",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
chartUrl := fmt.Sprintf("%s/%s:%s", repository.Spec.URL, obj.Spec.Chart, obj.Spec.Version)
|
||||||
|
|
||||||
|
assertConditions := tt.assertConditions
|
||||||
|
for k := range assertConditions {
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<name>", obj.Spec.Chart)
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<version>", obj.Spec.Version)
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", chartUrl)
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.beforeFunc != nil {
|
||||||
|
tt.beforeFunc(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Expect(r.Client.Create(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
defer func() {
|
||||||
|
g.Expect(r.Client.Delete(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
}()
|
||||||
|
|
||||||
|
sp := patch.NewSerialPatcher(obj, r.Client)
|
||||||
|
|
||||||
|
var b chart.Build
|
||||||
|
got, err := r.reconcileSource(ctx, sp, obj, &b)
|
||||||
|
if tt.wantErr {
|
||||||
|
g.Expect(err).To(HaveOccurred())
|
||||||
|
} else {
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
g.Expect(got).To(Equal(tt.want))
|
||||||
|
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T) {
|
func TestHelmChartReconciler_reconcileSourceFromOCI_verifySignature(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/google/go-containerregistry/pkg/name"
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
|
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
|
"github.com/sigstore/cosign/v2/pkg/cosign"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
@ -663,6 +664,16 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
|
||||||
|
|
||||||
// if no secret is provided, try keyless verification
|
// if no secret is provided, try keyless verification
|
||||||
ctrl.LoggerFrom(ctx).Info("no secret reference is provided, trying to verify the image using keyless method")
|
ctrl.LoggerFrom(ctx).Info("no secret reference is provided, trying to verify the image using keyless method")
|
||||||
|
|
||||||
|
var identities []cosign.Identity
|
||||||
|
for _, match := range obj.Spec.Verify.MatchOIDCIdentity {
|
||||||
|
identities = append(identities, cosign.Identity{
|
||||||
|
IssuerRegExp: match.Issuer,
|
||||||
|
SubjectRegExp: match.Subject,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defaultCosignOciOpts = append(defaultCosignOciOpts, soci.WithIdentities(identities))
|
||||||
|
|
||||||
verifier, err := soci.NewCosignVerifier(ctxTimeout, defaultCosignOciOpts...)
|
verifier, err := soci.NewCosignVerifier(ctxTimeout, defaultCosignOciOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -1435,6 +1435,181 @@ func TestOCIRepository_reconcileSource_verifyOCISourceSignature(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOCIRepository_reconcileSource_verifyOCISourceSignature_keyless(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
reference *ociv1.OCIRepositoryRef
|
||||||
|
want sreconcile.Result
|
||||||
|
wantErr bool
|
||||||
|
wantErrMsg string
|
||||||
|
beforeFunc func(obj *ociv1.OCIRepository)
|
||||||
|
assertConditions []metav1.Condition
|
||||||
|
revision string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "signed image with no identity matching specified should pass verification",
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
Tag: "6.5.1",
|
||||||
|
},
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision <revision>"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:049fff8f9c92abba8615c6c3dcf9d10d30082213f6fe86c9305257f806c31e31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with correct subject and issuer should pass verification",
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
Tag: "6.5.1",
|
||||||
|
},
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
beforeFunc: func(obj *ociv1.OCIRepository) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []ociv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
|
||||||
|
Subject: "^https://github.com/stefanprodan/podinfo.*$",
|
||||||
|
Issuer: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision <revision>"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:049fff8f9c92abba8615c6c3dcf9d10d30082213f6fe86c9305257f806c31e31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with both incorrect and correct identity matchers should pass verification",
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
Tag: "6.5.1",
|
||||||
|
},
|
||||||
|
want: sreconcile.ResultSuccess,
|
||||||
|
beforeFunc: func(obj *ociv1.OCIRepository) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []ociv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
Subject: "intruder",
|
||||||
|
Issuer: "^https://honeypot.com$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
|
Subject: "^https://github.com/stefanprodan/podinfo.*$",
|
||||||
|
Issuer: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.TrueCondition(sourcev1.SourceVerifiedCondition, meta.SucceededReason, "verified signature of revision <revision>"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:049fff8f9c92abba8615c6c3dcf9d10d30082213f6fe86c9305257f806c31e31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "signed image with incorrect subject and issuer should not pass verification",
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
Tag: "6.5.1",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
want: sreconcile.ResultEmpty,
|
||||||
|
beforeFunc: func(obj *ociv1.OCIRepository) {
|
||||||
|
obj.Spec.Verify.MatchOIDCIdentity = []ociv1.OIDCIdentityMatch{
|
||||||
|
{
|
||||||
|
Subject: "intruder",
|
||||||
|
Issuer: "^https://honeypot.com$",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "failed to verify the signature using provider '<provider> keyless': no matching signatures: none of the expected identities matched what was in the certificate"),
|
||||||
|
},
|
||||||
|
revision: "6.5.1@sha256:049fff8f9c92abba8615c6c3dcf9d10d30082213f6fe86c9305257f806c31e31",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unsigned image should not pass verification",
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
Tag: "6.1.0",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
want: sreconcile.ResultEmpty,
|
||||||
|
assertConditions: []metav1.Condition{
|
||||||
|
*conditions.TrueCondition(meta.ReconcilingCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.UnknownCondition(meta.ReadyCondition, meta.ProgressingReason, "building artifact: new revision '<revision>' for '<url>'"),
|
||||||
|
*conditions.FalseCondition(sourcev1.SourceVerifiedCondition, sourcev1.VerificationError, "failed to verify the signature using provider '<provider> keyless': no matching signatures"),
|
||||||
|
},
|
||||||
|
revision: "6.1.0@sha256:3816fe9636a297f0c934b1fa0f46fe4c068920375536ac2803604adfb4c55894",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
clientBuilder := fakeclient.NewClientBuilder().
|
||||||
|
WithScheme(testEnv.GetScheme()).
|
||||||
|
WithStatusSubresource(&ociv1.OCIRepository{})
|
||||||
|
|
||||||
|
r := &OCIRepositoryReconciler{
|
||||||
|
Client: clientBuilder.Build(),
|
||||||
|
EventRecorder: record.NewFakeRecorder(32),
|
||||||
|
Storage: testStorage,
|
||||||
|
patchOptions: getPatchOptions(ociRepositoryReadyCondition.Owned, "sc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
obj := &ociv1.OCIRepository{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
GenerateName: "verify-oci-source-signature-",
|
||||||
|
Generation: 1,
|
||||||
|
},
|
||||||
|
Spec: ociv1.OCIRepositorySpec{
|
||||||
|
URL: "oci://ghcr.io/stefanprodan/manifests/podinfo",
|
||||||
|
Verify: &ociv1.OCIRepositoryVerification{
|
||||||
|
Provider: "cosign",
|
||||||
|
},
|
||||||
|
Interval: metav1.Duration{Duration: interval},
|
||||||
|
Timeout: &metav1.Duration{Duration: timeout},
|
||||||
|
Reference: tt.reference,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url := strings.TrimPrefix(obj.Spec.URL, "oci://") + ":" + tt.reference.Tag
|
||||||
|
|
||||||
|
assertConditions := tt.assertConditions
|
||||||
|
for k := range assertConditions {
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<revision>", tt.revision)
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<url>", url)
|
||||||
|
assertConditions[k].Message = strings.ReplaceAll(assertConditions[k].Message, "<provider>", "cosign")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.beforeFunc != nil {
|
||||||
|
tt.beforeFunc(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Expect(r.Client.Create(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
defer func() {
|
||||||
|
g.Expect(r.Client.Delete(ctx, obj)).ToNot(HaveOccurred())
|
||||||
|
}()
|
||||||
|
|
||||||
|
sp := patch.NewSerialPatcher(obj, r.Client)
|
||||||
|
|
||||||
|
artifact := &sourcev1.Artifact{}
|
||||||
|
got, err := r.reconcileSource(ctx, sp, obj, artifact, t.TempDir())
|
||||||
|
if tt.wantErr {
|
||||||
|
g.Expect(err).To(HaveOccurred())
|
||||||
|
tt.wantErrMsg = strings.ReplaceAll(tt.wantErrMsg, "<url>", url)
|
||||||
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErrMsg))
|
||||||
|
} else {
|
||||||
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
g.Expect(got).To(Equal(tt.want))
|
||||||
|
g.Expect(obj.Status.Conditions).To(conditions.MatchConditions(tt.assertConditions))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOCIRepository_reconcileSource_noop(t *testing.T) {
|
func TestOCIRepository_reconcileSource_noop(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,9 @@ type Verifier interface {
|
||||||
|
|
||||||
// options is a struct that holds options for verifier.
|
// options is a struct that holds options for verifier.
|
||||||
type options struct {
|
type options struct {
|
||||||
PublicKey []byte
|
PublicKey []byte
|
||||||
ROpt []remote.Option
|
ROpt []remote.Option
|
||||||
|
Identities []cosign.Identity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options is a function that configures the options applied to a Verifier.
|
// Options is a function that configures the options applied to a Verifier.
|
||||||
|
|
@ -62,6 +63,14 @@ func WithRemoteOptions(opts ...remote.Option) Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithIdentities specifies the identity matchers that have to be met
|
||||||
|
// for the signature to be deemed valid.
|
||||||
|
func WithIdentities(identities []cosign.Identity) Options {
|
||||||
|
return func(opts *options) {
|
||||||
|
opts.Identities = identities
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CosignVerifier is a struct which is responsible for executing verification logic.
|
// CosignVerifier is a struct which is responsible for executing verification logic.
|
||||||
type CosignVerifier struct {
|
type CosignVerifier struct {
|
||||||
opts *cosign.CheckOpts
|
opts *cosign.CheckOpts
|
||||||
|
|
@ -82,6 +91,7 @@ func NewCosignVerifier(ctx context.Context, opts ...Options) (*CosignVerifier, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkOpts.Identities = o.Identities
|
||||||
if o.ROpt != nil {
|
if o.ROpt != nil {
|
||||||
co = append(co, ociremote.WithRemoteOptions(o.ROpt...))
|
co = append(co, ociremote.WithRemoteOptions(o.ROpt...))
|
||||||
}
|
}
|
||||||
|
|
@ -141,17 +151,7 @@ func NewCosignVerifier(ctx context.Context, opts ...Options) (*CosignVerifier, e
|
||||||
|
|
||||||
// VerifyImageSignatures verify the authenticity of the given ref OCI image.
|
// VerifyImageSignatures verify the authenticity of the given ref OCI image.
|
||||||
func (v *CosignVerifier) VerifyImageSignatures(ctx context.Context, ref name.Reference) ([]oci.Signature, bool, error) {
|
func (v *CosignVerifier) VerifyImageSignatures(ctx context.Context, ref name.Reference) ([]oci.Signature, bool, error) {
|
||||||
opts := v.opts
|
return cosign.VerifyImageSignatures(ctx, ref, v.opts)
|
||||||
|
|
||||||
// TODO: expose the match conditions in the CRD
|
|
||||||
opts.Identities = []cosign.Identity{
|
|
||||||
{
|
|
||||||
IssuerRegExp: ".*",
|
|
||||||
SubjectRegExp: ".*",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return cosign.VerifyImageSignatures(ctx, ref, opts)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify verifies the authenticity of the given ref OCI image.
|
// Verify verifies the authenticity of the given ref OCI image.
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/google/go-containerregistry/pkg/authn"
|
"github.com/google/go-containerregistry/pkg/authn"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/remote"
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
||||||
|
"github.com/sigstore/cosign/v2/pkg/cosign"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOptions(t *testing.T) {
|
func TestOptions(t *testing.T) {
|
||||||
|
|
@ -75,6 +76,30 @@ func TestOptions(t *testing.T) {
|
||||||
remote.WithTransport(http.DefaultTransport),
|
remote.WithTransport(http.DefaultTransport),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
name: "identities option",
|
||||||
|
opts: []Options{WithIdentities([]cosign.Identity{
|
||||||
|
{
|
||||||
|
SubjectRegExp: "test-user",
|
||||||
|
IssuerRegExp: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubjectRegExp: "dev-user",
|
||||||
|
IssuerRegExp: "^https://accounts.google.com$",
|
||||||
|
},
|
||||||
|
})},
|
||||||
|
want: &options{
|
||||||
|
Identities: []cosign.Identity{
|
||||||
|
{
|
||||||
|
SubjectRegExp: "test-user",
|
||||||
|
IssuerRegExp: "^https://token.actions.githubusercontent.com$",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
SubjectRegExp: "dev-user",
|
||||||
|
IssuerRegExp: "^https://accounts.google.com$",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue