Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support certStoresByType #1

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions pkg/verifier/notation/certStoresByType.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
Copyright The Ratify 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 notation

import (
"context"

"github.com/deislabs/ratify/internal/logger"
"github.com/notaryproject/notation-go/verifier/truststore"
)

type verificationCertStores map[string]interface{}

// type certStores map[string][]string
type certStoresByType map[string]map[string][]string

func newCertStoreByType(confVerificationCertStores verificationCertStores) (certStoresByType, error) {
certStoresByType := make(map[string]map[string][]string)
for certStoreType, certStores := range confVerificationCertStores {
certStoresByType[certStoreType] = make(map[string][]string)
for certStore, certs := range certStores.(verificationCertStores) {
var reformedCerts []string
for _, cert := range certs.([]interface{}) {
if reformedCert, ok := cert.(string); ok {
reformedCerts = append(reformedCerts, reformedCert)
}
}
certStoresByType[certStoreType][certStore] = reformedCerts
}
}
return certStoresByType, nil
}

// GetCertGroupFromStore returns certain type of certs from namedStore
func GetCertGroupFromStore(ctx context.Context, certStoresByType certStoresByType, storeType truststore.Type, namedStore string) (certGroup []string) {
if certStores, ok := certStoresByType[string(storeType)]; ok {
if certGroup, ok = certStores[namedStore]; ok {
return
}
}
logger.GetLogger(ctx, logOpt).Debugf("unable to fetch certGroup from namedStore: %+v in type: %v", namedStore, storeType)
return
}
63 changes: 55 additions & 8 deletions pkg/verifier/notation/notation.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ import (
"github.com/notaryproject/notation-go"
notationVerifier "github.com/notaryproject/notation-go/verifier"
"github.com/notaryproject/notation-go/verifier/trustpolicy"
"github.com/notaryproject/notation-go/verifier/truststore"
oci "github.com/opencontainers/image-spec/specs-go/v1"
)

const (
verifierType = "notation"
defaultCertPath = "ratify-certs/notation/truststore"
verifierType = "notation"
defaultCertPath = "ratify-certs/notation/truststore"
trustStoreTypeCA = string(truststore.TypeCA)
trustStoreTypeypeSigningAuthority = string(truststore.TypeSigningAuthority)
)

// NotationPluginVerifierConfig describes the configuration of notation verifier
Expand All @@ -56,8 +59,21 @@ type NotationPluginVerifierConfig struct { //nolint:revive // ignore linter to h

// VerificationCerts is array of directories containing certificates.
VerificationCerts []string `json:"verificationCerts"`
// VerificationCerts is map defining which keyvault certificates belong to which trust store
VerificationCertStores map[string][]string `json:"verificationCertStores"`
// VerificationCertStores is map defining which keyvault certificates belong to which trust store name.
// VerificationCertStores accepts new format map[string]map[string][]string
// {
// "ca": {
// "certs": {"kv1", "kv2"},
// },
// "signingauthority": {
// "certs": {"kv3"}
// },
// }
// VerificationCertStores accepts legacy format map[string][]string as well.
// {
// "certs": {"kv1", "kv2"},
// },
VerificationCertStores verificationCertStores `json:"verificationCertStores"`
// TrustPolicyDoc represents a trustpolicy.json document. Reference: https://pkg.go.dev/github.com/notaryproject/[email protected]/verifier/trustpolicy#Document
TrustPolicyDoc trustpolicy.Document `json:"trustPolicyDoc"`
}
Expand Down Expand Up @@ -168,11 +184,14 @@ func (v *notationPluginVerifier) Verify(ctx context.Context,
}

