Skip to content

Commit

Permalink
feat: Support more trust store types (ratify-project#1538)
Browse files Browse the repository at this point in the history
Signed-off-by: Juncheng Zhu <[email protected]>
Co-authored-by: Binbin Li <[email protected]>
  • Loading branch information
2 people authored and duffney committed Jul 10, 2024
1 parent 931ffec commit ec39df0
Show file tree
Hide file tree
Showing 7 changed files with 340 additions and 34 deletions.
28 changes: 28 additions & 0 deletions pkg/verifier/notation/certStores.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
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/notaryproject/notation-go/verifier/truststore"
)

// certStores is an interface that defines the methods for managing certificate stores.
type certStores interface {
// GetCertGroup returns certain type of cert group from namedStore
GetCertGroup(ctx context.Context, storeType truststore.Type, namedStore string) (certGroup []string)
}
105 changes: 105 additions & 0 deletions pkg/verifier/notation/certstoresbytype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
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"
"fmt"

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

type certStoreType string

const (
CA certStoreType = "CA"
SigningAuthority certStoreType = "signingAuthority"
)

func (certstoretype certStoreType) String() string {
return string(certstoretype)
}

// verificationCertStores describes the configuration of verification certStores
// type verificationCertStores supports new format map[string]map[string][]string
//
// {
// "ca": {
// "certs": {"kv1", "kv2"},
// },
// "signingauthority": {
// "certs": {"kv3"}
// },
// }
//
// type verificationCertStores supports legacy format map[string][]string as well.
//
// {
// "certs": {"kv1", "kv2"},
// },
type verificationCertStores map[string]interface{}

// certStoresByType implements certStores interface and place certs under the trustStoreType
//
// {
// "ca": {
// "certs": {"kv1", "kv2"},
// },
// "signingauthority": {
// "certs": {"kv3"}
// },
// }
type certStoresByType map[certStoreType]map[string][]string

// newCertStoreByType performs type assertion and converts certificate stores configuration into certStoresByType
func newCertStoreByType(confInNewFormat verificationCertStores) (certStores, error) {
s := make(certStoresByType)
for certstoretype, storeData := range confInNewFormat {
s[certStoreType(certstoretype)] = make(map[string][]string)
parsedStoreData, ok := storeData.(verificationCertStores)
if !ok {
return nil, fmt.Errorf("certStores: %s assertion to type verificationCertStores failed", storeData)
}
for storeName, certProviderList := range parsedStoreData {
var certProviderNames []string
parsedCertProviders, ok := certProviderList.([]interface{})
if !ok {
return nil, fmt.Errorf("certProviderList: %s assertion to type []interface{} failed", certProviderList)
}
for _, certProvider := range parsedCertProviders {
certProviderName, ok := certProvider.(string)
if !ok {
return nil, fmt.Errorf("certProvider: %s assertion to type string failed", certProvider)
}
certProviderNames = append(certProviderNames, certProviderName)
}
s[certStoreType(certstoretype)][storeName] = certProviderNames
}
}
return s, nil
}

// GetCertGroup returns certain type of certs from namedStore
func (s certStoresByType) GetCertGroup(ctx context.Context, storeType truststore.Type, namedStore string) (certGroup []string) {
if certStores, ok := s[certStoreType(storeType)]; ok {
if certGroup, ok = certStores[namedStore]; ok {
return
}
}
logger.GetLogger(ctx, logOpt).Warnf("unable to fetch certGroup from namedStore: %+v in type: %v", namedStore, storeType)
return
}
61 changes: 61 additions & 0 deletions pkg/verifier/notation/certstoresbytype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
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 "testing"

func TestNewCertStoreByTypeInvalidInput(t *testing.T) {
tests := []struct {
name string
conf verificationCertStores
expectErr bool
}{
{
name: "invalid certStores type",
conf: verificationCertStores{
trustStoreTypeCA: []string{},
},
expectErr: true,
},
{
name: "invalid certProviderList type",
conf: verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": "akv1",
"certstore2": []interface{}{"akv3", "akv4"},
},
},
expectErr: true,
},
{
name: "invalid certProvider type",
conf: verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"akv1", []string{}},
},
},
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := newCertStoreByType(tt.conf)
if (err != nil) != tt.expectErr {
t.Errorf("error = %v, expectErr = %v", err, tt.expectErr)
}
})
}
}
58 changes: 50 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 defines a collection of Notary Project Trust Stores.
// 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,10 @@ 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, 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 +216,37 @@ 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 {
if err := normalizeVerificationCertsStores(conf); 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
}
57 changes: 53 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 Expand Up @@ -408,3 +410,50 @@ func TestGetNestedReferences(t *testing.T) {
t.Fatalf("notation signature should not have nested references")
}
}

func TestNormalizeVerificationCertsStores(t *testing.T) {
tests := []struct {
name string
conf *NotationPluginVerifierConfig
expectErr bool
}{
{
name: "successfully normalizaVerificationCertsStores",
conf: &NotationPluginVerifierConfig{
Name: test,
VerificationCerts: []string{testPath, defaultCertDir},
VerificationCertStores: verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"akv1", "akv2"},
"certstore2": []interface{}{"akv3", "akv4"},
},
},
},
expectErr: false,
},
{

name: "failed normalizaVerificationCertsStores with both old VerificationCertStores and new VerificationCertStores are provided",
conf: &NotationPluginVerifierConfig{
Name: test,
VerificationCerts: []string{testPath, defaultCertDir},
VerificationCertStores: verificationCertStores{
trustStoreTypeCA: verificationCertStores{
"certstore1": []interface{}{"akv1", "akv2"},
},
"certstore2": []interface{}{"akv3", "akv4"},
},
},
expectErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := normalizeVerificationCertsStores(tt.conf)
if (err != nil) != tt.expectErr {
t.Errorf("error = %v, expectErr = %v", err, tt.expectErr)
}
})
}
}
Loading

0 comments on commit ec39df0

Please sign in to comment.