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 more trust store types #1538

Merged
Merged
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
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.
junczhu marked this conversation as resolved.
Show resolved Hide resolved
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)

Check warning on line 34 in pkg/verifier/notation/certstoresbytype.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/notation/certstoresbytype.go#L33-L34

Added lines #L33 - L34 were not covered by tests
}

// 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{}
junczhu marked this conversation as resolved.
Show resolved Hide resolved

// 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 @@
"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 @@

// 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 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

Check warning on line 189 in pkg/verifier/notation/notation.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/notation/notation.go#L189

Added line #L189 was not covered by tests
}

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

Expand Down Expand Up @@ -201,10 +216,37 @@

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

Check warning on line 221 in pkg/verifier/notation/notation.go

View check run for this annotation

Codecov / codecov/patch

pkg/verifier/notation/notation.go#L221

Added line #L221 was not covered by tests
}
}
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
Loading