-
Notifications
You must be signed in to change notification settings - Fork 62
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
wip: notation implementation #1885
base: develop
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,7 @@ jobs: | |
"regular", | ||
"notaryv1", | ||
"cosign", | ||
"notation", | ||
"namespaced", | ||
"deployment", | ||
"pre-config", | ||
|
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package notation | ||
|
||
import ( | ||
"connaisseur/internal/image" | ||
"connaisseur/internal/policy" | ||
"connaisseur/internal/utils" | ||
"connaisseur/internal/validator/auth" | ||
"context" | ||
"fmt" | ||
|
||
"github.com/notaryproject/notation-go" | ||
"github.com/notaryproject/notation-go/registry" | ||
"github.com/notaryproject/notation-go/verifier" | ||
"github.com/notaryproject/notation-go/verifier/trustpolicy" | ||
"github.com/notaryproject/notation-go/verifier/truststore" | ||
"github.com/sirupsen/logrus" | ||
"oras.land/oras-go/v2/registry/remote" | ||
orasAuth "oras.land/oras-go/v2/registry/remote/auth" | ||
) | ||
|
||
type NotationValidator struct { | ||
Name string `validate:"required"` | ||
Type string `validate:"eq=notation"` | ||
Auth auth.Auth | ||
TrustStore truststore.X509TrustStore | ||
} | ||
|
||
type NotationValidatorYaml struct { | ||
Name string `yaml:"name"` | ||
Type string `yaml:"type"` | ||
Auth auth.Auth `yaml:"auth"` | ||
TrustRoots []auth.TrustRoot `yaml:"trustRoots"` | ||
} | ||
|
||
func (nv *NotationValidator) UnmarshalYAML(unmarshal func(interface{}) error) error { | ||
var valData NotationValidatorYaml | ||
if err := unmarshal(&valData); err != nil { | ||
return err | ||
} | ||
|
||
if len(valData.TrustRoots) < 1 { | ||
return fmt.Errorf("no trust roots provided for validator %s", valData.Name) | ||
} | ||
|
||
imts, err := NewInMemoryTrustStore(valData.TrustRoots) | ||
if err != nil { | ||
return fmt.Errorf("failed to create trust store: %s", err) | ||
} | ||
|
||
nv.Name = valData.Name | ||
nv.Type = valData.Type | ||
nv.Auth = valData.Auth | ||
nv.TrustStore = imts | ||
|
||
return nil | ||
} | ||
|
||
func (nv *NotationValidator) ValidateImage( | ||
ctx context.Context, | ||
image *image.Image, | ||
args policy.RuleOptions, | ||
) (string, error) { | ||
|
||
trustPolicy, err := nv.setUpTrustPolicy(image, args) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to set up trust policy: %s", err) | ||
} | ||
|
||
verifier, err := verifier.New(trustPolicy, nv.TrustStore, nil) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create verifier: %s", err) | ||
} | ||
|
||
remoteRepo, err := remote.NewRepository(image.Context().String()) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create remote repository: %s", err) | ||
} | ||
|
||
if authn := nv.Auth.LookUp(image.Context().Name()); authn.Username != "" && | ||
authn.Password != "" { | ||
client := orasAuth.DefaultClient | ||
client.Credential = func(nv2_ctx context.Context, s string) (orasAuth.Credential, error) { | ||
return orasAuth.Credential{ | ||
Username: authn.Username, | ||
Password: authn.Password, | ||
}, nil | ||
} | ||
remoteRepo.Client = client | ||
} | ||
remoteRegisty := registry.NewRepository(remoteRepo) | ||
|
||
if image.Digest() == "" { | ||
desc, err := remoteRegisty.Resolve(ctx, image.Name()) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to resolve image tag: %s", err) | ||
} | ||
logrus.Debugf("resolved digest: %s", desc.Digest.String()) | ||
image.SetDigest(desc.Digest.String()) | ||
} | ||
|
||
verifyOptions := notation.VerifyOptions{ | ||
ArtifactReference: fmt.Sprintf("%s@%s", image.Context().String(), image.Digest()), | ||
MaxSignatureAttempts: 10, | ||
} | ||
|
||
digest, _, err := notation.Verify(ctx, verifier, remoteRegisty, verifyOptions) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to verify image: %s", err) | ||
} | ||
|
||
return string(digest.Digest), nil | ||
} | ||
|
||
func (nv *NotationValidator) setUpTrustPolicy( | ||
image *image.Image, | ||
args policy.RuleOptions, | ||
) (*trustpolicy.Document, error) { | ||
imts := nv.TrustStore.(*InMemoryTrustStore) | ||
trs, err := auth.GetTrustRoots([]string{args.TrustRoot}, imts.trustRoots, true) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to get trust roots: %s", err) | ||
} | ||
|
||
return &trustpolicy.Document{ | ||
Version: "1.0", | ||
TrustPolicies: []trustpolicy.TrustPolicy{ | ||
{ | ||
Name: "default", | ||
RegistryScopes: []string{image.Context().String()}, | ||
SignatureVerification: trustpolicy.SignatureVerification{ | ||
VerificationLevel: trustpolicy.LevelStrict.Name, | ||
}, | ||
TrustStores: utils.Map(trs, func(tr auth.TrustRoot) string { return fmt.Sprintf("ca:%s", tr.Name) }), | ||
TrustedIdentities: []string{"*"}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without knowing anything, this looks weird 🤔 |
||
}, | ||
}, | ||
}, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package notation | ||
|
||
import ( | ||
"connaisseur/internal/validator/auth" | ||
"context" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"fmt" | ||
|
||
"github.com/notaryproject/notation-go/verifier/truststore" | ||
) | ||
|
||
type InMemoryTrustStore struct { | ||
trustRoots []auth.TrustRoot | ||
certs map[string][]*x509.Certificate | ||
truststore.X509TrustStore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is this used, isn't the interface already satisfied? |
||
} | ||
|
||
func NewInMemoryTrustStore(trustRoots []auth.TrustRoot) (*InMemoryTrustStore, error) { | ||
certs := make(map[string][]*x509.Certificate) | ||
|
||
for _, trustRoot := range trustRoots { | ||
block, _ := pem.Decode([]byte(trustRoot.Cert)) | ||
cert, err := x509.ParseCertificate(block.Bytes) | ||
if err != nil { | ||
return &InMemoryTrustStore{}, fmt.Errorf("failed to parse certificate for trust root %s: %w", trustRoot.Name, err) | ||
} | ||
certs[trustRoot.Name] = []*x509.Certificate{cert} | ||
} | ||
|
||
return &InMemoryTrustStore{ | ||
trustRoots: trustRoots, | ||
certs: certs, | ||
}, nil | ||
} | ||
|
||
func (imts *InMemoryTrustStore) GetCertificates( | ||
ctx context.Context, | ||
_ truststore.Type, | ||
namedStore string, | ||
) ([]*x509.Certificate, error) { | ||
for name, certs := range imts.certs { | ||
if name == namedStore { | ||
return certs, nil | ||
} | ||
} | ||
|
||
return nil, fmt.Errorf("no certificates found for trustRoot %s", namedStore) | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,8 @@ | ||||||
# simple test cases for notation | ||||||
- id: unsigned | ||||||
txt: Testing unsigned image... | ||||||
ref: ghcr.io/sse-secure-systems/testimage:notation-unsign | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
expected_msg: error during notation validation | ||||||
- id: signed | ||||||
txt: Testing signed image... | ||||||
ref: ghcr.io/sse-secure-systems/testimage:notation-sign | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
application: | ||
validators: | ||
- name: ghcr-notation | ||
type: notation | ||
trustRoots: | ||
- name: default | ||
cert: | | ||
-----BEGIN CERTIFICATE----- | ||
MIIDrjCCApagAwIBAgIUfA/t/J6eINSu566aAozkOjQKey4wDQYJKoZIhvcNAQEL | ||
BQAwbDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy | ||
bGluMQwwCgYDVQQKDANTU0UxEjAQBgNVBAsMCURlZmVuc2l2ZTEZMBcGA1UEAwwQ | ||
c2VjdXJlc3lzdGVtcy5kZTAgFw0yNTAxMjQxMjQzMTRaGA8yMTI0MTIzMTEyNDMx | ||
NFowbDELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxpbjEPMA0GA1UEBwwGQmVy | ||
bGluMQwwCgYDVQQKDANTU0UxEjAQBgNVBAsMCURlZmVuc2l2ZTEZMBcGA1UEAwwQ | ||
c2VjdXJlc3lzdGVtcy5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB | ||
AJSmGE+Knp0Qc2RqIkMys8e7X1F8zVbpVMmRxvkuZIrWb84Xb+XVeQgE1o5JQgPL | ||
7PSgG6EHL/XjY1wKO2bwlC4AxLm3Cvo/r764yUTqGFoeImEsiT/BW8bFKndJYkP8 | ||
b8iHUkrRX7ZYkhjby91zwFzeewDb9dZPkqiV7npATOL/T5KSUVQ6uIIozX5GCj+b | ||
B/iqZWB0bP33uqPEu+GUyYZudJlYe/Yv9aw8vioVXdoEHH10DtTosfXlub/Xd8bC | ||
8a7qOBITpWJRrjWRwjaWgnKUlJxymhqU5Iudi57VgtkzD2AgRGLZEN27x67o5p0X | ||
aN2O2cFDCCY/7DMlW965++sCAwEAAaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud | ||
JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBSiqJeF6lNSF21K51ePceEtsIBPajAN | ||
BgkqhkiG9w0BAQsFAAOCAQEAhaKvo0ofGKIoMNaCqv4qYCBsnXTLWqeRMzrxY3WP | ||
RmGkiLoKivXP2ZL4R2igERW8IbXSDqC9u1to7ahwLiiM9Ikjik8I/x3EYJz3DAkz | ||
eTDqS227EhSOOGo1G6f0ph/GPO4o71s8ek55Q92ZNrAqHwzGwsByGFbHcwABwtAA | ||
1gqAB5luiuokUXhmlqkH46wbQLiVLYnetqIQ8uJiSnUFrWaKQSICnCY1kxptqepv | ||
Vfgbd7LIriH+m57IWD2fil6qRXV7c0J6v+N5N7gf2TgDvRBxYRhHV+fQpxRWm+Ti | ||
fyEquTrEhfXY4yfYWnpJ/EbjsHheqK+F4EacAqBTjhzW+g== | ||
-----END CERTIFICATE----- | ||
policy: | ||
- pattern: "ghcr.io/sse-secure-systems/testimage:*" | ||
validator: ghcr-notation | ||
alerts: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#!/usr/bin/env bash | ||
set -euo pipefail | ||
|
||
notation_test() { | ||
update_with_file "notation/install.yaml" | ||
update '(.application.validators[] | select(.name == "ghcr-notation") | .auth) += {"secretName": env(IMAGEPULLSECRET)}' | ||
install "make" | ||
multi_test "notation/cases.yaml" | ||
uninstall "make" | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: missing EOF newline There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also for other files below |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
VerifyTimestamp
? Without having looked at it this looks like something we want to have an opinion on