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 }