github.com/extrame/fabric-ca@v2.0.0-alpha+incompatible/cmd/fabric-ca-server/main_test.go (about)

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