github.com/silveraid/fabric-ca@v1.1.0-preview.0.20180127000700-71974f53ab08/cmd/fabric-ca-server/main_test.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8                   http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"crypto/x509"
    21  	"errors"
    22  	"fmt"
    23  	"io/ioutil"
    24  	"os"
    25  	"path"
    26  	"path/filepath"
    27  	"regexp"
    28  	"testing"
    29  
    30  	"github.com/hyperledger/fabric-ca/api"
    31  	"github.com/hyperledger/fabric-ca/lib"
    32  	"github.com/hyperledger/fabric-ca/lib/metadata"
    33  	"github.com/hyperledger/fabric-ca/util"
    34  	"github.com/stretchr/testify/assert"
    35  )
    36  
    37  const (
    38  	initYaml    = "i.yaml"
    39  	startYaml   = "s.yaml"
    40  	ldapTestDir = "ldapTestDir"
    41  )
    42  
    43  var (
    44  	longUserName = util.RandomString(1025)
    45  )
    46  
    47  var (
    48  	longFileName = util.RandomString(261)
    49  )
    50  
    51  // Create a config element in unexpected format
    52  var badSyntaxYaml = "bad.yaml"
    53  
    54  // Unsupported file type
    55  var unsupportedFileType = "config.txt"
    56  
    57  type TestData struct {
    58  	input    []string // input
    59  	expected string   // expected result
    60  }
    61  
    62  // checkTest validates success cases
    63  func checkTest(in *TestData, t *testing.T) {
    64  	os.Args = in.input
    65  	scmd := NewCommand(in.input[1], blockingStart)
    66  	// Execute the command
    67  	err := scmd.Execute()
    68  	if err != nil {
    69  		t.Errorf("FAILED:\n \tin: %v;\n \tout: %v\n \texpected: SUCCESS\n", in.input, err.Error())
    70  	} else {
    71  		signingProfile := scmd.cfg.CAcfg.Signing.Default
    72  		ku, eku, unk := signingProfile.Usages()
    73  		// expected key usage is digital signature
    74  		assert.Equal(t, x509.KeyUsageDigitalSignature, ku, "Expected KeyUsageDigitalSignature")
    75  		assert.Equal(t, 0, len(eku), "Found %d extended usages but expected 0", len(eku))
    76  		assert.Equal(t, 0, len(unk), "Found %d unknown key usages", len(unk))
    77  	}
    78  }
    79  
    80  // errorTest validates error cases
    81  func errorTest(in *TestData, t *testing.T) {
    82  	err := RunMain(in.input)
    83  	if err != nil {
    84  		matched, _ := regexp.MatchString(in.expected, err.Error())
    85  		if !matched {
    86  			t.Errorf("FAILED:\n \tin: %v;\n \tout: %v;\n \texpected: %v\n", in.input, err.Error(), in.expected)
    87  		}
    88  	} else {
    89  		t.Errorf("FAILED:\n \tin: %v;\n \tout: <nil>\n \texpected: %v\n", in.input, in.expected)
    90  	}
    91  }
    92  
    93  func TestMain(m *testing.M) {
    94  	metadata.Version = "1.1.0"
    95  	os.Exit(m.Run())
    96  }
    97  
    98  func TestNoArguments(t *testing.T) {
    99  	err := RunMain([]string{cmdName})
   100  	if err == nil {
   101  		assert.Error(t, errors.New("Should have resulted in an error as no agruments provided"))
   102  	}
   103  }
   104  
   105  func TestErrors(t *testing.T) {
   106  	os.Unsetenv(homeEnvVar)
   107  	_ = ioutil.WriteFile(badSyntaxYaml, []byte("signing: true\n"), 0644)
   108  
   109  	errorCases := []TestData{
   110  		{[]string{cmdName, "init", "-c", initYaml}, "option is required"},
   111  		{[]string{cmdName, "init", "-c", initYaml, "-n", "acme.com", "-b", "user::"}, "Failed to read"},
   112  		{[]string{cmdName, "init", "-b", "user:pass", "-n", "acme.com", "ca.key"}, "Unrecognized arguments found"},
   113  		{[]string{cmdName, "init", "-c", badSyntaxYaml, "-b", "user:pass"}, "Incorrect format"},
   114  		{[]string{cmdName, "init", "-c", initYaml, "-b", fmt.Sprintf("%s:foo", longUserName)}, "than 1024 characters"},
   115  		{[]string{cmdName, "init", "-c", fmt.Sprintf("/tmp/%s.yaml", longFileName), "-b", "user:pass"}, "file name too long"},
   116  		{[]string{cmdName, "init", "-b", "user:pass", "-c", unsupportedFileType}, "Unsupported Config Type"},
   117  		{[]string{cmdName, "init", "-c", initYaml, "-b", "user"}, "missing a colon"},
   118  		{[]string{cmdName, "init", "-c", initYaml, "-b", "user:"}, "empty password"},
   119  		{[]string{cmdName, "bogus", "-c", initYaml, "-b", "user:pass"}, "unknown command"},
   120  		{[]string{cmdName, "start", "-c"}, "needs an argument:"},
   121  		{[]string{cmdName, "start", "-c", startYaml, "-b", "user:pass", "ca.key"}, "Unrecognized arguments found"},
   122  	}
   123  
   124  	for _, e := range errorCases {
   125  		errorTest(&e, t)
   126  		_ = os.Remove(initYaml)
   127  	}
   128  }
   129  
   130  func TestOneTimePass(t *testing.T) {
   131  	testDir := "oneTimePass"
   132  	os.RemoveAll(testDir)
   133  	defer os.RemoveAll(testDir)
   134  	// Test with "-b" option
   135  	err := RunMain([]string{cmdName, "init", "-b", "admin:adminpw", "--registry.maxenrollments", "1", "-H", testDir})
   136  	if err != nil {
   137  		t.Fatalf("Failed to init server with one time passwords: %s", err)
   138  	}
   139  }
   140  
   141  func TestLDAP(t *testing.T) {
   142  	os.RemoveAll(ldapTestDir)
   143  	defer os.RemoveAll(ldapTestDir)
   144  	// Test with "-b" option
   145  	err := RunMain([]string{cmdName, "init", "-c", path.Join(ldapTestDir, "config.yaml"),
   146  		"-b", "a:b", "--ldap.enabled", "--ldap.url", "ldap://CN=admin@localhost:389/dc=example,dc=com"})
   147  	if err != nil {
   148  		t.Errorf("Failed to init server with LDAP enabled and -b: %s", err)
   149  	}
   150  	// Try without "-b" option
   151  	os.RemoveAll(ldapTestDir)
   152  	err = RunMain([]string{cmdName, "init", "-c", path.Join(ldapTestDir, "config.yaml"),
   153  		"--ldap.enabled", "--ldap.url", "ldap://CN=admin@localhost:389/dc=example,dc=com"})
   154  	if err != nil {
   155  		t.Errorf("Failed to init server with LDAP enabled and no -b: %s", err)
   156  	}
   157  }
   158  
   159  func TestValid(t *testing.T) {
   160  	os.Unsetenv(homeEnvVar)
   161  	blockingStart = false
   162  
   163  	os.Setenv("CA_CFG_PATH", ".")
   164  	validCases := []TestData{
   165  		{[]string{cmdName, "init", "-b", "admin:a:d:m:i:n:p:w"}, ""},
   166  		{[]string{cmdName, "init", "-d"}, ""},
   167  		{[]string{cmdName, "start", "-c", startYaml, "-b", "admin:admin"}, ""},
   168  	}
   169  
   170  	for _, v := range validCases {
   171  		checkTest(&v, t)
   172  	}
   173  }
   174  
   175  // Test to check that config and datasource files are created in correct location
   176  // based on the arguments passed to the fabric-ca-server and environment variables
   177  func TestDBLocation(t *testing.T) {
   178  	blockingStart = false
   179  	envs := []string{"FABRIC_CA_SERVER_HOME", "FABRIC_CA_HOME", "CA_CFG_PATH",
   180  		"FABRIC_CA_SERVER_DB_DATASOURCE"}
   181  	for _, env := range envs {
   182  		os.Unsetenv(env)
   183  	}
   184  
   185  	// Invoke server with -c arg set to serverConfig/config.yml (relative path)
   186  	cfgFile := "serverConfig/config.yml"
   187  	dsFile := "serverConfig/fabric-ca-server.db"
   188  	args := TestData{[]string{cmdName, "start", "-b", "admin:admin", "-c", cfgFile, "-p", "7091"}, ""}
   189  	checkConfigAndDBLoc(t, args, cfgFile, dsFile)
   190  	os.RemoveAll("serverConfig")
   191  
   192  	// Invoke server with -c arg set to serverConfig1/config.yml (relative path)
   193  	// and FABRIC_CA_SERVER_DB_DATASOURCE env variable set to fabric-ca-srv.db (relative path)
   194  	os.Setenv("FABRIC_CA_SERVER_DB_DATASOURCE", "fabric-ca-srv.db")
   195  	cfgFile = "serverConfig1/config.yml"
   196  	dsFile = "serverConfig1/fabric-ca-srv.db"
   197  	args = TestData{[]string{cmdName, "start", "-b", "admin:admin", "-c", cfgFile, "-p", "7092"}, ""}
   198  	checkConfigAndDBLoc(t, args, cfgFile, dsFile)
   199  	os.RemoveAll("serverConfig1")
   200  
   201  	// Invoke server with -c arg set to serverConfig2/config.yml (relative path)
   202  	// and FABRIC_CA_SERVER_DB_DATASOURCE env variable set to /tmp/fabric-ca-srv.db (absolute path)
   203  	cfgFile = "serverConfig2/config.yml"
   204  	dsFile = os.TempDir() + "/fabric-ca-srv.db"
   205  	os.Setenv("FABRIC_CA_SERVER_DB_DATASOURCE", dsFile)
   206  	args = TestData{[]string{cmdName, "start", "-b", "admin:admin", "-c", cfgFile, "-p", "7093"}, ""}
   207  	checkConfigAndDBLoc(t, args, cfgFile, dsFile)
   208  	os.RemoveAll("serverConfig2")
   209  	os.Remove(dsFile)
   210  
   211  	// Invoke server with -c arg set to /tmp/config/config.yml (absolute path)
   212  	// and FABRIC_CA_SERVER_DB_DATASOURCE env variable set to fabric-ca-srv.db (relative path)
   213  	cfgDir := os.TempDir() + "/config/"
   214  	cfgFile = cfgDir + "config.yml"
   215  	dsFile = "fabric-ca-srv.db"
   216  	os.Setenv("FABRIC_CA_SERVER_DB_DATASOURCE", dsFile)
   217  	args = TestData{[]string{cmdName, "start", "-b", "admin:admin", "-c", cfgFile, "-p", "7094"}, ""}
   218  	checkConfigAndDBLoc(t, args, cfgFile, cfgDir+dsFile)
   219  	os.RemoveAll(os.TempDir() + "/config")
   220  
   221  	// Invoke server with -c arg set to /tmp/config/config.yml (absolute path)
   222  	// and FABRIC_CA_SERVER_DB_DATASOURCE env variable set to /tmp/fabric-ca-srv.db (absolute path)
   223  	cfgFile = os.TempDir() + "/config/config.yml"
   224  	dsFile = os.TempDir() + "/fabric-ca-srv.db"
   225  	os.Setenv("FABRIC_CA_SERVER_DB_DATASOURCE", dsFile)
   226  	args = TestData{[]string{cmdName, "start", "-b", "admin:admin", "-c", cfgFile, "-p", "7095"}, ""}
   227  	checkConfigAndDBLoc(t, args, cfgFile, dsFile)
   228  	os.RemoveAll(os.TempDir() + "/config")
   229  	os.Remove(dsFile)
   230  	os.Unsetenv("FABRIC_CA_SERVER_DB_DATASOURCE")
   231  }
   232  
   233  func TestDefaultMultiCAs(t *testing.T) {
   234  	blockingStart = false
   235  
   236  	err := RunMain([]string{cmdName, "start", "-p", "7055", "-c", startYaml, "-d", "-b", "user:pass", "--cacount", "4"})
   237  	if err != nil {
   238  		t.Error("Failed to start server with multiple default CAs using the --cacount flag from command line: ", err)
   239  	}
   240  
   241  	if !util.FileExists("ca/ca4/fabric-ca-server_ca4.db") {
   242  		t.Error("Failed to create 4 default CA instances")
   243  	}
   244  
   245  	os.RemoveAll("ca")
   246  }
   247  
   248  func TestCACountWithAbsPath(t *testing.T) {
   249  	testDir := "myTestDir"
   250  	defer os.RemoveAll(testDir)
   251  	// Run init to create the ca-cert.pem
   252  	err := RunMain([]string{cmdName, "init", "-H", testDir, "-b", "user:pass"})
   253  	if err != nil {
   254  		t.Fatalf("Failed to init CA: %s", err)
   255  	}
   256  	// Set the complete path to the ca-cert.pem file
   257  	cwd, err := os.Getwd()
   258  	if err != nil {
   259  		t.Fatalf("Failed to get current working directory: %s", err)
   260  	}
   261  	certFilePath := path.Join(cwd, testDir, "ca-cert.pem")
   262  	// Init again with the absolute path to ca-cert.pem and --cacount to make sure this works
   263  	err = RunMain([]string{cmdName, "init", "-H", testDir, "--ca.certfile", certFilePath, "--cacount", "2"})
   264  	if err != nil {
   265  		t.Fatalf("Failed to init multi CA with absolute path: %s", err)
   266  	}
   267  }
   268  
   269  func TestMultiCA(t *testing.T) {
   270  	blockingStart = false
   271  
   272  	cleanUpMultiCAFiles()
   273  	defer cleanUpMultiCAFiles()
   274  
   275  	err := RunMain([]string{cmdName, "start", "-d", "-p", "7056", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cafiles", "ca/rootca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/rootca/ca2/fabric-ca-server-config.yaml"})
   276  	if err != nil {
   277  		t.Error("Failed to start server with multiple CAs using the --cafiles flag from command line: ", err)
   278  	}
   279  
   280  	if !util.FileExists("../../testdata/ca/rootca/ca2/fabric-ca2-server.db") {
   281  		t.Error("Failed to create 2 CA instances")
   282  	}
   283  
   284  	err = RunMain([]string{cmdName, "start", "-d", "-p", "7056", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cacount", "1", "--cafiles", "ca/rootca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/rootca/ca2/fabric-ca-server-config.yaml"})
   285  	if err == nil {
   286  		t.Error("Should have failed to start server, can't specify values for both --cacount and --cafiles")
   287  	}
   288  }
   289  
   290  // Tests to see that the bootstrap by default has permission to register any attibute
   291  func TestRegistrarAttribute(t *testing.T) {
   292  	var err error
   293  	blockingStart = false
   294  
   295  	err = os.Setenv("FABRIC_CA_SERVER_HOME", "testregattr/server")
   296  	if !assert.NoError(t, err, "Failed to set environment variable") {
   297  		t.Fatal("Failed to set environment variable")
   298  	}
   299  
   300  	args := TestData{[]string{cmdName, "start", "-b", "admin:admin", "-p", "7096", "-d"}, ""}
   301  	os.Args = args.input
   302  	scmd := NewCommand(args.input[1], blockingStart)
   303  	// Execute the command
   304  	err = scmd.Execute()
   305  	if !assert.NoError(t, err, "Failed to start server") {
   306  		t.Fatal("Failed to start server")
   307  	}
   308  
   309  	client := getTestClient(7096, "testregattr/client")
   310  
   311  	resp, err := client.Enroll(&api.EnrollmentRequest{
   312  		Name:   "admin",
   313  		Secret: "admin",
   314  	})
   315  	if !assert.NoError(t, err, "Failed to enroll 'admin'") {
   316  		t.Fatal("Failed to enroll 'admin'")
   317  	}
   318  
   319  	adminIdentity := resp.Identity
   320  
   321  	_, err = adminIdentity.Register(&api.RegistrationRequest{
   322  		Name: "testuser",
   323  		Attributes: []api.Attribute{
   324  			api.Attribute{
   325  				Name:  "hf.Revoker",
   326  				Value: "true",
   327  			},
   328  			api.Attribute{
   329  				Name:  "hf.IntermediateCA",
   330  				Value: "true",
   331  			},
   332  			api.Attribute{
   333  				Name:  "hf.Registrar.Roles",
   334  				Value: "peer,client",
   335  			},
   336  		},
   337  	})
   338  	assert.NoError(t, err, "Bootstrap user 'admin' should have been able to register a user with attributes")
   339  }
   340  
   341  // TestTLSEnabledButCertfileNotSpecified tests if the server with default config starts
   342  // fine with --tls.enabled and with or without --tls.certfile flag. When
   343  // --tls.certfile is not specified, it should use default name 'tls-cert.pem'
   344  func TestTLSEnabledButCertfileNotSpecified(t *testing.T) {
   345  	blockingStart = false
   346  	rootHomeDir := "tlsintCATestRootSrvHome"
   347  	err := os.RemoveAll(rootHomeDir)
   348  	if err != nil {
   349  		t.Fatalf("Failed to remove directory %s: %s", rootHomeDir, err)
   350  	}
   351  	defer os.RemoveAll(rootHomeDir)
   352  
   353  	err = RunMain([]string{cmdName, "start", "-p", "7100", "-H", rootHomeDir, "-d", "-b", "admin:admin", "--tls.enabled"})
   354  	if err != nil {
   355  		t.Error("Server should not have failed to start when TLS is enabled and TLS cert file name is not specified...it should have used default TLS cert file name 'tls-cert.pem'", err)
   356  	}
   357  
   358  	// start the root server with TLS enabled
   359  	err = RunMain([]string{cmdName, "start", "-p", "7101", "-H", rootHomeDir, "-d", "-b", "admin:admin", "--tls.enabled",
   360  		"--tls.certfile", "tls-cert.pem"})
   361  	if err != nil {
   362  		t.Error("Server should not have failed to start when TLS is enabled and TLS cert file name is specified.", err)
   363  	}
   364  }
   365  
   366  func TestVersion(t *testing.T) {
   367  	err := RunMain([]string{cmdName, "version"})
   368  	if err != nil {
   369  		t.Error("Failed to get fabric-ca-server version: ", err)
   370  	}
   371  }
   372  
   373  // Run server with specified args and check if the configuration and datasource
   374  // files exist in the specified locations
   375  func checkConfigAndDBLoc(t *testing.T, args TestData, cfgFile string, dsFile string) {
   376  	checkTest(&args, t)
   377  	if _, err := os.Stat(cfgFile); os.IsNotExist(err) {
   378  		t.Errorf("Server configuration file is not found in the expected location: %v, TestData: %v",
   379  			err.Error(), args)
   380  	} else if _, err := os.Stat(dsFile); os.IsNotExist(err) {
   381  		t.Errorf("Datasource is not located in the location %s: %v, TestData: %v",
   382  			dsFile, err.Error(), args)
   383  	}
   384  }
   385  
   386  func TestClean(t *testing.T) {
   387  	defYaml := util.GetDefaultConfigFile(cmdName)
   388  	os.Remove(defYaml)
   389  	os.Remove(initYaml)
   390  	os.Remove(startYaml)
   391  	os.Remove(badSyntaxYaml)
   392  	os.Remove(fmt.Sprintf("/tmp/%s.yaml", longFileName))
   393  	os.Remove(unsupportedFileType)
   394  	os.Remove("ca-key.pem")
   395  	os.Remove("ca-cert.pem")
   396  	os.Remove("fabric-ca-server.db")
   397  	os.RemoveAll("keystore")
   398  	os.RemoveAll("msp")
   399  	os.RemoveAll("../../testdata/msp")
   400  	os.Remove("../../testdata/fabric-ca-server.db")
   401  	os.Remove("../../testdata/ca-cert.pem")
   402  	os.RemoveAll(ldapTestDir)
   403  	os.RemoveAll("testregattr")
   404  }
   405  
   406  func cleanUpMultiCAFiles() {
   407  	caFolder := "../../testdata/ca/rootca"
   408  	nestedFolders := []string{"ca1", "ca2"}
   409  	removeFiles := []string{"msp", "ca-cert.pem", "ca-key.pem", "fabric-ca-server.db", "fabric-ca2-server.db"}
   410  
   411  	for _, nestedFolder := range nestedFolders {
   412  		path := filepath.Join(caFolder, nestedFolder)
   413  		for _, file := range removeFiles {
   414  			os.RemoveAll(filepath.Join(path, file))
   415  		}
   416  		os.RemoveAll(filepath.Join(path, "msp"))
   417  	}
   418  
   419  	os.Remove("../../testdata/test.yaml")
   420  }
   421  
   422  func getTestClient(port int, homeDir string) *lib.Client {
   423  	return &lib.Client{
   424  		Config:  &lib.ClientConfig{URL: fmt.Sprintf("http://localhost:%d", port)},
   425  		HomeDir: homeDir,
   426  	}
   427  }