func getVerifierService(conf *NotationPluginVerifierConfig, pluginDirectory string) (notation.Verifier, error) {
store := &trustStore{
certPaths: conf.VerificationCerts,
certStores: conf.VerificationCertStores,
// store := &trustStore{
// certPaths: conf.VerificationCerts,
// certStores: conf.VerificationCertStores,
// }
store, err := newTrustStore(conf.VerificationCerts, conf.VerificationCertStores)
if err != nil {
return nil, err
}

return notationVerifier.New(&conf.TrustPolicyDoc, store, NewRatifyPluginManager(pluginDirectory))
}

Expand Down Expand Up @@ -201,10 +220,38 @@ func parseVerifierConfig(verifierConfig config.VerifierConfig, _ string) (*Notat

defaultCertsDir := paths.Join(homedir.Get(), ratifyconfig.ConfigFileDir, defaultCertPath)
conf.VerificationCerts = append(conf.VerificationCerts, defaultCertsDir)
if len(conf.VerificationCertStores) > 0 {
err := normalizeVerificationCertsStores(conf)
if err != nil {
return nil, err
}
}
return conf, nil
}

// signatures should not have nested references
func (v *notationPluginVerifier) GetNestedReferences() []string {
return []string{}
}

// normalizeVerificationCertsStores normalize the structure does not match the latest spec
func normalizeVerificationCertsStores(conf *NotationPluginVerifierConfig) error {
isCertStoresByType, isLegacyCertStore := false, false
for key := range conf.VerificationCertStores {
if key != trustStoreTypeCA && key != trustStoreTypeypeSigningAuthority {
isLegacyCertStore = true
logger.GetLogger(context.Background(), logOpt).Debugf("Get VerificationCertStores in legacy format")
} else {
isCertStoresByType = true
}
}
if isCertStoresByType && isLegacyCertStore {
return re.ErrorCodeConfigInvalid.NewError(re.Verifier, conf.Name, re.EmptyLink, nil, "both old VerificationCertStores and new VerificationCertStores are provided, please provide only one", re.HideStackTrace)
} else if !isCertStoresByType && isLegacyCertStore {
// normalize <store>:<certs> to ca:<store><certs> if no store type is provided
conf.VerificationCertStores = verificationCertStores{
trustStoreTypeCA: conf.VerificationCertStores,
}
}
return nil
}
10 changes: 6 additions & 4 deletions pkg/verifier/notation/notation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,17 +248,19 @@ func TestParseVerifierConfig(t *testing.T) {
"name": test,
"verificationCerts": []string{testPath},
"verificationCertStores": map[string][]string{
"certstore1": {"defaultns/akv1", "akv2"},
"certstore1": {"akv1", "akv2"},
"certstore2": {"akv3", "akv4"},
},
},
expectErr: false,
expect: &NotationPluginVerifierConfig{
Name: test,
VerificationCerts: []string{testPath, defaultCertDir},
VerificationCertStores: map[string][]string{
"certstore1": {"defaultns/akv1", "akv2"},
"certstore2": {"akv3", "akv4"},
VerificationCertStores: verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"akv1", "akv2"},
"certstore2": []interface{}{"akv3", "akv4"},
},
},
},
},
Expand Down
27 changes: 20 additions & 7 deletions pkg/verifier/notation/truststore.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,40 @@ var logOpt = logger.Option{
}

type trustStore struct {
certPaths []string
certStores map[string][]string
certPaths []string
certStoresByType certStoresByType
}

func newTrustStore(certPaths []string, verificationCertStores verificationCertStores) (*trustStore, error) {
certStoresByType, err := newCertStoreByType(verificationCertStores)
if err != nil {
return nil, err
}
store := &trustStore{
certPaths: certPaths,
certStoresByType: certStoresByType,
}
return store, nil
}

