diff --git a/.github/.codecov.yml b/.github/.codecov.yml index f1f634615..5cb09045b 100644 --- a/.github/.codecov.yml +++ b/.github/.codecov.yml @@ -15,4 +15,7 @@ coverage: status: project: default: - target: 70% \ No newline at end of file + target: 70% + patch: + default: + target: 80% diff --git a/cmd/notation/internal/truststore/testdata/NotationTestRoot.pem b/cmd/notation/internal/truststore/testdata/NotationTestRoot.pem new file mode 100644 index 000000000..aa23820da --- /dev/null +++ b/cmd/notation/internal/truststore/testdata/NotationTestRoot.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEizCCAvOgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEb +MBkGA1UEAxMSTm90YXRpb24gVGVzdCBSb290MCAXDTIwMDkwOTA3MDAwMFoYDzIx +MjIwOTA1MjAzODQ1WjBaMQswCQYDVQQGEwJVUzELMAkGA1UECBMCV0ExEDAOBgNV +BAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEbMBkGA1UEAxMSTm90YXRpb24g +VGVzdCBSb290MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxxAZ8VZe +gqBUctz3BkwhObZKnW+KsN5/N1/u2vPLmEzHDj6xgd8Hn0JoughDaxeQCV66NC2o +bqPnPp4+68G/qZnxkXVXdFyqVodu4FgPUjiqcJjft7bh45BVgLFpOqSqDQ3ko30B +7gdGfIIkoBj/8gz3tHnmIvl3MywtOhDeGnlLNzBY52wVmhPIdKOaW/7WkMrXKFCk +LkNICGnIpWuyBtC+7RfM8hG6eRW1KCm5xrkRmn5ptonjxix/JTGj4me/NMkwdVkz +6wcCSAJnqTgHi2oqk73qqNu0LHsEMFBF8IGqmVkn2MOHkFamPBokzQ6HXXfvR4nb +cWQZCUgRinPTVg9CF0B6XSCEMCSH5kveZxTQtAFRB6NosbzuU5jDmJgpbDfauev7 +Eg/6bZzphcugRkVuwulymzsake5Jbvs9Kyw3CNPYH2G3Kli1FNhfc46ugXHbIfXg +NQcou3xabcu+r6cFRqqK6NmV9ouMQRj8Ri95Gp2BUlpTEFhcvMb9d4nXAgMBAAGj +WjBYMA4GA1UdDwEB/wQEAwICBDATBgNVHSUEDDAKBggrBgEFBQcDAzASBgNVHRMB +Af8ECDAGAQH/AgEBMB0GA1UdDgQWBBS5FZjt9UsEPkcKrStrnjSpTq4kDTANBgkq +hkiG9w0BAQsFAAOCAYEAKtxfv12LzM85bxOMp5++pIDa6eMcBaurYbAM2yC9B6Lu +Hf0JGeFdNqt4Fw38Ajooj2vWMWBrARVEZRVqTC5+ZSN2meGBXBXlT4n8FdEdmv+0 +5iwVYdmDFp8FKeoOZZZF23u+r2OrazJo1ufWmoSI2P0lEfZQQFQElltWu3QH+OLO +WXJmB7KbLKyheelGK5XhtAYYapRdW4sKJ398ybpv5C1oALCcTwoSmvH8wW5J4/gj +mhKICYh2goMauf0lesdxj+0His7E8blOWrUmfOB5dp73XawLKcd/UxHN8zAPC08L +DL9NMcihn3ZHKi7/dtkiV2iSaDPD1ChSGdqfXIysYqOhYoktgAfBZ43CWnqQhgB8 +NezRKdOStYC3P2AGJW18irxxTRp2CO+gnXEcyhyr+cvyf0j8MkRSaHLXzjIrECu8 +BUitB6sKughdN13fs5t5SIiO6foeFdvIpZFFKO8s+4oTOSDCos2WFoC+8TZS6r58 +3OtFLmywl1HRgQkobGgw +-----END CERTIFICATE----- diff --git a/cmd/notation/internal/truststore/testdata/invalid.txt b/cmd/notation/internal/truststore/testdata/invalid.txt new file mode 100644 index 000000000..98eea0a15 --- /dev/null +++ b/cmd/notation/internal/truststore/testdata/invalid.txt @@ -0,0 +1 @@ +invalid test cert diff --git a/cmd/notation/internal/truststore/testdata/self-signed.crt b/cmd/notation/internal/truststore/testdata/self-signed.crt new file mode 100644 index 000000000..dd0094e90 --- /dev/null +++ b/cmd/notation/internal/truststore/testdata/self-signed.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIBeTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP +MA0GA1UEAxMGYWxwaW5lMB4XDTIzMDUwOTA0NTUxMloXDTMzMDUxMDA0NTUxMlow +TjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8w +DQYDVQQKEwZOb3RhcnkxDzANBgNVBAMTBmFscGluZTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAK5hpq1229GGLjMK6i9KZhuUO+SV7rUFnWIDiIPO5yWx +YDkl+bGroeAvJYu6MVCMQ6FMRXD9jhnG6R+sAHwY7gVgcJ1OXak87PkLp/Ii1Cr7 +XkkySZeD+Br1vSQzfxs3pFG+iBCeVVkeZdsg+xqwnAlqAILXwIbTGRyJP1Xiu9nw +OeuX1YmxPl2m29Pt1EtfVCL9COsVKt5LgOVyWP/9ISWevOBqSCU9bk35HFo9VTeU +f6+ffhSMjv0Y9uwkFFOKXpcV8Sa3ArqyBmgQlUfGg1iwYlqiDE0fTYxiB3gLgETA +lmTm50J+WB9LoDrnrQpbXFLoegm+JV+uSD8J8H7DL2sCAwEAAaMnMCUwDgYDVR0P +AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IB +AQAt0Nvna1c4pPn8kzoN5VvmFmeIgdO/BJpmdhdg0WIQ9aeN/xPXXaVjPp1Mk7ed +XHAvBwQr0Gyzqyy7g/h0gdnAFG7f6blrRNzbrRBCq6cNqX8iwgK/9+2OYKxk1QWj +8Gx0cvu1DN1aXjPPGgQ2j3tHjJvJv32J/zuZa8gU40RPPSLaBlc5ZjpFmyi29sKl +TeeZ+F/Ssic51qXXw2CsYGGWK5yQ3xSCxbw6bb2G/s/YI7/KlWg9BktBJHzRu04Z +NR77W7/dyJ3Lj17PlW1XKmMOFHsQivagXeRCbmYZ43fX4ugFRFKL7KE0EgmGOWpJ +0xv+6ig93sqHzQ/0uv1YgFov +-----END CERTIFICATE----- diff --git a/cmd/notation/internal/truststore/testdata/truststore/x509/ca/test/self-signed.crt b/cmd/notation/internal/truststore/testdata/truststore/x509/ca/test/self-signed.crt new file mode 100644 index 000000000..dd0094e90 --- /dev/null +++ b/cmd/notation/internal/truststore/testdata/truststore/x509/ca/test/self-signed.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPjCCAiagAwIBAgIBeTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJVUzEL +MAkGA1UECBMCV0ExEDAOBgNVBAcTB1NlYXR0bGUxDzANBgNVBAoTBk5vdGFyeTEP +MA0GA1UEAxMGYWxwaW5lMB4XDTIzMDUwOTA0NTUxMloXDTMzMDUxMDA0NTUxMlow +TjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8w +DQYDVQQKEwZOb3RhcnkxDzANBgNVBAMTBmFscGluZTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAK5hpq1229GGLjMK6i9KZhuUO+SV7rUFnWIDiIPO5yWx +YDkl+bGroeAvJYu6MVCMQ6FMRXD9jhnG6R+sAHwY7gVgcJ1OXak87PkLp/Ii1Cr7 +XkkySZeD+Br1vSQzfxs3pFG+iBCeVVkeZdsg+xqwnAlqAILXwIbTGRyJP1Xiu9nw +OeuX1YmxPl2m29Pt1EtfVCL9COsVKt5LgOVyWP/9ISWevOBqSCU9bk35HFo9VTeU +f6+ffhSMjv0Y9uwkFFOKXpcV8Sa3ArqyBmgQlUfGg1iwYlqiDE0fTYxiB3gLgETA +lmTm50J+WB9LoDrnrQpbXFLoegm+JV+uSD8J8H7DL2sCAwEAAaMnMCUwDgYDVR0P +AQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0GCSqGSIb3DQEBCwUAA4IB +AQAt0Nvna1c4pPn8kzoN5VvmFmeIgdO/BJpmdhdg0WIQ9aeN/xPXXaVjPp1Mk7ed +XHAvBwQr0Gyzqyy7g/h0gdnAFG7f6blrRNzbrRBCq6cNqX8iwgK/9+2OYKxk1QWj +8Gx0cvu1DN1aXjPPGgQ2j3tHjJvJv32J/zuZa8gU40RPPSLaBlc5ZjpFmyi29sKl +TeeZ+F/Ssic51qXXw2CsYGGWK5yQ3xSCxbw6bb2G/s/YI7/KlWg9BktBJHzRu04Z +NR77W7/dyJ3Lj17PlW1XKmMOFHsQivagXeRCbmYZ43fX4ugFRFKL7KE0EgmGOWpJ +0xv+6ig93sqHzQ/0uv1YgFov +-----END CERTIFICATE----- diff --git a/cmd/notation/internal/truststore/truststore.go b/cmd/notation/internal/truststore/truststore.go index 39edb0658..c2365a18f 100644 --- a/cmd/notation/internal/truststore/truststore.go +++ b/cmd/notation/internal/truststore/truststore.go @@ -194,7 +194,7 @@ func DeleteCert(storeType, namedStore, cert string, confirmed bool) error { return nil } -// CheckNonErrNotExistError returns nil when no err or err is fs.ErrNotExist +// CheckNonErrNotExistError returns nil when err is nil or err is fs.ErrNotExist func CheckNonErrNotExistError(err error) error { if err != nil && !errors.Is(err, fs.ErrNotExist) { return err diff --git a/cmd/notation/internal/truststore/truststore_test.go b/cmd/notation/internal/truststore/truststore_test.go index 2693fb4c9..b8aab3635 100644 --- a/cmd/notation/internal/truststore/truststore_test.go +++ b/cmd/notation/internal/truststore/truststore_test.go @@ -15,15 +15,107 @@ package truststore import ( "errors" + "os" "path/filepath" + "runtime" + "strings" "testing" + + "github.com/notaryproject/notation-go/dir" ) -func TestEmptyCertFile(t *testing.T) { - path := filepath.FromSlash("../../../../internal/testdata/Empty.txt") - expectedErr := errors.New("no valid certificate found in the empty file") - err := AddCert(path, "ca", "test", false) - if err == nil || err.Error() != "no valid certificate found in the file" { - t.Fatalf("expected err: %v, got: %v", expectedErr, err) - } +func TestAddCert(t *testing.T) { + defer func(oldDir string) { + dir.UserConfigDir = oldDir + }(dir.UserConfigDir) + + t.Run("empty store type", func(t *testing.T) { + expectedErrMsg := "store type cannot be empty" + err := AddCert("", "", "test", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) + + t.Run("invalid store type", func(t *testing.T) { + expectedErrMsg := "unsupported store type: invalid" + err := AddCert("", "invalid", "test", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) + + t.Run("invalid store name", func(t *testing.T) { + expectedErrMsg := "named store name needs to follow [a-zA-Z0-9_.-]+ format" + err := AddCert("", "ca", "test%", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) + + t.Run("no valid certificate in file", func(t *testing.T) { + path := filepath.FromSlash("testdata/invalid.txt") + expectedErrMsg := "x509: malformed certificate" + err := AddCert(path, "ca", "test", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) + + t.Run("cert already exists", func(t *testing.T) { + dir.UserConfigDir = "testdata" + path := filepath.FromSlash("testdata/self-signed.crt") + expectedErrMsg := "certificate already exists in the Trust Store" + err := AddCert(path, "ca", "test", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) + + t.Run("empty file", func(t *testing.T) { + path := filepath.FromSlash("../../../../internal/testdata/Empty.txt") + expectedErr := errors.New("no valid certificate found in the empty file") + err := AddCert(path, "ca", "test", false) + if err == nil || err.Error() != "no valid certificate found in the file" { + t.Fatalf("expected err: %v, but got: %v", expectedErr, err) + } + }) + + t.Run("failed to add cert to store", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + dir.UserConfigDir = t.TempDir() + if err := os.Chmod(dir.UserConfigDir, 0000); err != nil { + t.Fatal(err) + } + defer os.Chmod(dir.UserConfigDir, 0700) + + path := filepath.FromSlash("testdata/NotationTestRoot.pem") + expectedErrMsg := "permission denied" + err := AddCert(path, "ca", "test", false) + if err == nil || !strings.Contains(err.Error(), expectedErrMsg) { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) +} + +func TestDeleteAllCerts(t *testing.T) { + defer func(oldDir string) { + dir.UserConfigDir = oldDir + }(dir.UserConfigDir) + + t.Run("store does not exist", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + dir.UserConfigDir = "testdata" + expectedErrMsg := `stat testdata/truststore/x509/tsa/test: no such file or directory` + err := DeleteAllCerts("tsa", "test", true) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected err: %v, but got: %v", expectedErrMsg, err) + } + }) } diff --git a/cmd/notation/plugin/install.go b/cmd/notation/plugin/install.go index b7538fc16..d79924e10 100644 --- a/cmd/notation/plugin/install.go +++ b/cmd/notation/plugin/install.go @@ -132,7 +132,7 @@ func install(command *cobra.Command, opts *pluginInstallOpts) error { } pluginURL, err := url.Parse(opts.pluginSource) if err != nil { - return fmt.Errorf("failed to parse plugin download URL %s with error: %w", pluginURL, err) + return fmt.Errorf("failed to parse plugin download URL %s with error: %w", opts.pluginSource, err) } if pluginURL.Scheme != "https" { return fmt.Errorf("failed to download plugin from URL: only the HTTPS scheme is supported, but got %s", pluginURL.Scheme) diff --git a/cmd/notation/plugin/install_test.go b/cmd/notation/plugin/install_test.go new file mode 100644 index 000000000..6f583d7ed --- /dev/null +++ b/cmd/notation/plugin/install_test.go @@ -0,0 +1,96 @@ +// Copyright The Notary Project 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 plugin + +import ( + "context" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + notationplugin "github.com/notaryproject/notation/cmd/notation/internal/plugin" + "github.com/notaryproject/notation/internal/osutil" + "github.com/spf13/cobra" +) + +func TestInstall(t *testing.T) { + t.Run("invalid plugin source url", func(t *testing.T) { + opts := &pluginInstallOpts{ + pluginSourceType: notationplugin.PluginSourceTypeURL, + inputChecksum: "dummy", + pluginSource: "http://[::1]/%", + } + expectedErrMsg := `failed to parse plugin download URL http://[::1]/% with error: parse "http://[::1]/%": invalid URL escape "%"` + err := install(&cobra.Command{}, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected error %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("unknown plugin source type", func(t *testing.T) { + opts := &pluginInstallOpts{ + pluginSourceType: -1, + } + expectedErrMsg := `plugin installation failed: unknown plugin source type` + err := install(&cobra.Command{}, opts) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected error %s, but got %s", expectedErrMsg, err) + } + }) +} + +func TestInstallPlugin(t *testing.T) { + ctx := context.Background() + t.Run("input path does not exist", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + expectedErrMsg := `stat invalid: no such file or directory` + err := installPlugin(ctx, "invalid", "", false) + if err == nil || err.Error() != expectedErrMsg { + t.Fatalf("expected error %s, but got %s", expectedErrMsg, err) + } + }) + + t.Run("failed to get file type", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on Windows") + } + + tempDir := t.TempDir() + data := []byte("data") + filename := filepath.Join(tempDir, "a", "file.txt") + if err := osutil.WriteFile(filename, data); err != nil { + t.Fatal(err) + } + err := os.Chmod(tempDir, 0) + if err != nil { + t.Fatal(err) + } + defer func() { + err := os.Chmod(tempDir, 0700) + if err != nil { + t.Fatal(err) + } + }() + + expectedErrMsg := `permission denied` + err = installPlugin(ctx, filename, "", false) + if err == nil || !strings.Contains(err.Error(), expectedErrMsg) { + t.Fatalf("expected permission denied error, but got %s", err) + } + }) +}