diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 00000000..cf0e51cd --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,12 @@ +with-expecter: true +dir: "{{.InterfaceDir}}/mocks" +outpkg: "mocks" +packages: + github.com/rancher/cli/cliclient: + # place your package-specific config here + config: + interfaces: + # select the interfaces you want mocked + HTTPClienter: + # Modify package-level config for this specific interface (if applicable) + config: \ No newline at end of file diff --git a/README.md b/README.md index 3acba21d..20e2cb1d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ Pass credentials by replacing `` with your config file for the s To build `rancher/cli`, run `make`. To use a custom Docker repository, do `REPO=custom make`, which produces a `custom/cli` image. +## Contributing + +Mocks can ge generated by below command: + +`make mock` + ## Contact For bugs, questions, comments, corrections, suggestions, etc., open an issue in diff --git a/cliclient/cliclient.go b/cliclient/cliclient.go index e8fba559..b525bf4e 100644 --- a/cliclient/cliclient.go +++ b/cliclient/cliclient.go @@ -1,9 +1,13 @@ package cliclient import ( + "context" "errors" "fmt" + "net" + "net/http" "strings" + "time" errorsPkg "github.com/pkg/errors" "github.com/rancher/cli/config" @@ -25,6 +29,28 @@ type MasterClient struct { CAPIClient *capiClient.Client } +// HTTPClienter is a http.Client factory interface +type HTTPClienter interface { + New() *http.Client +} + +// DefaultHTTPClient stores the default http.Client factory +var DefaultHTTPClient HTTPClienter = &HTTPClient{} + +/* +TestingReplaceDefaultHTTPClient replaces DefaultHTTPClient for unit tests. +Call the returned function by defer keyword, for example: + + defer cliclient.TestingReplaceDefaultHTTPClient(mockClient)() +*/ +func TestingReplaceDefaultHTTPClient(fakeClient HTTPClienter) func() { + origHttpClient := DefaultHTTPClient + DefaultHTTPClient = fakeClient + return func() { + DefaultHTTPClient = origHttpClient + } +} + // NewMasterClient returns a new MasterClient with Cluster, Management and Project // clients populated func NewMasterClient(config *config.ServerConfig) (*MasterClient, error) { @@ -99,8 +125,30 @@ func NewProjectClient(config *config.ServerConfig) (*MasterClient, error) { return mc, nil } +var testingForceClientInsecure = false + +/* +TestingForceClientInsecure sets testForceClientInsecure to true for unit tests. +It's a workaround to github.com/rancher/norman/clientbase.NewAPIClient, +which replaces net/http.Client.Transport (including proxy and TLS config), +so the client TLS config of net/http/httptest.Server will be lost. +Call the returned function by defer keyword, for example: + + defer cliclient.TestingForceClientInsecure()() +*/ +func TestingForceClientInsecure() func() { + origTestForceClientInsecure := testingForceClientInsecure + testingForceClientInsecure = true + return func() { + testingForceClientInsecure = origTestForceClientInsecure + } +} + func (mc *MasterClient) newManagementClient() error { options := createClientOpts(mc.UserConfig) + if testingForceClientInsecure { + options.Insecure = true + } // Setup the management client mClient, err := managementClient.NewClient(options) @@ -181,10 +229,11 @@ func createClientOpts(config *config.ServerConfig) *clientbase.ClientOpts { } options := &clientbase.ClientOpts{ - URL: serverURL, - AccessKey: config.AccessKey, - SecretKey: config.SecretKey, - CACerts: config.CACerts, + HTTPClient: DefaultHTTPClient.New(), + URL: serverURL, + AccessKey: config.AccessKey, + SecretKey: config.SecretKey, + CACerts: config.CACerts, } return options } @@ -203,3 +252,27 @@ func CheckProject(s string) []string { return clustProj } + +type HTTPClient struct{} + +/* +HTTPClient.New makes http.Client including http.Transport, +with default values (for example: proxy) and custom timeouts. +See: https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/ +*/ +func (c *HTTPClient) New() *http.Client { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + dialer := &net.Dialer{ + Timeout: 5 * time.Second, + KeepAlive: 30 * time.Second, + } + return dialer.DialContext(ctx, network, addr) + } + transport.ResponseHeaderTimeout = 10 * time.Second + + return &http.Client{ + Transport: transport, + Timeout: time.Minute, // from github.com/rancher/norman/clientbase.NewAPIClient + } +} diff --git a/cliclient/cliclient_test.go b/cliclient/cliclient_test.go new file mode 100644 index 00000000..3e72937a --- /dev/null +++ b/cliclient/cliclient_test.go @@ -0,0 +1,101 @@ +package cliclient + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "reflect" + "runtime" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/rancher/cli/cliclient/mocks" + "github.com/rancher/cli/config" + "github.com/rancher/norman/types" +) + +func TestCreateClientOpts(t *testing.T) { + conf := &config.ServerConfig{ + URL: "https://a.b", + AccessKey: "AccessKey", + SecretKey: "SecretKey", + CACerts: "CACerts", + } + + clientOpts := createClientOpts(conf) + + assert.NotNil(t, clientOpts) + assert.NotNil(t, clientOpts.HTTPClient) + assert.NotNil(t, clientOpts.HTTPClient.Transport) + assert.Equal(t, "https://a.b/v3", clientOpts.URL) + assert.Equal(t, "AccessKey", clientOpts.AccessKey) + assert.Equal(t, "SecretKey", clientOpts.SecretKey) + assert.Equal(t, "CACerts", clientOpts.CACerts) +} + +func TestDefaultHTTPClient(t *testing.T) { + client := DefaultHTTPClient.New() + + assert.NotNil(t, client) + assert.Equal(t, time.Minute, client.Timeout) + + assert.NotNil(t, client.Transport) + transport, is := client.Transport.(*http.Transport) + assert.True(t, is) + assert.NotNil(t, transport) + assert.NotNil(t, transport.DialContext) + assert.NotNil(t, transport.Proxy) + assert.Equal(t, "net/http.ProxyFromEnvironment", runtime.FuncForPC(reflect.ValueOf(transport.Proxy).Pointer()).Name()) + assert.Equal(t, 10*time.Second, transport.ResponseHeaderTimeout) +} + +func TestMasterClientNewManagementClient(t *testing.T) { + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v3/settings/cacerts": + crt, _ := os.ReadFile("../test/ca-cert.pem") + resp := map[string]string{ + "name": "cacerts", + "value": string(crt), + } + respBody, _ := json.Marshal(resp) + _, _ = w.Write(respBody) + case "/v3": + schemaURL := url.URL{ + Scheme: "https", + Host: r.Host, + Path: r.URL.Path, + } + w.Header().Add("X-API-Schemas", schemaURL.String()) + schemas := &types.SchemaCollection{ + Data: []types.Schema{}, + } + respBody, _ := json.Marshal(schemas) + _, _ = w.Write(respBody) + default: + fmt.Println(r.URL.Path) + } + })) + defer server.Close() + server.StartTLS() + + fakeClient := server.Client() + mockClient := mocks.NewMockHTTPClienter(t) + mockClient.EXPECT().New().Return(fakeClient) + defer TestingReplaceDefaultHTTPClient(mockClient)() + defer TestingForceClientInsecure()() + + conf := &config.ServerConfig{ + URL: server.URL, + } + + mc, err := NewManagementClient(conf) + + assert.Nil(t, err) + assert.NotNil(t, mc) +} diff --git a/cliclient/mocks/mock_HTTPClienter.go b/cliclient/mocks/mock_HTTPClienter.go new file mode 100644 index 00000000..0bb8ad9a --- /dev/null +++ b/cliclient/mocks/mock_HTTPClienter.go @@ -0,0 +1,83 @@ +// Code generated by mockery v2.46.1. DO NOT EDIT. + +package mocks + +import ( + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// MockHTTPClienter is an autogenerated mock type for the HTTPClienter type +type MockHTTPClienter struct { + mock.Mock +} + +type MockHTTPClienter_Expecter struct { + mock *mock.Mock +} + +func (_m *MockHTTPClienter) EXPECT() *MockHTTPClienter_Expecter { + return &MockHTTPClienter_Expecter{mock: &_m.Mock} +} + +// New provides a mock function with given fields: +func (_m *MockHTTPClienter) New() *http.Client { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for New") + } + + var r0 *http.Client + if rf, ok := ret.Get(0).(func() *http.Client); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*http.Client) + } + } + + return r0 +} + +// MockHTTPClienter_New_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'New' +type MockHTTPClienter_New_Call struct { + *mock.Call +} + +// New is a helper method to define mock.On call +func (_e *MockHTTPClienter_Expecter) New() *MockHTTPClienter_New_Call { + return &MockHTTPClienter_New_Call{Call: _e.mock.On("New")} +} + +func (_c *MockHTTPClienter_New_Call) Run(run func()) *MockHTTPClienter_New_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockHTTPClienter_New_Call) Return(_a0 *http.Client) *MockHTTPClienter_New_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockHTTPClienter_New_Call) RunAndReturn(run func() *http.Client) *MockHTTPClienter_New_Call { + _c.Call.Return(run) + return _c +} + +// NewMockHTTPClienter creates a new instance of MockHTTPClienter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockHTTPClienter(t interface { + mock.TestingT + Cleanup(func()) +}) *MockHTTPClienter { + mock := &MockHTTPClienter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/cmd/kubectl_token.go b/cmd/kubectl_token.go index dfd8ece8..d0033397 100644 --- a/cmd/kubectl_token.go +++ b/cmd/kubectl_token.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/rancher/cli/cliclient" "github.com/rancher/cli/config" apiv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" managementClient "github.com/rancher/rancher/pkg/client/generated/management/v3" @@ -639,10 +640,9 @@ func getClient(skipVerify bool, caCerts string) (*http.Client, error) { return nil, err } - // clone the DefaultTransport to get the default values - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = tlsConfig - return &http.Client{Transport: transport}, nil + client := cliclient.DefaultHTTPClient.New() + client.Transport.(*http.Transport).TLSClientConfig = tlsConfig + return client, nil } func getTLSConfig(skipVerify bool, caCerts string) (*tls.Config, error) { diff --git a/cmd/kubectl_token_test.go b/cmd/kubectl_token_test.go index 1f8a28e8..840f9f75 100644 --- a/cmd/kubectl_token_test.go +++ b/cmd/kubectl_token_test.go @@ -114,3 +114,20 @@ var responseOK = `{ } ] }` + +func Test_getClient(t *testing.T) { + /* ca-cert.pem is generated by: + openssl genrsa 2048 > ca-key.pem + openssl req -new -x509 -nodes -days 365000 -key ca-key.pem -out ca-cert.pem + */ + client, err := getClient(false, "../test/ca-cert.pem") + + assert.Nil(t, err) + assert.NotNil(t, client) + assert.NotNil(t, client.Transport) + transport, is := client.Transport.(*http.Transport) + assert.True(t, is) + assert.NotNil(t, transport) + assert.NotNil(t, transport.TLSClientConfig) + assert.NotNil(t, transport.TLSClientConfig.RootCAs) +} diff --git a/cmd/login.go b/cmd/login.go index d7af6ff6..23188457 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -260,10 +260,8 @@ func getCertFromServer(ctx *cli.Context, cf *config.ServerConfig) (*cliclient.Ma req.SetBasicAuth(cf.AccessKey, cf.SecretKey) - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := &http.Client{Transport: tr} + client := cliclient.DefaultHTTPClient.New() + client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} res, err := client.Do(req) if err != nil { diff --git a/cmd/login_test.go b/cmd/login_test.go new file mode 100644 index 00000000..2decbfe6 --- /dev/null +++ b/cmd/login_test.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "encoding/json" + "flag" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/rancher/norman/types" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + + "github.com/rancher/cli/cliclient" + "github.com/rancher/cli/cliclient/mocks" + "github.com/rancher/cli/config" +) + +func Test_getCertFromServer(t *testing.T) { + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v3/settings/cacerts": + crt, _ := os.ReadFile("../test/ca-cert.pem") + resp := &CACertResponse{ + Name: "cacerts", + Value: string(crt), + } + respBody, _ := json.Marshal(resp) + _, _ = w.Write(respBody) + case "/v3": + schemaURL := url.URL{ + Scheme: "https", + Host: r.Host, + Path: r.URL.Path, + } + w.Header().Add("X-API-Schemas", schemaURL.String()) + schemas := &types.SchemaCollection{ + Data: []types.Schema{}, + } + respBody, _ := json.Marshal(schemas) + _, _ = w.Write(respBody) + default: + fmt.Println(r.URL.Path) + } + })) + defer server.Close() + server.StartTLS() + + fakeClient := server.Client() + mockClient := mocks.NewMockHTTPClienter(t) + mockClient.EXPECT().New().Return(fakeClient) + defer cliclient.TestingReplaceDefaultHTTPClient(mockClient)() + defer cliclient.TestingForceClientInsecure()() + + app := cli.NewApp() + appFlags := flag.NewFlagSet("flags", flag.ContinueOnError) + appFlags.Bool("skip-verify", true, "") + cliCtx := cli.NewContext(app, appFlags, nil) + conf := &config.ServerConfig{ + URL: server.URL, + } + + masterClient, err := getCertFromServer(cliCtx, conf) + + assert.Nil(t, err) + assert.NotNil(t, masterClient) +} diff --git a/cmd/ssh.go b/cmd/ssh.go index 53a0d859..1e3dcb6c 100644 --- a/cmd/ssh.go +++ b/cmd/ssh.go @@ -170,7 +170,7 @@ func getSSHKey(c *cliclient.MasterClient, link, nodeName string) ([]byte, string req.SetBasicAuth(c.UserConfig.AccessKey, c.UserConfig.SecretKey) req.Header.Add("Accept-Encoding", "zip") - client := &http.Client{} + client := cliclient.DefaultHTTPClient.New() if c.UserConfig.CACerts != "" { roots := x509.NewCertPool() @@ -178,12 +178,9 @@ func getSSHKey(c *cliclient.MasterClient, link, nodeName string) ([]byte, string if !ok { return []byte{}, "", err } - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: roots, - }, + client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{ + RootCAs: roots, } - client.Transport = tr } resp, err := client.Do(req) diff --git a/cmd/ssh_test.go b/cmd/ssh_test.go new file mode 100644 index 00000000..23b3ade8 --- /dev/null +++ b/cmd/ssh_test.go @@ -0,0 +1,81 @@ +package cmd + +import ( + "archive/zip" + "encoding/json" + "flag" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "os" + "testing" + + "github.com/rancher/norman/types" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + + "github.com/rancher/cli/cliclient" + "github.com/rancher/cli/cliclient/mocks" + "github.com/rancher/cli/config" +) + +func Test_getSSHKey(t *testing.T) { + server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/v3/settings/cacerts": + crt, _ := os.ReadFile("../test/ca-cert.pem") + resp := &CACertResponse{ + Name: "cacerts", + Value: string(crt), + } + respBody, _ := json.Marshal(resp) + _, _ = w.Write(respBody) + case "/v3": + schemaURL := url.URL{ + Scheme: "https", + Host: r.Host, + Path: r.URL.Path, + } + w.Header().Add("X-API-Schemas", schemaURL.String()) + schemas := &types.SchemaCollection{ + Data: []types.Schema{}, + } + respBody, _ := json.Marshal(schemas) + _, _ = w.Write(respBody) + case "/sshkeys": + zipWriter := zip.NewWriter(w) + idW, _ := zipWriter.Create("id_rsa") + _, _ = idW.Write([]byte("RSA")) + zipWriter.Close() + default: + fmt.Println(r.URL.Path) + } + })) + defer server.Close() + server.StartTLS() + + fakeClient := server.Client() + mockClient := mocks.NewMockHTTPClienter(t) + mockClient.EXPECT().New().Return(fakeClient) + defer cliclient.TestingReplaceDefaultHTTPClient(mockClient)() + defer cliclient.TestingForceClientInsecure()() + + app := cli.NewApp() + appFlags := flag.NewFlagSet("flags", flag.ContinueOnError) + appFlags.Bool("skip-verify", true, "") + cliCtx := cli.NewContext(app, appFlags, nil) + conf := &config.ServerConfig{ + URL: server.URL, + } + sshkeysURL, _ := url.JoinPath(server.URL, "sshkeys") + + masterClient, err := getCertFromServer(cliCtx, conf) + + assert.Nil(t, err) + assert.NotNil(t, masterClient) + + _, _, err = getSSHKey(masterClient, sshkeysURL, "node") + + assert.Nil(t, err) +} diff --git a/go.mod b/go.mod index 2f24ff05..b7220ebb 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect golang.org/x/net v0.27.0 // indirect diff --git a/go.sum b/go.sum index 57a4f9f6..80f43d7e 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/main.go b/main.go index 9e6564bf..4b9a0b65 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,9 @@ Version: {{.Version}} Options: {{range .Flags}}{{if .Hidden}}{{else}}{{.}} {{end}}{{end}}{{end}} +Environment variables: + HTTP_PROXY, HTTPS_PROXY and NO_PROXY: Proxy settings (or the lowercase versions thereof) + Commands: {{range .Commands}}{{.Name}}{{with .Aliases}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} {{end}} @@ -50,6 +53,8 @@ Commands:{{range .VisibleCategories}}{{if .Name}} Options: {{range .VisibleFlags}}{{.}} {{end}}{{end}} +Environment variables: + HTTP_PROXY, HTTPS_PROXY and NO_PROXY: Proxy settings (or the lowercase versions thereof) ` func main() { diff --git a/scripts/mock b/scripts/mock new file mode 100755 index 00000000..d3c6d757 --- /dev/null +++ b/scripts/mock @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +echo Mocking interfaces + +go install github.com/vektra/mockery/v2@v2.46.1 +mockery diff --git a/test/ca-cert.pem b/test/ca-cert.pem new file mode 100644 index 00000000..2b5c6f6a --- /dev/null +++ b/test/ca-cert.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDbTCCAlWgAwIBAgIUHwVDepw+DqZImGszYnKqkCEWRzswDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yNDA5MjgyMDMyMDdaGA8zMDI0 +MDEzMDIwMzIwN1owRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx +ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBALciVbcKQLbNvBdd7jiDV9PBB87m3Fi2bd3Hqk9m +3RjmCS5XOPaxiHggauJEIW2d2UpM17TnAEF2b9+Krvq8aJWqiz+z/tSAgR9T9tZa +9g/DdghXfGnkSqzV1GOPTg3Txnpqd3Jx6khx595d2mCTiz2+t4FIkVEg15ODIghN +ltMmMfYYyEdoPCn0+5p8WrGH30eqk+07pnPBgK+fLhE9DmztM3ZFhJEM7SDAyS3Q +wweK0P5HsWnVu/0uUyZzq5b9Ejy+Ft9BVkfJNbJGIQSwI1khi+8igIJQmk63kIng +Id7BANAWYB/591+LyXhv0JsArJDzlnOzl4wRaqM5JQ4JUw8CAwEAAaNTMFEwHQYD +VR0OBBYEFJhvhpmt9w2u7S/pQ9typlCG72R/MB8GA1UdIwQYMBaAFJhvhpmt9w2u +7S/pQ9typlCG72R/MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB +AKu5C0E6igRO93cSulJtP1lB8Ly1UGFuXZehiMvaZ2WImBtopKcOXNuj6Vrk6x/w +iPfTKEAwktT790j5YWEM0EridkudLgEmGom0eBP3sUWYyuqgFzqt5NtppEOLzAB6 +XtT6KvcEv2DYrH0OTcda4gJSSwvs4lt+P0U9f3k4UxQ2Ur8c4mPQw8R5LYzJ35Vs +wLMBkpqu4veclfLOjbNwq+fmM0hicQM98E0ILElJB6l0tav4pH9s3AuYeGZaW2dW +J/E2OJwifW5bcFiN7JAicdp2BOs0p3FI/oz0idiV/GcdtbbPRzJt3eWfJ+3eeWGi +F03gSFkkqAiVRxPlkrGy4RI= +-----END CERTIFICATE----- diff --git a/test/ca-key.pem b/test/ca-key.pem new file mode 100644 index 00000000..109a173c --- /dev/null +++ b/test/ca-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3IlW3CkC2zbwX +Xe44g1fTwQfO5txYtm3dx6pPZt0Y5gkuVzj2sYh4IGriRCFtndlKTNe05wBBdm/f +iq76vGiVqos/s/7UgIEfU/bWWvYPw3YIV3xp5Eqs1dRjj04N08Z6andycepIcefe +Xdpgk4s9vreBSJFRINeTgyIITZbTJjH2GMhHaDwp9PuafFqxh99HqpPtO6ZzwYCv +ny4RPQ5s7TN2RYSRDO0gwMkt0MMHitD+R7Fp1bv9LlMmc6uW/RI8vhbfQVZHyTWy +RiEEsCNZIYvvIoCCUJpOt5CJ4CHewQDQFmAf+fdfi8l4b9CbAKyQ85Zzs5eMEWqj +OSUOCVMPAgMBAAECggEADAkpJKZQr32g+CYtDbDFeRNteHHKwdfSK6pqLTK/otnH +eQYJe95vb3mt8fXz/njKeuwcRyA2xOv27p72NgtjkjvgT/emmVUNZ2ovcFIPwY8h +Nut3r/FC3ni6hIU6u4TovUs5qMoHfEBkMpnszBPA/yCwKG0vrmsf+pVlNgd8u0lh +tTnOTfT39j/g9E1tbC/B2aThJpEmwNjohg7wS/PqoQluVCkCWx/uysArnWfnYJFw +pWLCYATMXOIW4074TCSBvHre5Eelfqm3VtCpUCeomM5mDap/w790JhxIDO6FKuKE +z4Ur4YuWG2ZDEC/VyorpW3gPrUZw9IQu2ykPMBo+iQKBgQDT5ZX9nis3IfKPzG0C +m/vBFak2vPgoVALnu+Tq560JOD43h27bbXMw4zaQENRS+ZiwV6KgGKJA3cztbEfz +EKINxBROwqHP2J2I3j6WfuaCHtpxYSLb8K5lTW0rcIm8ZnBHMat0HSUoKuYOhOkX +keVHvzYMWkOztihr4eqY2hBOJwKBgQDdQDKKpJsfDRxQKBANyFltSlxA64mycon2 +/c3f+G0bWTGA1gEEhgnvbvaLAFjIvopenP36YNUS/XmkF2T4lH47ASa2qJABhvtf +m/3JN/Ay1m0DJybVwdU/rguDHH5IJOBM7Z3xi6eMxGQLxvIOJ6ovNxzvaFTDfr/L +O8oN7pDM2QKBgCd5gMzDl4do0phJXBF9X5Qj5ecloxFYPUZQcZ/1Uv4BtU2A9Hz6 +UdhMq9CA4h0nFHPdj5VFoYx2BYRBhNHaSmZIHgRiK+TdlNN3m69LqsOY3db7YpQY +rVR/7ROHnpEzgD0zGp7CAzcNbthLmGTksBMCox3//tbO247lgl/BWAJHAoGAMped +dwqQ1hbfZIzkruIjYNRiKA40HHQIjEwZKZ/bAyfwI+/Lp8cmunN7OOnJY43+gggQ +I5LE5mTDzfvzgMwj2UY4HgiJdW/yRziaXcrGyDk0EEaBoNuD8d3QkBiR39le2Ph0 +52jx1USLRA4oB4iWZ9mIak5HI7T2EnkHiQwGXHECgYEAyJD2arLAxLez+aPlDtfN +QWF5au72Zpgr7zu3xDoIfHwGeiaQX8tc+90qvDvSsnhsrOtWE/Th0ulTAjgon6aL ++idq5va2fkiEXssxtjTelMSaBGk+R44VFqK9gpgWTIKkdc2+iMycmHUtFJSsnSSJ +m/IugX+tIGn61kuojRYLtNc= +-----END PRIVATE KEY-----