Skip to content

Commit 36dc82e

Browse files
feat(notation): add notation verifier to ocirepository controller
If implemented, this commit will use notation to verify the signature if notation is set in the OCIRepository deployment Signed-off-by: Jason <[email protected]>
1 parent 5002b95 commit 36dc82e

File tree

1 file changed

+71
-2
lines changed

1 file changed

+71
-2
lines changed

controllers/ocirepository_controller.go

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"crypto/tls"
2222
"crypto/x509"
23+
"encoding/json"
2324
"errors"
2425
"fmt"
2526
"io"
@@ -72,6 +73,10 @@ import (
7273
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
7374
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
7475
"github.com/fluxcd/source-controller/internal/util"
76+
77+
_ "github.com/notaryproject/notation-core-go/signature/cose"
78+
_ "github.com/notaryproject/notation-core-go/signature/jws"
79+
"github.com/notaryproject/notation-go/verifier/trustpolicy"
7580
)
7681

7782
// ociRepositoryReadyCondition contains the information required to summarize a
@@ -440,7 +445,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
440445
return sreconcile.ResultEmpty, e
441446
}
442447

443-
err := r.verifySignature(ctx, obj, url, opts.verifyOpts...)
448+
err := r.verifySignature(ctx, obj, url, opts.keychain, opts.verifyOpts...)
444449
if err != nil {
445450
provider := obj.Spec.Verify.Provider
446451
if obj.Spec.Verify.SecretRef == nil {
@@ -627,7 +632,7 @@ func (r *OCIRepositoryReconciler) digestFromRevision(revision string) string {
627632
// verifySignature verifies the authenticity of the given image reference URL.
628633
// First, it tries to use a key if a Secret with a valid public key is provided.
629634
// If not, it falls back to a keyless approach for verification.
630-
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository, url string, opt ...remote.Option) error {
635+
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository, url string, auth authn.Keychain, opt ...remote.Option) error {
631636
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
632637
defer cancel()
633638

@@ -700,6 +705,67 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
700705
}
701706

702707
return fmt.Errorf("no matching signatures were found for '%s'", url)
708+
case "notation":
709+
ref, err := name.ParseReference(url)
710+
if err != nil {
711+
return err
712+
}
713+
714+
// get the public keys from the given secret
715+
if secretRef := obj.Spec.Verify.SecretRef; secretRef != nil {
716+
certSecretName := types.NamespacedName{
717+
Namespace: obj.Namespace,
718+
Name: secretRef.Name,
719+
}
720+
721+
var pubSecret corev1.Secret
722+
if err := r.Get(ctxTimeout, certSecretName, &pubSecret); err != nil {
723+
return err
724+
}
725+
726+
var doc trustpolicy.Document
727+
728+
signatureVerified := false
729+
for k, data := range pubSecret.Data {
730+
if strings.HasSuffix(k, ".json") {
731+
if err := json.Unmarshal(data, &doc); err != nil {
732+
return err
733+
}
734+
}
735+
}
736+
737+
defaultNotaryOciOpts := []soci.NotationOptions{
738+
soci.WithTrustStore(&doc),
739+
soci.WithNotaryRemoteOptions(opt...),
740+
}
741+
742+
for k, data := range pubSecret.Data {
743+
// search for public keys in the secret
744+
if strings.HasSuffix(k, ".pem") {
745+
746+
verifier, err := soci.NewNotaryVerifier(append(defaultNotaryOciOpts, soci.WithNotaryPublicKey(data), soci.WithNotaryKeychain(auth))...)
747+
if err != nil {
748+
return err
749+
}
750+
751+
signatures, err := verifier.Verify(ctxTimeout, ref)
752+
if err != nil {
753+
continue
754+
}
755+
756+
if signatures {
757+
signatureVerified = true
758+
break
759+
}
760+
}
761+
}
762+
763+
if !signatureVerified {
764+
return fmt.Errorf("no matching signatures were found for '%s'", url)
765+
}
766+
return nil
767+
}
768+
return nil
703769
}
704770

705771
return nil
@@ -1197,6 +1263,8 @@ func makeRemoteOptions(ctxTimeout context.Context, obj *ociv1.OCIRepository, tra
11971263
verifyOpts: []remote.Option{},
11981264
}
11991265

1266+
o.keychain = keychain
1267+
12001268
if transport != nil {
12011269
o.craneOpts = append(o.craneOpts, crane.WithTransport(transport))
12021270
o.verifyOpts = append(o.verifyOpts, remote.WithTransport(transport))
@@ -1221,6 +1289,7 @@ func makeRemoteOptions(ctxTimeout context.Context, obj *ociv1.OCIRepository, tra
12211289
type remoteOptions struct {
12221290
craneOpts []crane.Option
12231291
verifyOpts []remote.Option
1292+
keychain authn.Keychain
12241293
}
12251294

12261295
// ociContentConfigChanged evaluates the current spec with the observations

0 commit comments

Comments
 (0)