From 9195741c0983d57088d6b80b8b71965469e5e170 Mon Sep 17 00:00:00 2001 From: Saad Karim Date: Tue, 14 Feb 2017 09:16:46 -0500 Subject: [PATCH] TLS testcases and process file names client config If a file name in the client config file is not absolute, it must be processed relative to the location of the config file. Added testcases to test TLS with the new CLI and config file. 3 scenarios tested: 1) Read from config file directly 2) Read from environment variable 3) Read from CLI flags Added new tags to EnrollmentRequest struct for reflection. Added support to be able to provide root CA certificate via command line option for TLS configuration on client side. https://jira.hyperledger.org/browse/FAB-1549 Change-Id: I4ffb52ab4b3b32befbc191a42d2a06dbdde5570e Signed-off-by: Saad Karim --- api/client.go | 12 +- cli/server/config.go | 2 +- cmd/fabric-ca-client/config.go | 63 ++++--- cmd/fabric-ca-client/enroll.go | 24 ++- cmd/fabric-ca-client/main.go | 18 +- cmd/fabric-ca-client/main_test.go | 229 +++++++++++++++++++++----- lib/client.go | 21 ++- lib/client_test.go | 2 +- lib/clientconfig.go | 9 +- lib/dbutil/dbutil.go | 4 +- lib/identity.go | 10 ++ lib/server_test.go | 46 +++++- lib/tls/tls.go | 45 +++-- lib/tls/tls_test.go | 32 ++-- testdata/cert.pem | 25 --- testdata/client-config.json | 11 +- testdata/client-config2.json | 6 - testdata/fabric-ca-client-config.yaml | 70 ++++++++ util/util.go | 2 +- 19 files changed, 474 insertions(+), 157 deletions(-) delete mode 100644 testdata/cert.pem delete mode 100644 testdata/client-config2.json create mode 100644 testdata/fabric-ca-client-config.yaml diff --git a/api/client.go b/api/client.go index ccb6f4d0b..7b29065a2 100644 --- a/api/client.go +++ b/api/client.go @@ -52,17 +52,17 @@ type RegistrationResponse struct { // EnrollmentRequest is a request to enroll an identity type EnrollmentRequest struct { // The identity name to enroll - Name string `json:"name"` + Name string `json:"name" skip:"true"` // The secret returned via Register - Secret string `json:"secret,omitempty"` + Secret string `json:"secret,omitempty" skip:"true"` // Hosts is a comma-separated host list in the CSR - Hosts string `json:"hosts,omitempty"` + Hosts string `json:"hosts,omitempty" help:"Comma-separated host list"` // Profile is the name of the signing profile to use in issuing the certificate - Profile string `json:"profile,omitempty"` + Profile string `json:"profile,omitempty" help:"Name of the signing profile to use in issuing the certificate"` // Label is the label to use in HSM operations - Label string `json:"label,omitempty"` + Label string `json:"label,omitempty" help:"Label to use in HSM operations"` // CSR is Certificate Signing Request info - CSR *CSRInfo `json:"csr,omitempty"` + CSR *CSRInfo `json:"csr,omitempty" help:"Certificate Signing Request info"` } // ReenrollmentRequest is a request to reenroll an identity. diff --git a/cli/server/config.go b/cli/server/config.go index a7976b21e..384b66b34 100644 --- a/cli/server/config.go +++ b/cli/server/config.go @@ -180,7 +180,7 @@ func configInit(cfg *cli.Config) { // Make TLS client files absolute func absTLSClient(cfg *tls.ClientTLSConfig) { for i := 0; i < len(cfg.CertFiles); i++ { - cfg.CertFiles[i] = abs(cfg.CertFiles[i]) + cfg.CertFilesList[i] = abs(cfg.CertFilesList[i]) } cfg.Client.CertFile = abs(cfg.Client.CertFile) cfg.Client.KeyFile = abs(cfg.Client.KeyFile) diff --git a/cmd/fabric-ca-client/config.go b/cmd/fabric-ca-client/config.go index a3ae46800..b76c0d6ef 100644 --- a/cmd/fabric-ca-client/config.go +++ b/cmd/fabric-ca-client/config.go @@ -26,6 +26,7 @@ import ( "github.com/cloudflare/cfssl/log" "github.com/hyperledger/fabric-ca/lib" + "github.com/hyperledger/fabric-ca/lib/tls" "github.com/hyperledger/fabric-ca/util" "github.com/spf13/viper" ) @@ -76,39 +77,39 @@ const ( ############################################################################# # URL of the Fabric-ca-server (default: http://localhost:7054) -serverURL: <<>> +URL: <<>> ############################################################################# # TLS section for the client's listenting port ############################################################################# tls: - # Enable TLS (default: false) - enabled: false + # Enable TLS (default: false) + enabled: false - # TLS for the client's listenting port (default: false) - certfiles: - client: - certfile: - keyfile: + # TLS for the client's listenting port (default: false) + certfiles: # Comma Separated (e.g. root.pem, root2.pem) + client: + certfile: + keyfile: ############################################################################# # Certificate Signing Request section for generating the CSR for # an enrollment certificate (ECert) ############################################################################# csr: - cn: <<>> - names: - - C: US - ST: "North Carolina" - L: - O: Hyperledger - OU: Fabric - hosts: - - <<>> - ca: - pathlen: - pathlenzero: - expiry: + cn: <<>> + names: + - C: US + ST: "North Carolina" + L: + O: Hyperledger + OU: Fabric + hosts: + - <<>> + ca: + pathlen: + pathlenzero: + expiry: ` ) @@ -157,12 +158,20 @@ func configInit() error { } // Unmarshal the config into 'clientCfg' - clientCfg = new(lib.ClientConfig) err = viper.Unmarshal(clientCfg) if err != nil { util.Fatal("Failed to unmarshall client config: %s", err) } + purl, err := url.Parse(clientCfg.URL) + if err != nil { + return err + } + + clientCfg.TLS.Enabled = purl.Scheme == "https" + + processCertFiles(&clientCfg.TLS) + return nil } @@ -194,3 +203,13 @@ func createDefaultConfigFile() error { // Now write the file return ioutil.WriteFile(cfgFileName, []byte(cfg), 0755) } + +// processCertFiles parses comma seperated string to generate a string array +func processCertFiles(cfg *tls.ClientTLSConfig) { + CertFiles := strings.Split(cfg.CertFiles, ",") + cfg.CertFilesList = make([]string, 0) + + for i := range CertFiles { + cfg.CertFilesList = append(cfg.CertFilesList, strings.TrimSpace(CertFiles[i])) + } +} diff --git a/cmd/fabric-ca-client/enroll.go b/cmd/fabric-ca-client/enroll.go index b938b33c4..869ab9e71 100644 --- a/cmd/fabric-ca-client/enroll.go +++ b/cmd/fabric-ca-client/enroll.go @@ -18,13 +18,13 @@ package main import ( "fmt" + "io/ioutil" "path/filepath" + "strings" "github.com/cloudflare/cfssl/log" - "github.com/hyperledger/fabric-ca/api" - "github.com/hyperledger/fabric-ca/lib" - "github.com/hyperledger/fabric-ca/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var ( @@ -59,22 +59,20 @@ func init() { func runEnroll() error { log.Debug("Entered Enroll") - user, pass, err := util.GetUser() + rawurl := viper.GetString("url") + ID, err := clientCfg.Enroll(rawurl, filepath.Dir(cfgFileName)) if err != nil { return err } - req := &api.EnrollmentRequest{ - Name: user, - Secret: pass, + cfgFile, err := ioutil.ReadFile(cfgFileName) + if err != nil { + return err } - client := lib.Client{ - HomeDir: filepath.Dir(cfgFileName), - Config: clientCfg, - } + cfg := strings.Replace(string(cfgFile), "<<>>", ID.GetName(), 1) - ID, err := client.Enroll(req) + err = ioutil.WriteFile(cfgFileName, []byte(cfg), 0644) if err != nil { return err } @@ -85,7 +83,7 @@ func runEnroll() error { } log.Infof("Enrollment information was successfully stored in %s and %s", - client.GetMyKeyFile(), client.GetMyCertFile()) + ID.GetMyKeyFile(), ID.GetMyCertFile()) return nil } diff --git a/cmd/fabric-ca-client/main.go b/cmd/fabric-ca-client/main.go index 8fb3d529f..4ee723966 100644 --- a/cmd/fabric-ca-client/main.go +++ b/cmd/fabric-ca-client/main.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/lib" "github.com/hyperledger/fabric-ca/util" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -38,6 +39,9 @@ var rootCmd = &cobra.Command{ } util.CmdRunBegin() + + log.Debugf("Client configuration settings: %+v", clientCfg) + return nil }, } @@ -54,8 +58,6 @@ func init() { viper.SetEnvPrefix(envVarPrefix) viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - url := util.GetServerURL() - host, err := os.Hostname() if err != nil { log.Error(err) @@ -64,10 +66,18 @@ func init() { // Set global flags used by all commands pflags := rootCmd.PersistentFlags() pflags.StringVarP(&cfgFileName, "config", "c", cfg, "Configuration file") - util.FlagString(pflags, "url", "u", url, "URL of the Fabric-ca server") util.FlagString(pflags, "myhost", "m", host, "Hostname to include in the certificate signing request during enrollment") - util.FlagBool(pflags, "debug", "d", false, "Enable debug logging") + + clientCfg = &lib.ClientConfig{} + tags := map[string]string{ + "help.csr.cn": "The common name field of the certificate signing request to a parent fabric-ca-server", + "help.csr.serialnumber": "The serial number in a certificate signing request to a parent fabric-ca-server", + } + err = util.RegisterFlags(pflags, clientCfg, tags) + if err != nil { + panic(err) + } } diff --git a/cmd/fabric-ca-client/main_test.go b/cmd/fabric-ca-client/main_test.go index 86f3b6259..3d2ccbc9d 100644 --- a/cmd/fabric-ca-client/main_test.go +++ b/cmd/fabric-ca-client/main_test.go @@ -17,39 +17,37 @@ limitations under the License. package main import ( - "fmt" "io/ioutil" "os" "path" "path/filepath" "strings" "testing" - "time" - "github.com/hyperledger/fabric-ca/cli/server" + "github.com/cloudflare/cfssl/csr" + "github.com/hyperledger/fabric-ca/lib" "github.com/hyperledger/fabric-ca/util" ) const ( testYaml = "test.yaml" myhost = "hostname" + certfile = "ec.pem" + keyfile = "ec-key.pem" + tdDir = "../../testdata" + db = "fabric-ca-server.db" ) var ( - defYaml string - tdDir = "../../testdata" - cfgFile = "testconfig.json" - fabricCADB = path.Join(tdDir, "fabric-ca.db") - clientConfig = path.Join(tdDir, "client-config.json") - rrFile = path.Join(tdDir, "registerrequest.json") - serverStarted bool - serverExitCode = 0 + defYaml string + fabricCADB = path.Join(tdDir, db) + rrFile = path.Join(tdDir, "registerrequest.json") ) // TestCreateDefaultConfigFile test to make sure default config file gets generated correctly func TestCreateDefaultConfigFile(t *testing.T) { defYaml = util.GetDefaultConfigFile("fabric-ca-client") - os.RemoveAll(defYaml) + os.Remove(defYaml) fabricCAServerURL := "http://localhost:7058" @@ -73,36 +71,71 @@ func TestCreateDefaultConfigFile(t *testing.T) { t.Error("Failed to update default config file with host name") } - os.RemoveAll(defYaml) + os.Remove(defYaml) } -func startServer() { - if !serverStarted { - os.Remove(fabricCADB) - serverStarted = true - fmt.Println("starting fabric-ca server ...") - go runServer() - time.Sleep(10 * time.Second) - fmt.Println("fabric-ca server started") - } else { - fmt.Println("fabric-ca server already started") +func TestClientCommandsNoTLS(t *testing.T) { + os.Remove(fabricCADB) + + srv := getServer() + srv.HomeDir = tdDir + + err := srv.RegisterBootstrapUser("admin", "adminpw", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + err = srv.RegisterBootstrapUser("admin2", "adminpw2", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + aff := make(map[string]interface{}) + aff["bank_a"] = "banks" + + srv.Config.Affiliations = aff + + err = srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + testEnroll(t) + testReenroll(t) + testRegister(t) + testRevoke(t) + testBogus(t) + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) } } -func runServer() { - os.Setenv("FABRIC_CA_DEBUG", "true") - s := new(server.Server) - s.ConfigDir = tdDir - s.ConfigFile = cfgFile - s.StartFromConfig = false - s.Start() +func getServer() *lib.Server { + return &lib.Server{ + HomeDir: ".", + Config: getServerConfig(), + } } -// TestEnroll tests fabric-ca-client enroll -func TestEnroll(t *testing.T) { - startServer() +func getServerConfig() *lib.ServerConfig { + return &lib.ServerConfig{ + Debug: true, + Port: 7054, + CA: lib.ServerConfigCA{ + Keyfile: keyfile, + Certfile: certfile, + }, + CSR: csr.CertificateRequest{ + CN: "TestCN", + }, + } +} +// TestEnroll tests fabric-ca-client enroll +func testEnroll(t *testing.T) { t.Log("Testing Enroll CMD") defYaml = util.GetDefaultConfigFile("fabric-ca-client") @@ -136,7 +169,7 @@ func TestEnroll(t *testing.T) { } // TestReenroll tests fabric-ca-client reenroll -func TestReenroll(t *testing.T) { +func testReenroll(t *testing.T) { t.Log("Testing Reenroll CMD") defYaml = util.GetDefaultConfigFile("fabric-ca-client") @@ -155,7 +188,7 @@ func TestReenroll(t *testing.T) { } // TestRegister tests fabric-ca-client register -func TestRegister(t *testing.T) { +func testRegister(t *testing.T) { t.Log("Testing Register CMD") defYaml = util.GetDefaultConfigFile("fabric-ca-client") @@ -164,14 +197,14 @@ func TestRegister(t *testing.T) { t.Error("Should have failed, no register request file provided") } - err = RunMain([]string{cmdName, "register", "-f", "../../testdata/registerrequest.json"}) + err = RunMain([]string{cmdName, "register", "-f", rrFile}) if err != nil { t.Errorf("client register -f failed: %s", err) } os.Remove(defYaml) // Delete default config file - err = RunMain([]string{cmdName, "register", "--url", "http://localhost:7055", "-f", "../../testdata/registerrequest.json"}) + err = RunMain([]string{cmdName, "register", "--url", "http://localhost:7055", "-f", rrFile}) if err == nil { t.Error("Should have failed, client config file should have incorrect port (7055) for server") } @@ -181,7 +214,7 @@ func TestRegister(t *testing.T) { } // TestRevoke tests fabric-ca-client revoke -func TestRevoke(t *testing.T) { +func testRevoke(t *testing.T) { t.Log("Testing Revoke CMD") defYaml = util.GetDefaultConfigFile("fabric-ca-client") @@ -210,15 +243,133 @@ func TestRevoke(t *testing.T) { } // TestBogus tests a negative test case -func TestBogus(t *testing.T) { +func testBogus(t *testing.T) { err := RunMain([]string{cmdName, "bogus"}) if err == nil { t.Errorf("client bogus passed but should have failed") } } +func TestClientCommandsUsingConfigFile(t *testing.T) { + os.Remove(fabricCADB) + + srv := getServer() + srv.Config.Debug = true + + err := srv.RegisterBootstrapUser("admin", "adminpw", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + srv.HomeDir = tdDir + srv.Config.TLS.Enabled = true + srv.Config.TLS.CertFile = "tls_server-cert.pem" + srv.Config.TLS.KeyFile = "tls_server-key.pem" + + err = srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + err = RunMain([]string{cmdName, "enroll", "-c", "../../testdata/fabric-ca-client-config.yaml", "-u", "https://admin:adminpw@localhost:7054", "-d"}) + if err != nil { + t.Errorf("client enroll -c -u failed: %s", err) + } + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) + } +} + +func TestClientCommandsTLSEnvVar(t *testing.T) { + os.Remove(fabricCADB) + + srv := getServer() + srv.Config.Debug = true + + err := srv.RegisterBootstrapUser("admin", "adminpw", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + err = srv.RegisterBootstrapUser("admin2", "adminpw2", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + srv.HomeDir = tdDir + srv.Config.TLS.Enabled = true + srv.Config.TLS.CertFile = "tls_server-cert.pem" + srv.Config.TLS.KeyFile = "tls_server-key.pem" + + err = srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + os.Setenv("FABRIC_CA_CLIENT_TLS_CERTFILES", "root.pem") + os.Setenv("FABRIC_CA_CLIENT_TLS_CLIENT_KEYFILE", "tls_client-key.pem") + os.Setenv("FABRIC_CA_CLIENT_TLS_CLIENT_CERTFILE", "tls_client-cert.pem") + + err = RunMain([]string{cmdName, "enroll", "-c", "../../testdata/test.yaml", "-u", "https://admin:adminpw@localhost:7054", "-d"}) + if err != nil { + t.Errorf("client enroll -c -u failed: %s", err) + } + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) + } + + os.Unsetenv("FABRIC_CA_CLIENT_TLS_CERTFILES") + os.Unsetenv("FABRIC_CA_CLIENT_TLS_CLIENT_KEYFILE") + os.Unsetenv("FABRIC_CA_CLIENT_TLS_CLIENT_CERTFILE") + +} + +func TestClientCommandsTLS(t *testing.T) { + os.Remove(fabricCADB) + + srv := getServer() + srv.Config.Debug = true + + err := srv.RegisterBootstrapUser("admin", "adminpw", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + err = srv.RegisterBootstrapUser("admin2", "adminpw2", "bank1") + if err != nil { + t.Errorf("Failed to register bootstrap user: %s", err) + } + + srv.HomeDir = tdDir + srv.Config.TLS.Enabled = true + srv.Config.TLS.CertFile = "tls_server-cert.pem" + srv.Config.TLS.KeyFile = "tls_server-key.pem" + + err = srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + err = RunMain([]string{cmdName, "enroll", "-c", "../../testdata/test.yaml", "--tls.certfiles", "root.pem", "--tls.client.keyfile", "tls_client-key.pem", "--tls.client.certfile", "tls_client-cert.pem", "-u", "https://admin:adminpw@localhost:7054", "-d"}) + if err != nil { + t.Errorf("client enroll -c -u failed: %s", err) + } + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) + } +} + func TestCleanUp(t *testing.T) { os.Remove("cert.pem") os.Remove("key.pem") + os.Remove("../../testdata/cert.pem") + os.Remove("../../testdata/key.pem") + os.Remove("../../testdata/test.yaml") os.Remove(fabricCADB) } diff --git a/lib/client.go b/lib/client.go index 71cac4e88..973e40973 100644 --- a/lib/client.go +++ b/lib/client.go @@ -290,13 +290,22 @@ func (c *Client) SendPost(req *http.Request) (interface{}, error) { reqStr := util.HTTPRequestToString(req) log.Debugf("Sending request\n%s", reqStr) - tlsConfig, err := tls.GetClientTLSConfig(&c.Config.TLS) - if err != nil { - return nil, fmt.Errorf("Failed to get client TLS config [%s]; not sending\n%s", err, reqStr) - } + var tr = new(http.Transport) + + if c.Config.TLS.Enabled { + log.Info("TLS Enabled") + + err := tls.AbsTLSClient(&c.Config.TLS, c.HomeDir) + if err != nil { + return nil, err + } + + tlsConfig, err := tls.GetClientTLSConfig(&c.Config.TLS) + if err != nil { + return nil, fmt.Errorf("Failed to get client TLS config: %s", err) + } - tr := &http.Transport{ - TLSClientConfig: tlsConfig, + tr.TLSClientConfig = tlsConfig } httpClient := &http.Client{Transport: tr} diff --git a/lib/client_test.go b/lib/client_test.go index 90118bc56..11c1cc5e3 100644 --- a/lib/client_test.go +++ b/lib/client_test.go @@ -37,7 +37,7 @@ var ( fcaDB = path.Join(tdDir, "fabric-ca.db") cfgFile = path.Join(tdDir, "config.json") testCfgFile = "testconfig.json" - clientConfig = path.Join(tdDir, "client-config2.json") + clientConfig = path.Join(tdDir, "client-config.json") csrFile = path.Join(tdDir, "csr.json") ) diff --git a/lib/clientconfig.go b/lib/clientconfig.go index 5df638f8b..1f3f65b66 100644 --- a/lib/clientconfig.go +++ b/lib/clientconfig.go @@ -19,15 +19,18 @@ package lib import ( "net/url" + "github.com/cloudflare/cfssl/csr" "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/lib/tls" ) // ClientConfig is the fabric-ca client's config type ClientConfig struct { - URL string `yaml:"url,omitempty"` - TLS tls.ClientTLSConfig `yaml:"tls,omitempty"` - Enrollment api.EnrollmentRequest `yaml:"enrollment,omitempty"` + Debug bool `def:"false" opt:"d" help:"Enable debug level logging"` + URL string `def:"http://localhost:7054" opt:"u" help:"URL of fabric-ca-server"` + TLS tls.ClientTLSConfig + Enrollment api.EnrollmentRequest + CSR csr.CertificateRequest } // Enroll a client given the server's URL and the client's home directory. diff --git a/lib/dbutil/dbutil.go b/lib/dbutil/dbutil.go index 556b33b36..f660895dd 100644 --- a/lib/dbutil/dbutil.go +++ b/lib/dbutil/dbutil.go @@ -102,8 +102,8 @@ func NewUserRegistryPostgres(datasource string, clientTLSConfig *tls.ClientTLSCo connStr := getConnStr(datasource) if clientTLSConfig != nil { - if len(clientTLSConfig.CertFiles) > 0 { - root := clientTLSConfig.CertFiles[0] + if len(clientTLSConfig.CertFilesList) > 0 { + root := clientTLSConfig.CertFilesList[0] connStr = fmt.Sprintf("%s sslrootcert=%s", connStr, root) } diff --git a/lib/identity.go b/lib/identity.go index 94bb30fb3..72f8bcbf5 100644 --- a/lib/identity.go +++ b/lib/identity.go @@ -219,3 +219,13 @@ func getDefaultBCCSPInstance() (bccsp.BCCSP, error) { return defaultBccsp, nil } + +// GetMyKeyFile returns the path to this identity's key file +func (i *Identity) GetMyKeyFile() string { + return i.client.GetMyKeyFile() +} + +// GetMyCertFile returns the path to this identity's key file +func (i *Identity) GetMyCertFile() string { + return i.client.GetMyCertFile() +} diff --git a/lib/server_test.go b/lib/server_test.go index 60a2c0fb6..9d0da2ec5 100644 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -26,6 +26,7 @@ import ( "github.com/hyperledger/fabric-ca/api" "github.com/hyperledger/fabric-ca/lib" + "github.com/hyperledger/fabric-ca/lib/tls" ) const ( @@ -33,6 +34,7 @@ const ( rootDir = "rootDir" intermediatePort = 7056 intermediateDir = "intDir" + testdataDir = "../testdata" ) func TestServerInit(t *testing.T) { @@ -171,6 +173,41 @@ func TestIntermediateServer(t *testing.T) { t.Errorf("Root server stop failed: %s", err) } } +func TestRunningTLSServer(t *testing.T) { + srv := getServer(rootPort, testdataDir, "", t) + + srv.Config.TLS.Enabled = true + srv.Config.TLS.CertFile = "../testdata/tls_server-cert.pem" + srv.Config.TLS.KeyFile = "../testdata/tls_server-key.pem" + + err := srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + clientConfig := &lib.ClientConfig{ + URL: fmt.Sprintf("https://localhost:%d", rootPort), + TLS: tls.ClientTLSConfig{ + CertFilesList: []string{"../testdata/root.pem"}, + Client: tls.KeyCertFiles{ + KeyFile: "../testdata/tls_client-key.pem", + CertFile: "../testdata/tls_client-cert.pem", + }, + }, + } + + rawURL := fmt.Sprintf("https://admin:adminpw@localhost:%d", rootPort) + + _, err = clientConfig.Enroll(rawURL, testdataDir) + if err != nil { + t.Errorf("Failed to enroll over TLS: %s", err) + } + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) + } +} func testIntermediateServer(idx int, t *testing.T) { // Init the intermediate server @@ -193,6 +230,9 @@ func testIntermediateServer(idx int, t *testing.T) { } func TestEnd(t *testing.T) { + os.Remove("../testdata/ca-cert.pem") + os.Remove("../testdata/ca-key.pem") + os.Remove("../testdata/fabric-ca-server.db") os.RemoveAll(rootDir) os.RemoveAll(intermediateDir) } @@ -214,7 +254,9 @@ func getIntermediateServer(idx int, t *testing.T) *lib.Server { } func getServer(port int, home, parentURL string, t *testing.T) *lib.Server { - os.RemoveAll(home) + if home != testdataDir { + os.RemoveAll(home) + } affiliations := map[string]interface{}{ "hyperledger": map[string]interface{}{ "fabric": []string{"ledger", "orderer", "security"}, @@ -253,6 +295,6 @@ func getIntermediateClient() *lib.Client { func getTestClient(port int) *lib.Client { return &lib.Client{ Config: &lib.ClientConfig{URL: fmt.Sprintf("http://localhost:%d", port)}, - HomeDir: "../testdata", + HomeDir: testdataDir, } } diff --git a/lib/tls/tls.go b/lib/tls/tls.go index 73d2a7e76..53cf0e0ef 100644 --- a/lib/tls/tls.go +++ b/lib/tls/tls.go @@ -19,10 +19,12 @@ package tls import ( "crypto/tls" "crypto/x509" + "errors" "fmt" "io/ioutil" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/util" ) // ServerTLSConfig defines key material for a TLS server @@ -34,9 +36,10 @@ type ServerTLSConfig struct { // ClientTLSConfig defines the key material for a TLS client type ClientTLSConfig struct { - Enabled bool `help:"Enable TLS for client connection"` - CertFiles []string `help:"PEM-encoded list of trusted certificate files"` - Client KeyCertFiles + Enabled bool `help:"Enable TLS for client connection"` + CertFiles string `help:"PEM-encoded comma separated list of trusted certificate files (e.g. root1.pem, root2.pem)"` + CertFilesList []string + Client KeyCertFiles } // KeyCertFiles defines the files need for client on TLS @@ -47,9 +50,6 @@ type KeyCertFiles struct { // GetClientTLSConfig creates a tls.Config object from certs and roots func GetClientTLSConfig(cfg *ClientTLSConfig) (*tls.Config, error) { - //if !cfg.Enabled { - // return nil, nil - //} var certs []tls.Certificate log.Debugf("CA Files: %s\n", cfg.CertFiles) @@ -57,18 +57,18 @@ func GetClientTLSConfig(cfg *ClientTLSConfig) (*tls.Config, error) { log.Debugf("Client Key File: %s\n", cfg.Client.KeyFile) clientCert, err := tls.LoadX509KeyPair(cfg.Client.CertFile, cfg.Client.KeyFile) if err != nil { - log.Debugf("Client Cert or Key not provided, if server requires mutual TLS, the connection will fail [error: %s]", err) + log.Debugf("Client Cert or Key not provided, if server requires mutual TLS, the connection will fail: %s", err) } certs = append(certs, clientCert) rootCAPool := x509.NewCertPool() - if len(cfg.CertFiles) == 0 { - log.Debug("No CA cert files provided. If server requires TLS, connection will fail") + if len(cfg.CertFilesList) == 0 { + return nil, errors.New("No CA certificate files provided") } - for _, cacert := range cfg.CertFiles { + for _, cacert := range cfg.CertFilesList { caCert, err := ioutil.ReadFile(cacert) if err != nil { return nil, err @@ -86,3 +86,28 @@ func GetClientTLSConfig(cfg *ClientTLSConfig) (*tls.Config, error) { return config, nil } + +// AbsTLSClient makes TLS client files absolute +func AbsTLSClient(cfg *ClientTLSConfig, configDir string) error { + var err error + + for i := 0; i < len(cfg.CertFilesList); i++ { + cfg.CertFilesList[i], err = util.MakeFileAbs(cfg.CertFilesList[i], configDir) + if err != nil { + return err + } + + } + + cfg.Client.CertFile, err = util.MakeFileAbs(cfg.Client.CertFile, configDir) + if err != nil { + return err + } + + cfg.Client.KeyFile, err = util.MakeFileAbs(cfg.Client.KeyFile, configDir) + if err != nil { + return err + } + + return nil +} diff --git a/lib/tls/tls_test.go b/lib/tls/tls_test.go index 0acdbd61a..9ab72037e 100644 --- a/lib/tls/tls_test.go +++ b/lib/tls/tls_test.go @@ -16,26 +16,34 @@ limitations under the License. package tls -import ( - "encoding/json" - "io/ioutil" - "testing" +import "testing" + +const ( + configDir = "../../testdata" + caCert = "root.pem" + certFile = "tls_client-cert.pem" + keyFile = "tls_client-key.pem" ) -const clientConfig = "../../testdata/client-config.json" +type testTLSConfig struct { + TLS *ClientTLSConfig +} func TestGetClientTLSConfig(t *testing.T) { - tlsConfig, err := ioutil.ReadFile(clientConfig) - if err != nil { - t.Errorf("Failed to read in TLS configuration file [error: %s]", err) + + cfg := &ClientTLSConfig{ + CertFilesList: []string{"root.pem"}, + Client: KeyCertFiles{ + KeyFile: "tls_client-key.pem", + CertFile: "tls_client-cert.pem", + }, } - var cfg = new(ClientTLSConfig) - json.Unmarshal(tlsConfig, cfg) + AbsTLSClient(cfg, configDir) - _, err = GetClientTLSConfig(cfg) + _, err := GetClientTLSConfig(cfg) if err != nil { - t.Errorf("Failed to get TLS Config [error: %s]", err) + t.Errorf("Failed to get TLS Config: %s", err) } } diff --git a/testdata/cert.pem b/testdata/cert.pem deleted file mode 100644 index cdc039804..000000000 --- a/testdata/cert.pem +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIJAPjglM6s674UMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV -BAYTAlVTMQswCQYDVQQIEwJuYzEPMA0GA1UEBxMGRHVyaGFtMSEwHwYDVQQKExhJ -bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDDAKBgNVBAsTA1NXRzEQMA4GA1UEAxMH -YWJjLmNvbTAeFw0xNjA5MzAxNzU4MjZaFw0xNzA5MzAxNzU4MjZaMG4xCzAJBgNV -BAYTAlVTMQswCQYDVQQIEwJuYzEPMA0GA1UEBxMGRHVyaGFtMSEwHwYDVQQKExhJ -bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDDAKBgNVBAsTA1NXRzEQMA4GA1UEAxMH -YWJjLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL5vRZQTeAP/ -auw9FZ75tCq7Lpuy88sRsFmDzgAWzg3WEGTd0QOXFNInHx3O3SU6Va4fftuee78B -OVmAcVChRO6qsPO72E097msG9N1s5gCz0HmMPGGTKXaxQtnsVicqmNrVesxj91vM -fdVQiUsNt5QUoEhqUk/OgGq3Y71WPTB6Lbj0mWscUtSGJsQrwK6BivvPsamPHPTl -fOaRitwccuMKkgUuRDd1KVwqCwMabwR81tONffyoF+poF2pB6rw1Ku2jT1pEAL8K -BjA4Bf5j2Od6nRcVqwVuRWalPWBxVGc527qQGK8hMpbDHjAirJhTatugoQwIbyuD -rsd0qGINFQsCAwEAAaOB0zCB0DAdBgNVHQ4EFgQUsH/ZTJS5vMBlOhZmBFMtoToL -hZcwgaAGA1UdIwSBmDCBlYAUsH/ZTJS5vMBlOhZmBFMtoToLhZehcqRwMG4xCzAJ -BgNVBAYTAlVTMQswCQYDVQQIEwJuYzEPMA0GA1UEBxMGRHVyaGFtMSEwHwYDVQQK -ExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDDAKBgNVBAsTA1NXRzEQMA4GA1UE -AxMHYWJjLmNvbYIJAPjglM6s674UMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBAHkkwZto8jxz609UrzLNi0vrf4fW1pFh8yBNRmMFAwMBNE1vEjAwxvMy -aq6Eszt17JMlR84dOYM73dg9c6BoHrNHCbZ+J+p8CN7D35Po8auOQ/l2YeUIrPe2 -ekMH2clbIoFi81OTcoMbuhUdD4S1jc4hEWdvgAIbfCcEuT3fZrfba1AMjLaobR9/ -vyOQbqXLbrFzZa3wmwF3C2GBJyDV9XnHRWvSH7Q5myjsAfHbbBO4FcoxIQ53zvzk -9kjEOqgC4qvgjlpw4od2vZ2xxCalYpLckT4lL/SOWzHk+7vHZFqB0wWDsWaEk/Hp -Wrdm7D4UEdTdvU29u4gW8koAfTLUYEE= ------END CERTIFICATE----- diff --git a/testdata/client-config.json b/testdata/client-config.json index bcc10587e..5995241eb 100644 --- a/testdata/client-config.json +++ b/testdata/client-config.json @@ -1,8 +1,11 @@ { "serverURL":"http://localhost:7054", - "certfiles":["../../testdata/root.pem"], - "client": { - "keyfile":"../../testdata/tls_client-key.pem", - "certfile":"../../testdata/tls_client-cert.pem" + "tls":{ + "enabled":false, + "ca_certfiles":["root.pem"], + "client":{ + "keyfile":"tls_client-key.pem", + "certfile":"tls_client-cert.pem" + } } } diff --git a/testdata/client-config2.json b/testdata/client-config2.json deleted file mode 100644 index 698f3c883..000000000 --- a/testdata/client-config2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "serverURL":"http://localhost:7054", - "ca_certfiles":["../testdata/root.pem"], - "keyfile":"../testdata/tls_client-key.pem", - "certfile":"../testdata/tls_client-cert.pem" -} diff --git a/testdata/fabric-ca-client-config.yaml b/testdata/fabric-ca-client-config.yaml new file mode 100644 index 000000000..a953ba256 --- /dev/null +++ b/testdata/fabric-ca-client-config.yaml @@ -0,0 +1,70 @@ + +############################################################################# +# This is a configuration file for the fabric-ca-client command. +# +# COMMAND LINE ARGUMENTS AND ENVIRONMENT VARIABLES +# ------------------------------------------------ +# Each configuration element can be overridden via command line +# arguments or environment variables. The precedence for determining +# the value of each element is as follows: +# 1) command line argument +# Examples: +# a) --url https://localhost:7054 +# To set the fabric-ca server url +# 2) environment variable +# Examples: +# a) FABRIC_CA_CLIENT_URL=https://localhost:7054 +# To set the fabric-ca server url +# 3) configuration file +# 4) default value (if there is one) +# All default values are shown beside each element below. +# +# FILE NAME ELEMENTS +# ------------------ +# All filename elements below end with the word "file". +# For example, see "certfile" and "keyfile" in the "ca" section. +# The value of each filename element can be a simple filename, a +# relative path, or an absolute path. If the value is not an +# absolute path, it is interpretted as being relative to the location +# of this configuration file. +# +############################################################################# + +############################################################################# +# Client Configuration +############################################################################# + +# URL of the Fabric-ca-server (default: http://localhost:7054) +URL: http://localhost:7054 + +############################################################################# +# TLS section for the client's listenting port +############################################################################# +tls: + # Enable TLS (default: false) + enabled: false + + # TLS for the client's listenting port (default: false) + certfiles: root.pem # Comma Separated (e.g. root.pem, root2.pem) + client: + certfile: tls_client-cert.pem + keyfile: tls_client-key.pem + +############################################################################# +# Certificate Signing Request section for generating the CSR for +# an enrollment certificate (ECert) +############################################################################# +csr: + cn: admin + names: + - C: US + ST: "North Carolina" + L: + O: Hyperledger + OU: Fabric + hosts: + - saads-mbp.raleigh.ibm.com + ca: + pathlen: + pathlenzero: + expiry: diff --git a/util/util.go b/util/util.go index adb9d96f0..7e748cb7d 100644 --- a/util/util.go +++ b/util/util.go @@ -384,7 +384,7 @@ func HTTPResponseToString(resp *http.Response) string { // CreateClientHome will create a home directory if it does not exist func CreateClientHome() (string, error) { log.Debug("CreateHome") - home := filepath.Join(GetDefaultConfigFile("fabric-ca-client")) + home := filepath.Dir(GetDefaultConfigFile("fabric-ca-client")) if _, err := os.Stat(home); err != nil { if os.IsNotExist(err) {