From e36f8838c689a93dd7572b7a1f150088c20d8d19 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Wed, 9 Oct 2024 00:54:26 +0800 Subject: [PATCH 1/3] fix: github actions permissions (#1059) Fix: - permission for `stale` and `license-checker` - replace tagged version with pinned version for `stale` action --------- Signed-off-by: Junjie Gao Signed-off-by: Patrick Zheng --- .github/workflows/license-checker.yml | 5 +---- .github/workflows/stale.yml | 9 +++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/license-checker.yml b/.github/workflows/license-checker.yml index 54e121173..ead1dcdd3 100644 --- a/.github/workflows/license-checker.yml +++ b/.github/workflows/license-checker.yml @@ -28,7 +28,4 @@ permissions: jobs: check-license: - permissions: - contents: write - pull-requests: write - uses: notaryproject/notation-core-go/.github/workflows/reusable-license-checker.yml@main \ No newline at end of file + uses: notaryproject/notation-core-go/.github/workflows/reusable-license-checker.yml@main diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 3bdbf430e..ea4d822c6 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -16,11 +16,17 @@ on: schedule: - cron: "30 1 * * *" +permissions: + contents: read + jobs: stale: + permissions: + issues: write + pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: stale-issue-message: "This issue is stale because it has been opened for 60 days with no activity. Remove stale label or comment. Otherwise, it will be closed in 30 days." stale-pr-message: "This PR is stale because it has been opened for 45 days with no activity. Remove stale label or comment. Otherwise, it will be closed in 30 days." @@ -31,4 +37,3 @@ jobs: days-before-issue-close: 30 days-before-pr-close: 30 exempt-all-milestones: true - From 18cc27e29082de54ee8cf4a3ab2a1143019cf8c6 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 15 Oct 2024 07:59:46 +0800 Subject: [PATCH 2/3] fix: fix debug log (#1061) Signed-off-by: Patrick Zheng --- internal/trace/context.go | 1 + internal/trace/transport.go | 42 +++++++++++++++++++++++--------- test/e2e/suite/command/verify.go | 2 +- test/e2e/suite/plugin/verify.go | 10 ++++---- 4 files changed, 37 insertions(+), 18 deletions(-) diff --git a/internal/trace/context.go b/internal/trace/context.go index 8563d73ee..322696062 100644 --- a/internal/trace/context.go +++ b/internal/trace/context.go @@ -48,6 +48,7 @@ func WithLoggerLevel(ctx context.Context, level logrus.Level) context.Context { // create logger logger := logrus.New() + formatter.DisableQuote = true logger.SetFormatter(&formatter) logger.SetLevel(level) diff --git a/internal/trace/transport.go b/internal/trace/transport.go index 6d6f9546c..5c631c62b 100644 --- a/internal/trace/transport.go +++ b/internal/trace/transport.go @@ -31,13 +31,27 @@ package trace import ( "context" + "fmt" "net/http" "strings" + "sync/atomic" "github.com/notaryproject/notation-go/log" "github.com/sirupsen/logrus" ) +var ( + // requestCount records the number of logged request-response pairs and will + // be used as the unique id for the next pair. + requestCount uint64 + + // toScrub is a set of headers that should be scrubbed from the log. + toScrub = []string{ + "Authorization", + "Set-Cookie", + } +) + // Transport is an http.RoundTripper that keeps track of the in-flight // request and add hooks to report HTTP tracing events. type Transport struct { @@ -50,39 +64,43 @@ func NewTransport(base http.RoundTripper) *Transport { // RoundTrip calls base roundtrip while keeping track of the current request. func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error) { + id := atomic.AddUint64(&requestCount, 1) - 1 ctx := req.Context() e := log.GetLogger(ctx) - e.Debugf("> Request: %q %q", req.Method, req.URL) - e.Debugf("> Request headers:") - logHeader(req.Header, e) + // log the request + e.Debugf("Request #%d\n> Request: %q %q\n> Request headers:\n%s", + id, req.Method, req.URL, logHeader(req.Header)) + // log the response resp, err = t.RoundTripper.RoundTrip(req) if err != nil { e.Errorf("Error in getting response: %w", err) } else if resp == nil { e.Errorf("No response obtained for request %s %q", req.Method, req.URL) } else { - e.Debugf("< Response status: %q", resp.Status) - e.Debugf("< Response headers:") - logHeader(resp.Header, e) + e.Debugf("Response #%d\n< Response status: %q\n< Response headers:\n%s", + id, resp.Status, logHeader(resp.Header)) } return resp, err } // logHeader prints out the provided header keys and values, with auth header // scrubbed. -func logHeader(header http.Header, e log.Logger) { +func logHeader(header http.Header) string { if len(header) > 0 { + headers := []string{} for k, v := range header { - if strings.EqualFold(k, "Authorization") { - v = []string{"*****"} + for _, h := range toScrub { + if strings.EqualFold(k, h) { + v = []string{"*****"} + } } - e.Debugf(" %q: %q", k, strings.Join(v, ", ")) + headers = append(headers, fmt.Sprintf(" %q: %q", k, strings.Join(v, ", "))) } - } else { - e.Debugf(" Empty header") + return strings.Join(headers, "\n") } + return " Empty header" } // SetHTTPDebugLog sets up http debug log with logrus.Logger diff --git a/test/e2e/suite/command/verify.go b/test/e2e/suite/command/verify.go index 239b363b2..b0efdceee 100644 --- a/test/e2e/suite/command/verify.go +++ b/test/e2e/suite/command/verify.go @@ -253,7 +253,7 @@ var _ = Describe("notation verify", func() { notation.Exec("verify", artifact.ReferenceWithDigest(), "-v"). MatchKeyWords(VerifySuccessfully). - MatchErrKeyWords("Timestamp verification disabled: verifyTimestamp is set to \\\"afterCertExpiry\\\" and signing cert chain unexpired") + MatchErrKeyWords("Timestamp verification disabled: verifyTimestamp is set to \"afterCertExpiry\" and signing cert chain unexpired") }) }) }) diff --git a/test/e2e/suite/plugin/verify.go b/test/e2e/suite/plugin/verify.go index 51b76f7a7..520d5cf0b 100644 --- a/test/e2e/suite/plugin/verify.go +++ b/test/e2e/suite/plugin/verify.go @@ -46,7 +46,7 @@ var _ = Describe("notation plugin verify", func() { MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", - `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). + `{"verificationResults":{"SIGNATURE_VERIFIER.REVOCATION_CHECK":{"success":true},"SIGNATURE_VERIFIER.TRUSTED_IDENTITY":{"success":true}},"processedAttributes":null}`). MatchKeyWords(VerifySuccessfully) }) }) @@ -77,7 +77,7 @@ var _ = Describe("notation plugin verify", func() { MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", - `revocation check by verification plugin \"e2e-plugin\" failed with reason \"revocation check failed\"`, + `revocation check by verification plugin "e2e-plugin" failed with reason "revocation check failed"`, VerifyFailed) }) }) @@ -108,7 +108,7 @@ var _ = Describe("notation plugin verify", func() { MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", - `trusted identify verification by plugin \"e2e-plugin\" failed with reason \"trusted identity check failed\"`, + `trusted identify verification by plugin "e2e-plugin" failed with reason "trusted identity check failed"`, VerifyFailed) }) }) @@ -138,7 +138,7 @@ var _ = Describe("notation plugin verify", func() { MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", - `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). + `{"verificationResults":{"SIGNATURE_VERIFIER.REVOCATION_CHECK":{"success":true},"SIGNATURE_VERIFIER.TRUSTED_IDENTITY":{"success":true}},"processedAttributes":null}`). MatchKeyWords(VerifySuccessfully) }) }) @@ -197,7 +197,7 @@ var _ = Describe("notation plugin verify", func() { MatchErrKeyWords( "Plugin verify-signature request", "Plugin verify-signature response", - `{\"verificationResults\":{\"SIGNATURE_VERIFIER.REVOCATION_CHECK\":{\"success\":true},\"SIGNATURE_VERIFIER.TRUSTED_IDENTITY\":{\"success\":true}},\"processedAttributes\":null}`). + `{"verificationResults":{"SIGNATURE_VERIFIER.REVOCATION_CHECK":{"success":true},"SIGNATURE_VERIFIER.TRUSTED_IDENTITY":{"success":true}},"processedAttributes":null}`). MatchKeyWords(VerifySuccessfully) }) }) From 1e838d5fa2a5becfc74f44f9401f28d1ebfce287 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Sat, 2 Nov 2024 01:39:43 +0800 Subject: [PATCH 3/3] test: add unit tests (#1075) This PR intends to add more unit tests to increase the code coverage. --------- Signed-off-by: Patrick Zheng --- .github/.codecov.yml | 5 +- .../truststore/testdata/NotationTestRoot.pem | 27 +++++ .../internal/truststore/testdata/invalid.txt | 1 + .../truststore/testdata/self-signed.crt | 20 ++++ .../truststore/x509/ca/test/self-signed.crt | 20 ++++ .../internal/truststore/truststore.go | 2 +- .../internal/truststore/truststore_test.go | 106 ++++++++++++++++-- cmd/notation/plugin/install.go | 2 +- cmd/notation/plugin/install_test.go | 96 ++++++++++++++++ 9 files changed, 269 insertions(+), 10 deletions(-) create mode 100644 cmd/notation/internal/truststore/testdata/NotationTestRoot.pem create mode 100644 cmd/notation/internal/truststore/testdata/invalid.txt create mode 100644 cmd/notation/internal/truststore/testdata/self-signed.crt create mode 100644 cmd/notation/internal/truststore/testdata/truststore/x509/ca/test/self-signed.crt create mode 100644 cmd/notation/plugin/install_test.go 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) + } + }) +}