Introduce Initial OCIRepository Source Verification

Fixes #863

Signed-off-by: Furkan <furkan.turkal@trendyol.com>
Co-authored-by: Batuhan <batuhan.apaydin@trendyol.com>
Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>
This commit is contained in:
Furkan 2022-08-26 19:13:19 +03:00 committed by Stefan Prodan
parent 54d706a226
commit 697f260dba
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
15 changed files with 1548 additions and 67 deletions

View File

@ -9,6 +9,7 @@ on:
push:
branches:
- main
- feature/863
permissions:
contents: read # for actions/checkout to fetch code

View File

@ -71,6 +71,10 @@ const (
// required fields, or the provided credentials do not match.
AuthenticationFailedReason string = "AuthenticationFailed"
// VerificationError signals that the Source's verification
// check failed.
VerificationError string = "VerificationError"
// DirCreationFailedReason signals a failure caused by a directory creation
// operation.
DirCreationFailedReason string = "DirectoryCreationFailed"

View File

@ -78,6 +78,12 @@ type OCIRepositorySpec struct {
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef,omitempty"`
// Verify contains the secret name containing the trusted public keys
// used to verify the signature and specifies which provider to use to check
// whether OCI image is authentic.
// +optional
Verify *OCIRepositoryVerification `json:"verify,omitempty"`
// ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate
// the image pull if the service account has attached pull secrets. For more information:
// https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account
@ -156,11 +162,13 @@ type OCILayerSelector struct {
type OCIRepositoryVerification struct {
// Provider specifies the technology used to sign the OCI Artifact.
// +kubebuilder:validation:Enum=cosign
// +kubebuilder:default:=cosign
Provider string `json:"provider"`
// SecretRef specifies the Kubernetes Secret containing the
// trusted public keys.
SecretRef meta.LocalObjectReference `json:"secretRef"`
// +optional
SecretRef *meta.LocalObjectReference `json:"secretRef"`
}
// OCIRepositoryStatus defines the observed state of OCIRepository

View File

@ -729,6 +729,11 @@ func (in *OCIRepositorySpec) DeepCopyInto(out *OCIRepositorySpec) {
*out = new(meta.LocalObjectReference)
**out = **in
}
if in.Verify != nil {
in, out := &in.Verify, &out.Verify
*out = new(OCIRepositoryVerification)
(*in).DeepCopyInto(*out)
}
if in.CertSecretRef != nil {
in, out := &in.CertSecretRef, &out.CertSecretRef
*out = new(meta.LocalObjectReference)
@ -788,7 +793,11 @@ func (in *OCIRepositoryStatus) DeepCopy() *OCIRepositoryStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OCIRepositoryVerification) DeepCopyInto(out *OCIRepositoryVerification) {
*out = *in
out.SecretRef = in.SecretRef
if in.SecretRef != nil {
in, out := &in.SecretRef, &out.SecretRef
*out = new(meta.LocalObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIRepositoryVerification.

View File

@ -148,6 +148,31 @@ spec:
on a remote container registry.
pattern: ^oci://.*$
type: string
verify:
description: Verify contains the secret name containing the trusted
public keys used to verify the signature and specifies which provider
to use to check whether OCI image is authentic.
properties:
provider:
default: cosign
description: Provider specifies the technology used to sign the
OCI Artifact.
enum:
- cosign
type: string
secretRef:
description: SecretRef specifies the Kubernetes Secret containing
the trusted public keys.
properties:
name:
description: Name of the referent.
type: string
required:
- name
type: object
required:
- provider
type: object
required:
- interval
- url

View File

@ -51,6 +51,8 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: TUF_ROOT
value: "/tmp/.sigstore"
args:
- --watch-all-namespaces
- --log-level=info

View File

@ -0,0 +1,14 @@
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: podinfo-deploy-signed-with-key
spec:
interval: 5m
url: oci://ghcr.io/stefanprodan/podinfo-deploy
ref:
semver: "6.2.x"
verify:
provider: cosign
secretRef:
name: cosign-key

View File

@ -0,0 +1,12 @@
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: podinfo-deploy-signed-with-keyless
spec:
interval: 5m
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
semver: "6.2.x"
verify:
provider: cosign

View File

@ -28,6 +28,8 @@ import (
"strings"
"time"
soci "github.com/fluxcd/source-controller/internal/oci"
"github.com/Masterminds/semver/v3"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/k8schain"
@ -408,6 +410,20 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
// Extract the content of the first artifact layer
if !obj.GetArtifact().HasRevision(revision) {
if obj.Spec.Verify != nil {
provider := obj.Spec.Verify.Provider
err := r.verifyOCISourceSignature(ctx, obj, url, keychain)
if err != nil {
e := serror.NewGeneric(
fmt.Errorf("failed to verify OCI image signature '%s' using provider '%s': %w", url, provider, err),
sourcev1.VerificationError,
)
conditions.MarkFalse(obj, sourcev1.SourceVerifiedCondition, e.Reason, e.Err.Error())
return sreconcile.ResultEmpty, e
}
conditions.MarkTrue(obj, sourcev1.SourceVerifiedCondition, meta.SucceededReason, "OCI image %s with digest %s verified.", url, imgDigest)
}
layers, err := img.Layers()
if err != nil {
e := serror.NewGeneric(
@ -484,6 +500,90 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
return sreconcile.ResultSuccess, nil
}
// verifyOCISourceSignature verifies the authenticity of the given image reference url. First, it tries to keyful approach
// by looking at whether the given secret exists. Then, if it does not exist, it pushes a keyless approach for verification.
func (r *OCIRepositoryReconciler) verifyOCISourceSignature(ctx context.Context, obj *sourcev1.OCIRepository, url string, keychain authn.Keychain) error {
// Verify the image
if obj.Spec.Verify != nil {
provider := obj.Spec.Verify.Provider
switch provider {
case "cosign":
// get the public keys from the given secret
secretRef := obj.Spec.Verify.SecretRef
defaultCosignOciOpts := []soci.Options{
soci.WithAuthnKeychain(keychain),
soci.WithContext(ctx),
}
ref, err := name.ParseReference(url)
if err != nil {
return err
}
if secretRef != nil {
certSecretName := types.NamespacedName{
Namespace: obj.Namespace,
Name: secretRef.Name,
}
var pubSecret corev1.Secret
if err := r.Get(ctx, certSecretName, &pubSecret); err != nil {
return err
}
signatureVerified := false
// traverse all public keys and try to verify the signature
// this is brute-force approach, but it is ok for now
for k, data := range pubSecret.Data {
// search for public keys in the secret
if strings.HasSuffix(k, ".pub") {
verifier, err := soci.New(append(defaultCosignOciOpts, soci.WithPublicKey(data))...)
if err != nil {
return err
}
signatures, _, err := verifier.VerifyImageSignatures(ctx, ref)
if err != nil {
continue
}
if signatures != nil {
signatureVerified = true
break
}
}
}
if !signatureVerified {
ctrl.LoggerFrom(ctx).Error(err, "none of the keys in the secret %s succeeded to verify for the image %s", secretRef.Name)
return fmt.Errorf("no matching signatures were found for the image %s", url)
}
return nil
} else {
ctrl.LoggerFrom(ctx).Info("no secret reference is provided, trying to verify the image using keyless approach")
verifier, err := soci.New(defaultCosignOciOpts...)
if err != nil {
return err
}
signatures, _, err := verifier.VerifyImageSignatures(ctx, ref)
if err != nil {
return err
}
if len(signatures) > 0 {
return nil
}
}
return nil
}
}
return nil
}
// parseRepositoryURL validates and extracts the repository URL.
func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository) (string, error) {
if !strings.HasPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix) {
@ -651,7 +751,6 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *sourcev1.O
tlsConfig.RootCAs = syscerts
}
return transport, nil
}
// oidcAuth generates the OIDC credential authenticator based on the specified cloud provider.
@ -883,7 +982,8 @@ func (r *OCIRepositoryReconciler) garbageCollect(ctx context.Context, obj *sourc
// that this is a simple log. While the debug log contains complete details
// about the event.
func (r *OCIRepositoryReconciler) eventLogf(ctx context.Context,
obj runtime.Object, eventType string, reason string, messageFmt string, args ...interface{}) {
obj runtime.Object, eventType, reason, messageFmt string, args ...interface{},
) {
msg := fmt.Sprintf(messageFmt, args...)
// Log and emit event.
if eventType == corev1.EventTypeWarning {

View File

@ -24,6 +24,9 @@ import (
"encoding/pem"
"errors"
"fmt"
coptions "github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/cmd/cosign/cli/sign"
"github.com/sigstore/cosign/pkg/cosign"
"math/big"
"net"
"net/http"
@ -1213,6 +1216,128 @@ func TestOCIRepository_getArtifactURL(t *testing.T) {
}
}
func TestOCIRepository_verifyOCISourceSignature(t *testing.T) {
g := NewWithT(t)
tmpDir := t.TempDir()
regServer, err := setupRegistryServer(ctx, tmpDir, registryOptions{})
g.Expect(err).ToNot(HaveOccurred())
_, err = pushMultiplePodinfoImages(regServer.registryHost, "6.1.4", "6.1.5", "6.1.6")
g.Expect(err).ToNot(HaveOccurred())
tests := []struct {
name string
url string
reference *sourcev1.OCIRepositoryRef
shouldSign bool
wantErr bool
}{
{
name: "signed image should pass verification",
reference: &sourcev1.OCIRepositoryRef{
Tag: "6.1.4",
},
shouldSign: true,
},
{
name: "unsigned image should not pass verification",
reference: &sourcev1.OCIRepositoryRef{
Tag: "6.1.5",
},
shouldSign: false,
},
}
builder := fakeclient.NewClientBuilder().WithScheme(testEnv.GetScheme())
r := &OCIRepositoryReconciler{
Client: builder.Build(),
EventRecorder: record.NewFakeRecorder(32),
Storage: testStorage,
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
obj := &sourcev1.OCIRepository{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "artifact-url-",
},
Spec: sourcev1.OCIRepositorySpec{
URL: fmt.Sprintf("oci://%s/podinfo", regServer.registryHost),
Reference: tt.reference,
Verify: &sourcev1.OCIRepositoryVerification{
Provider: "cosign",
SecretRef: &meta.LocalObjectReference{Name: "cosign-key"}},
Interval: metav1.Duration{Duration: interval},
Timeout: &metav1.Duration{Duration: timeout},
},
}
pf := func(b bool) ([]byte, error) {
return []byte("foo"), nil
}
keys, err := cosign.GenerateKeyPair(pf)
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
err = os.WriteFile("cosign.key", keys.PrivateBytes, 0600)
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "cosign-key",
},
Data: map[string][]byte{
"cosign.pub": keys.PublicBytes,
}}
err = r.Create(ctx, secret)
if err != nil {
g.Expect(err).NotTo(HaveOccurred())
}
keychain, err := r.keychain(ctx, obj)
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
options := r.craneOptions(ctx, obj.Spec.Insecure)
options = append(options, crane.WithAuthFromKeychain(keychain))
url, err := r.getArtifactURL(obj, options)
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
if tt.shouldSign {
ko := coptions.KeyOpts{
KeyRef: "cosign.key",
PassFunc: pf,
}
t.Logf("url: %s", url)
ro := &coptions.RootOptions{}
err = sign.SignCmd(ro, ko, coptions.RegistryOptions{Keychain: keychain}, nil, []string{url}, "", "", false, "", "", "", false, false, "", false)
if err != nil {
g.Expect(err).ToNot(HaveOccurred())
}
}
err = r.verifyOCISourceSignature(ctx, obj, url, keychain)
if tt.wantErr {
g.Expect(err).To(HaveOccurred())
return
}
})
}
}
func TestOCIRepository_stalled(t *testing.T) {
g := NewWithT(t)

View File

@ -1028,6 +1028,22 @@ The secret must be of type kubernetes.io/dockerconfigjson.</p>
</tr>
<tr>
<td>
<code>verify</code><br>
<em>
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositoryVerification">
OCIRepositoryVerification
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check
whether OCI image is authentic.</p>
</td>
</tr>
<tr>
<td>
<code>serviceAccountName</code><br>
<em>
string
@ -2772,6 +2788,22 @@ The secret must be of type kubernetes.io/dockerconfigjson.</p>
</tr>
<tr>
<td>
<code>verify</code><br>
<em>
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositoryVerification">
OCIRepositoryVerification
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>Verify contains the secret name containing the trusted public keys
used to verify the signature and specifies which provider to use to check
whether OCI image is authentic.</p>
</td>
</tr>
<tr>
<td>
<code>serviceAccountName</code><br>
<em>
string
@ -2967,6 +2999,10 @@ github.com/fluxcd/pkg/apis/meta.ReconcileRequestStatus
</div>
<h3 id="source.toolkit.fluxcd.io/v1beta2.OCIRepositoryVerification">OCIRepositoryVerification
</h3>
<p>
(<em>Appears on:</em>
<a href="#source.toolkit.fluxcd.io/v1beta2.OCIRepositorySpec">OCIRepositorySpec</a>)
</p>
<p>OCIRepositoryVerification verifies the authenticity of an OCI Artifact</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
@ -2999,6 +3035,7 @@ github.com/fluxcd/pkg/apis/meta.LocalObjectReference
</em>
</td>
<td>
<em>(Optional)</em>
<p>SecretRef specifies the Kubernetes Secret containing the
trusted public keys.</p>
</td>

165
go.mod
View File

@ -58,6 +58,8 @@ require (
github.com/otiai10/copy v1.7.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/prometheus/client_golang v1.13.0
github.com/sigstore/cosign v1.11.1
github.com/sigstore/sigstore v1.4.0
github.com/spf13/pflag v1.0.5
golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503
golang.org/x/net v0.0.0-20220822230855-b0a4917ee28c
@ -78,14 +80,15 @@ require (
replace github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.16.0+incompatible
require (
cloud.google.com/go v0.102.1 // indirect
bitbucket.org/creachadair/shell v0.0.7 // indirect
cloud.google.com/go v0.103.0 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest v0.11.28 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
@ -100,33 +103,47 @@ require (
github.com/Masterminds/squirrel v1.5.3 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.44.84 // indirect
github.com/aws/aws-sdk-go-v2 v1.16.7 // indirect
github.com/aws/aws-sdk-go-v2/config v1.15.14 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.9 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2 v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/config v1.17.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.12.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.17.8 // indirect
github.com/aws/aws-sdk-go-v2/service/ecrpublic v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.9 // indirect
github.com/aws/smithy-go v1.12.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 // indirect
github.com/aws/smithy-go v1.12.1 // indirect
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220706184558-ce46abcd012b // indirect
github.com/benbjohnson/clock v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/bshuster-repo/logrus-logstash-hook v1.0.2 // indirect
github.com/bugsnag/bugsnag-go v2.1.2+incompatible // indirect
github.com/bugsnag/panicwrap v1.3.4 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/containerd/containerd v1.6.6 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
github.com/coreos/go-oidc/v3 v3.2.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20210823021906-dc406ceaf94b // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/docker/distribution v2.8.1+incompatible // indirect
@ -139,47 +156,80 @@ require (
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fullstorydev/grpcurl v1.8.6 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.2.3 // indirect
github.com/go-openapi/analysis v0.21.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.21.1 // indirect
github.com/go-openapi/loads v0.21.2 // indirect
github.com/go-openapi/runtime v0.24.1 // indirect
github.com/go-openapi/spec v0.20.7 // indirect
github.com/go-openapi/strfmt v0.21.3 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/validate v0.22.0 // indirect
github.com/go-piv/piv-go v1.10.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gofrs/uuid v4.2.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v1.8.2 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/certificate-transparency-go v1.1.3 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/go-containerregistry/pkg/authn/kubernetes v0.0.0-20220719135131-f79ec2192282 // indirect
github.com/google/go-github/v45 v45.2.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/trillian v1.4.1 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/in-toto/in-toto-golang v0.3.4-0.20220709202702-fa494aaa0add // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect
github.com/jhump/protoreflect v1.12.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmoiron/sqlx v1.3.5 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
@ -189,18 +239,23 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/letsencrypt/boulder v0.0.0-20220723181115-27de4befb95e // indirect
github.com/lib/pq v1.10.6 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.2.0 // indirect
@ -210,8 +265,13 @@ require (
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.3-0.20220729202839-6ad7100eb087 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
@ -219,42 +279,94 @@ require (
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/rubenv/sql-migrate v1.1.2 // indirect
github.com/russross/blackfriday v1.6.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sassoftware/relic v0.0.0-20210427151427-dfb082b79b74 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.4.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sigstore/fulcio v0.5.3 // indirect
github.com/sigstore/rekor v0.11.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.12.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.1.1 // indirect
github.com/stretchr/testify v1.8.0 // indirect
github.com/subosito/gotenv v1.3.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tent/canonical-json-go v0.0.0-20130607151641-96e4ba3a7613 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
github.com/theupdateframework/go-tuf v0.3.1 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect
github.com/transparency-dev/merkle v0.0.1 // indirect
github.com/urfave/cli v1.22.7 // indirect
github.com/vbatts/tar-split v0.11.2 // indirect
github.com/xanzy/go-gitlab v0.73.1 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940 // indirect
github.com/yvasiyarov/gorelic v0.0.7 // indirect
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect
github.com/zeebo/errs v1.2.2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/client/v2 v2.306.0-alpha.0 // indirect
go.etcd.io/etcd/client/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/etcdctl/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/etcdutl/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/pkg/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/raft/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/server/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/tests/v3 v3.6.0-alpha.0 // indirect
go.etcd.io/etcd/v3 v3.6.0-alpha.0 // indirect
go.mongodb.org/mongo-driver v1.10.1 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect
go.opentelemetry.io/otel v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.7.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.7.0 // indirect
go.opentelemetry.io/otel/sdk v1.7.0 // indirect
go.opentelemetry.io/otel/trace v1.7.0 // indirect
go.opentelemetry.io/proto/otlp v0.16.0 // indirect
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
golang.org/x/text v0.3.8-0.20211004125949-5bd84dd9b33b // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/tools v0.1.12 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect
google.golang.org/genproto v0.0.0-20220805133916-01dd62135a58 // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
@ -269,5 +381,6 @@ require (
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/kustomize/api v0.11.4 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.6 // indirect
sigs.k8s.io/release-utils v0.7.3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
)

961
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -165,3 +165,12 @@ echo "Run HelmChart from OCI registry tests"
kubectl -n source-system apply -f "${ROOT_DIR}/config/testdata/helmchart-from-oci/source.yaml"
kubectl -n source-system wait helmrepository/podinfo --for=condition=ready --timeout=1m
kubectl -n source-system wait helmchart/podinfo --for=condition=ready --timeout=1m
echo "Run OCIRepository verify tests"
kubectl -n source-system apply -f "${ROOT_DIR}/config/testdata/ocirepository/signed-with-key.yaml"
kubectl -n source-system apply -f "${ROOT_DIR}/config/testdata/ocirepository/signed-with-keyless.yaml"
curl -sSLo cosign.pub https://raw.githubusercontent.com/stefanprodan/podinfo/master/.cosign/cosign.pub
kubectl -n source-system create secret generic cosign-key --from-file=cosign.pub --dry-run=client -o yaml | kubectl apply -f -
kubectl -n source-system wait ocirepository/podinfo-deploy-signed-with-key --for=condition=ready --timeout=1m
kubectl -n source-system wait ocirepository/podinfo-deploy-signed-with-keyless --for=condition=ready --timeout=1m

133
internal/oci/oci.go Normal file
View File

@ -0,0 +1,133 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package oci
import (
"context"
"crypto"
"fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/rekor"
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
"github.com/google/go-containerregistry/pkg/name"
coptions "github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
)
// options is a struct that holds options for verifier.
type options struct {
PublicKey []byte
Keychain authn.Keychain
Context context.Context
}
// Options is a function that configures the options applied to a Verifier.
type Options func(opts *options)
// WithPublicKey sets the public key.
func WithPublicKey(publicKey []byte) Options {
return func(opts *options) {
opts.PublicKey = publicKey
}
}
func WithAuthnKeychain(keychain authn.Keychain) Options {
return func(opts *options) {
opts.Keychain = keychain
}
}
func WithContext(ctx context.Context) Options {
return func(opts *options) {
opts.Context = ctx
}
}
// Verifier is a struct which is responsible for executing verification logic.
type Verifier struct {
opts *cosign.CheckOpts
}
// New initializes a new Verifier.
func New(opts ...Options) (*Verifier, error) {
o := options{}
for _, opt := range opts {
opt(&o)
}
checkOpts := &cosign.CheckOpts{}
ro := coptions.RegistryOptions{}
co, err := ro.ClientOpts(o.Context)
if err != nil {
return nil, err
}
if o.Keychain != nil {
co = append(co, ociremote.WithRemoteOptions(remote.WithAuthFromKeychain(o.Keychain)))
}
checkOpts.RegistryClientOpts = co
// If a public key is provided, it will use it to verify the signature.
// If there is no public key provided, it will try keyless verification.
// https://github.com/sigstore/cosign/blob/main/KEYLESS.md.
if len(o.PublicKey) > 0 {
pubKeyRaw, err := cryptoutils.UnmarshalPEMToPublicKey(o.PublicKey)
if err != nil {
return nil, err
}
checkOpts.SigVerifier, err = signature.LoadVerifier(pubKeyRaw, crypto.SHA256)
if err != nil {
return nil, err
}
} else {
rcerts, err := fulcio.GetRoots()
if err != nil {
return nil, fmt.Errorf("unable to get Fulcio root certs: %w", err)
}
checkOpts.RootCerts = rcerts
icerts, err := fulcio.GetIntermediates()
if err != nil {
return nil, fmt.Errorf("unable to get Fulcio intermediate certs: %w", err)
}
checkOpts.IntermediateCerts = icerts
rc, err := rekor.NewClient(coptions.DefaultRekorURL)
if err != nil {
return nil, fmt.Errorf("unable to create Rekor client: %w", err)
}
checkOpts.RekorClient = rc
}
return &Verifier{
opts: checkOpts,
}, nil
}
// VerifyImageSignatures verify the authenticity of the given ref OCI image.
func (v *Verifier) VerifyImageSignatures(ctx context.Context, ref name.Reference) ([]oci.Signature, bool, error) {
return cosign.VerifyImageSignatures(ctx, ref, v.opts)
}