// trustStore implements GetCertificates API of X509TrustStore interface: [https://pkg.go.dev/github.com/notaryproject/[email protected]/verifier/truststore#X509TrustStore]
// Note: this api gets invoked when Ratify calls verify API, so the certificates
// will be loaded for each signature verification.
// And this API must follow the Notation Trust Store spec: https://github.com/notaryproject/notaryproject/blob/main/specs/trust-store-trust-policy.md#trust-store
func (s trustStore) GetCertificates(ctx context.Context, _ truststore.Type, namedStore string) ([]*x509.Certificate, error) {
certs, err := s.getCertificatesInternal(ctx, namedStore)
func (s *trustStore) GetCertificates(ctx context.Context, trustStoreType truststore.Type, namedStore string) ([]*x509.Certificate, error) {
certs, err := s.getCertificatesInternal(ctx, trustStoreType, namedStore)
if err != nil {
return nil, err
}
return s.filterValidCerts(certs)
}

func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore string) ([]*x509.Certificate, error) {
func (s *trustStore) getCertificatesInternal(ctx context.Context, storeType truststore.Type, namedStore string) ([]*x509.Certificate, error) {
certs := make([]*x509.Certificate, 0)

certGroup := GetCertGroupFromStore(ctx, s.certStoresByType, storeType, namedStore)
// certs configured for this namedStore overrides cert path
if certGroup := s.certStores[namedStore]; len(certGroup) > 0 {
if len(certGroup) > 0 {
for _, certStore := range certGroup {
logger.GetLogger(ctx, logOpt).Debugf("truststore getting certStore %v", certStore)
certMap, err := keymanagementprovider.GetCertificatesFromMap(ctx, certStore)
Expand Down Expand Up @@ -93,7 +106,7 @@ func (s trustStore) getCertificatesInternal(ctx context.Context, namedStore stri
}

// filterValidCerts keeps CA certificates and self-signed certs.
func (s trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certificate, error) {
func (s *trustStore) filterValidCerts(certs []*x509.Certificate) ([]*x509.Certificate, error) {
filteredCerts := make([]*x509.Certificate, 0)
for _, cert := range certs {
if !cert.IsCA {
Expand Down
37 changes: 22 additions & 15 deletions pkg/verifier/notation/truststore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"testing"

"github.com/deislabs/ratify/pkg/controllers"
"github.com/notaryproject/notation-go/verifier/truststore"
)

const (
Expand All @@ -48,26 +49,32 @@ func (m *mockCertStores) DeleteStore(_ string) {}

func TestGetCertificates_EmptyCertMap(t *testing.T) {
resetCertStore()
certStore := map[string][]string{}
certStore["store1"] = []string{"kv1"}
certStore["store2"] = []string{"kv2"}
store := &trustStore{
certStores: certStore,
certStore := verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"akv1", "akv2"},
"certstore2": []interface{}{"akv3", "akv4"},
},
}

if _, err := store.getCertificatesInternal(context.Background(), "store1"); err == nil {
store, err := newTrustStore([]string{}, certStore)
if err != nil {
panic("failed to parse verificationCertStores: " + err.Error())
}
if _, err := store.getCertificatesInternal(context.Background(), truststore.TypeCA, "certstore1"); err == nil {
t.Fatalf("error expected if cert map is empty")
}
}

func TestGetCertificates_NamedStore(t *testing.T) {
resetCertStore()
certStore := map[string][]string{}
certStore["store1"] = []string{"default/kv1"}
certStore["store2"] = []string{"projecta/kv2"}

store := &trustStore{
certStores: certStore,
certStore := verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"default/kv1"},
"certstore2": []interface{}{"projecta/kv2"},
},
}
store, err := newTrustStore(nil, certStore)
if err != nil {
panic("failed to parse verificationCertStores: " + err.Error())
}

kv1Cert := getCert(certStr)
Expand All @@ -81,7 +88,7 @@ func TestGetCertificates_NamedStore(t *testing.T) {
}

// only the certificate in the specified namedStore should be returned
result, _ := store.getCertificatesInternal(context.Background(), "store1")
result, _ := store.getCertificatesInternal(context.Background(), truststore.TypeCA, "certstore1")
expectedLen := 1

if len(result) != expectedLen {
Expand All @@ -107,7 +114,7 @@ func TestGetCertificates_certPath(t *testing.T) {
trustStore := &trustStore{
certPaths: []string{tmpFile.Name()},
}
certs, err := trustStore.getCertificatesInternal(context.Background(), "")
certs, err := trustStore.getCertificatesInternal(context.Background(), truststore.TypeCA, "")
if err != nil {
t.Fatalf("failed to get certs: %v", err)
}
Expand Down
Loading