Skip to content

Commit

Permalink
[FAB-2896] Start multiple default CA instances
Browse files Browse the repository at this point in the history
This is the last change-set in a series of change-sets
to support multple CAs. A cacount option has been
added to the server that wil start up the specified
number of default CA instances.

Example:

fabric-ca-server start -b admin:adminpw --cacount 2

Change-Id: I23fa1f1d1c232734dc2e8fca519a5ab30d6f5fac
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed Apr 27, 2017
1 parent c131944 commit 5610d33
Show file tree
Hide file tree
Showing 24 changed files with 370 additions and 120 deletions.
3 changes: 3 additions & 0 deletions cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ enrollment:
hosts:
profile:
label:
# Name of the CA to connect to within the fabric-ca server
caname:
`
)

Expand Down
14 changes: 12 additions & 2 deletions cmd/fabric-ca-client/getcacert.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package main

import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -61,7 +62,7 @@ func runGetCACert() error {
}

req := &api.GetCAInfoRequest{
CAName: clientCfg.CAInfo.CAName,
CAName: clientCfg.CAName,
}

si, err := client.GetCAInfo(req)
Expand All @@ -83,7 +84,16 @@ func storeCAChain(config *lib.ClientConfig, si *lib.GetServerInfoResponse) error
if err != nil {
return fmt.Errorf("Failed creating CA certificates directory: %s", err)
}
fname := strings.Replace(si.CAName, ".", "-", -1) + ".pem"
serverURL, err := url.Parse(config.URL)
if err != nil {
return err
}
fname := serverURL.Host
if config.CAName != "" {
fname = fmt.Sprintf("%s-%s", fname, config.CAName)
}
fname = strings.Replace(fname, ":", "-", -1)
fname = strings.Replace(fname, ".", "-", -1) + ".pem"
path := path.Join(caCertsDir, fname)
err = util.WriteFile(path, si.CAChain, 0644)
if err != nil {
Expand Down
45 changes: 44 additions & 1 deletion cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -578,23 +578,61 @@ func TestClientCommandsTLS(t *testing.T) {
}

func TestMultiCA(t *testing.T) {
cleanMultiCADir()

srv = getServer()
srv.HomeDir = "../../testdata"
srv.Config.CAfiles = []string{"ca/rootca/ca1/fabric-ca-server-config.yaml", "ca/rootca/ca2/fabric-ca-server-config.yaml"}
srv.CA.Config.CSR.Hosts = []string{"hostname"}
t.Logf("Server configuration: %+v\n", srv.Config)

err := srv.RegisterBootstrapUser("admin", "adminpw", "")
if err != nil {
t.Errorf("Failed to register bootstrap user: %s", err)
}

srv.BlockingStart = false
err := srv.Start()
err = srv.Start()
if err != nil {
t.Fatal("Failed to start server:", err)
}

// Test going to default CA if no caname provided in client request
err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://admin:adminpw@localhost:7054", "-d"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://adminca1:adminca1pw@localhost:7054", "-d", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "reenroll", "-c", testYaml, "-d", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "register", "-c", testYaml, "-d", "--id.name", "testuser", "--id.type", "user", "--id.affiliation", "org1", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "revoke", "-c", testYaml, "-d", "--revoke.name", "adminca1", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "getcacert", "-c", testYaml, "-d", "--caname", "rootca1"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://admin:adminpw@localhost:7054", "-d", "--caname", "rootca2"})
if err != nil {
t.Errorf("client enroll -c -u failed: %s", err)
}

err = RunMain([]string{cmdName, "enroll", "-c", testYaml, "-u", "http://adminca1:adminca1pw@localhost:7054", "-d", "--caname", "rootca3"})
if err == nil {
t.Errorf("Should have failed, rootca3 does not exist on server")
Expand Down Expand Up @@ -653,11 +691,16 @@ func getServerConfig() *lib.ServerConfig {
}

func getCAConfig() *lib.CAConfig {
affiliations := map[string]interface{}{
"org1": nil,
}

return &lib.CAConfig{
CA: lib.CAInfo{
Keyfile: keyfile,
Certfile: certfile,
},
Affiliations: affiliations,
CSR: csr.CertificateRequest{
CN: "TestCN",
},
Expand Down
1 change: 1 addition & 0 deletions cmd/fabric-ca-client/reenroll.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func runReenroll() error {
Label: clientCfg.Enrollment.Label,
Profile: clientCfg.Enrollment.Profile,
CSR: &clientCfg.CSR,
CAName: clientCfg.CAName,
}

resp, err := id.Reenroll(req)
Expand Down
1 change: 1 addition & 0 deletions cmd/fabric-ca-client/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ func runRegister() error {
return err
}

clientCfg.ID.CAName = clientCfg.CAName
resp, err := id.Register(&clientCfg.ID)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions cmd/fabric-ca-client/revoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func runRevoke(cmd *cobra.Command) error {
Serial: clientCfg.Revoke.Serial,
AKI: clientCfg.Revoke.AKI,
Reason: clientCfg.Revoke.Reason,
CAName: clientCfg.CAName,
})

if err == nil {
Expand Down
49 changes: 5 additions & 44 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ tls:
#############################################################################
ca:
# Name of this CA
name: <<<CANAME>>>
name:
# Key file (default: ca-key.pem)
keyfile: ca-key.pem
# Certificate file (default: ca-cert.pem)
Expand Down Expand Up @@ -304,46 +304,11 @@ func configInit() (err error) {
}

// Read the config
viper.SetConfigFile(cfgFileName)
// viper.SetConfigFile(cfgFileName)
viper.AutomaticEnv() // read in environment variables that match
err = viper.ReadInConfig()
err = lib.UnmarshalConfig(serverCfg, viper.GetViper(), cfgFileName, true, true)
if err != nil {
return fmt.Errorf("Failed to read config file: %s", err)
}

// Unmarshal the config into 'serverCfg'
// When viper bug https://github.com/spf13/viper/issues/327 is fixed
// and vendored, the work around code can be deleted.
viperIssue327WorkAround := true
if viperIssue327WorkAround {
sliceFields := []string{
"csr.hosts",
"tls.clientauth.certfiles",
"cafiles",
"db.tls.certfiles",
"ldap.tls.certfiles",
}
err = util.ViperUnmarshal(serverCfg, sliceFields, viper.GetViper())
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
err = viper.Unmarshal(&serverCfg.CAcfg)
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
} else {
err = viper.Unmarshal(serverCfg)
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
err = viper.Unmarshal(&serverCfg.CAcfg)
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
}

if serverCfg.CAcfg.CA.Name == "" {
return fmt.Errorf(caNameReqMsg)
return err
}

return nil
Expand Down Expand Up @@ -372,21 +337,17 @@ func createDefaultConfigFile() error {
return errors.New("An empty password in the '-b user:pass' option is not permitted")
}

var myhost, caName string
var myhost string
var err error
myhost, err = os.Hostname()
if err != nil {
return err
}

// Get hostname
caName = getCAName(myhost)

// Do string subtitution to get the default config
cfg := strings.Replace(defaultCfgTemplate, "<<<ADMIN>>>", user, 1)
cfg = strings.Replace(cfg, "<<<ADMINPW>>>", pass, 1)
cfg = strings.Replace(cfg, "<<<MYHOST>>>", myhost, 1)
cfg = strings.Replace(cfg, "<<<CANAME>>>", caName, 1)
// Now write the file
err = os.MkdirAll(filepath.Dir(cfgFileName), 0755)
if err != nil {
Expand Down
24 changes: 21 additions & 3 deletions cmd/fabric-ca-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ func TestErrors(t *testing.T) {
errorCases := []TestData{
{[]string{cmdName, "init", "-c", initYaml}, "option is required"},
{[]string{cmdName, "init", "-n", "acme.com", "-b", "user::"}, "Failed to read"},
{[]string{cmdName, "init", "-c", ymlWithoutCAName, "-n", "", "-b", "user:pass"}, caNameReqMsg},
{[]string{cmdName, "init", "-b", "user:pass", "-n", "acme.com", "ca.key"}, "too many arguments"},
{[]string{cmdName, "init", "-c", badSyntaxYaml, "-b", "user:pass"}, "Incorrect format"},
{[]string{cmdName, "init", "-c", initYaml, "-b", fmt.Sprintf("%s:foo", longUserName)}, "than 1024 characters"},
Expand Down Expand Up @@ -205,10 +204,25 @@ func TestDBLocation(t *testing.T) {
os.Unsetenv("FABRIC_CA_SERVER_DB_DATASOURCE")
}

func TestDefaultMultiCAs(t *testing.T) {
blockingStart = false

err := RunMain([]string{cmdName, "start", "-p", "7055", "-c", startYaml, "-d", "-b", "user:pass", "--cacount", "4"})
if err != nil {
t.Error("Failed to start server with multiple default CAs using the --cacount flag from command line: ", err)
}

if !util.FileExists("ca/ca4/fabric-ca-server.db") {
t.Error("Failed to create 4 default CA instances")
}

os.RemoveAll("ca")
}

func TestMultiCA(t *testing.T) {
blockingStart = false

err := RunMain([]string{cmdName, "start", "-d", "-p", "7055", "-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"})
err := RunMain([]string{cmdName, "start", "-d", "-p", "7056", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cacount", "0", "--cafiles", "ca/rootca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/rootca/ca2/fabric-ca-server-config.yaml"})
if err != nil {
t.Error("Failed to start server with multiple CAs using the --cafiles flag from command line: ", err)
}
Expand All @@ -217,8 +231,12 @@ func TestMultiCA(t *testing.T) {
t.Error("Failed to create 2 CA instances")
}

cleanUpMultiCAFiles()
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"})
if err == nil {
t.Error("Should have failed to start server, can't specify values for both --cacount and --cafiles")
}

cleanUpMultiCAFiles()
}

// Run server with specified args and check if the configuration and datasource
Expand Down
4 changes: 1 addition & 3 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ import (

const (
defaultDatabaseType = "sqlite3"
// DefaultCAName is the name for the ca to be used if no ca name provided by client
DefaultCAName = "ca"
)

// CA represents a certificate authority which signs, issues and revokes certificates
Expand Down Expand Up @@ -284,7 +282,7 @@ func (ca *CA) initConfig() (err error) {
return fmt.Errorf("Failed to initialize CA's home directory: %s", err)
}
}
log.Info("CA Home Directory: ", ca.HomeDir)
log.Debug("CA Home Directory: ", ca.HomeDir)
// Init config if not set
if ca.Config == nil {
ca.Config = new(CAConfig)
Expand Down
35 changes: 35 additions & 0 deletions lib/caconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,41 @@ import (
"github.com/hyperledger/fabric/bccsp/factory"
)

const (
// defaultCACfgTemplate is the a CA's default configuration file template
defaultCACfgTemplate = `
#############################################################################
# This file contains information specific to a single Certificate Authority (CA).
# A single fabric-ca-server can service multiple CAs. The server's configuration
# file contains configuration information for the default CA, and each of these
# CA-specific files define configuration settings for a non-default CA.
#
# The only required configuration item in each CA-specific file is a unique
# CA name (see "ca.name" below). Each CA name in the same fabric-ca-server
# must be unique. All other configuration settings needed for this CA are
# taken from the default CA settings, or you may override those settings by
# adding the setting to this file.
#
# For example, you should provide a different username and password for the
# bootstrap identity as found in the "identities" subsection of the "registry"
# section.
#
# See the server's configuration file for comments on all settings.
# All settings pertaining to the server's listening endpoint are by definition
# server-specific and so will be ignored in a CA configuration file.
#############################################################################
ca:
# Name of this CA
name: <<<CANAME>>>
###########################################################################
# Certificate Signing Request section for generating the CA certificate
###########################################################################
csr:
cn: <<<COMMONNAME>>>
`
)

// CAConfig is the CA instance's config
// The tags are recognized by the RegisterFlags function in fabric-ca/lib/util.go
// and are as follows:
Expand Down
7 changes: 2 additions & 5 deletions lib/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ func TestClient(t *testing.T) {
}

func testGetCAInfo(c *Client, t *testing.T) {
req := &api.GetCAInfoRequest{
CAName: DefaultCAName,
}
req := &api.GetCAInfoRequest{}
si, err := c.GetCAInfo(req)
if err != nil {
t.Fatalf("Failed to get server info: %s", err)
Expand Down Expand Up @@ -280,8 +278,7 @@ func TestCustomizableMaxEnroll(t *testing.T) {

func testTooManyEnrollments(t *testing.T) {
clientConfig := &ClientConfig{
URL: fmt.Sprintf("http://localhost:%d", ctport2),
CAName: DefaultCAName,
URL: fmt.Sprintf("http://localhost:%d", ctport2),
}

rawURL := fmt.Sprintf("http://admin:adminpw@localhost:%d", ctport2)
Expand Down
2 changes: 1 addition & 1 deletion lib/clientconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ClientConfig struct {
ID api.RegistrationRequest
Revoke api.RevocationRequest
CAInfo api.GetCAInfoRequest
CAName string `def:"ca" help:"Name of CA"`
CAName string `help:"Name of CA"`
}

// Enroll a client given the server's URL and the client's home directory.
Expand Down
Loading

0 comments on commit 5610d33

Please sign in to comment.