Skip to content

Commit

Permalink
[FAB-2896] Loading multiple CAs from config files
Browse files Browse the repository at this point in the history
This is change-set in a series of changes
to add support for multiple CAs. Support added for creating
multiple instances of CAs by providing a config file for
each instance of CA to be created. CA config files
are provided using the cafiles option.

Currently the traffic is still directed to one CA instance.
Next change-set will add the ability to direct traffic
to a specific CA by providing the ca name in the request
to server.

Change-Id: I28c98d524f03cc9c8cb879d3b81ee1e5f7bf9934
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed Apr 25, 2017
1 parent b4ce73f commit 3ab84cb
Show file tree
Hide file tree
Showing 17 changed files with 585 additions and 34 deletions.
2 changes: 1 addition & 1 deletion cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func configInit(command string) error {
sliceFields := []string{
"tls.certfiles",
}
err = util.ViperUnmarshal(clientCfg, sliceFields)
err = util.ViperUnmarshal(clientCfg, sliceFields, viper.GetViper())
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
Expand Down
28 changes: 27 additions & 1 deletion cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,30 @@ crypto:
security_level: 256
ephemeral: false
key_store_dir: keys
#############################################################################
# The fabric-ca-server init and start commands support the following two
# additional mutually exclusive options:
#
# 1) --cacount <number-of-CAs>
# Automatically generate multiple default CA instances
#
# 2) --cafiles <CA-config-files>
# For each CA config file in the list, generate a separate signing CA. Each CA
# config file in this list MAY contain all of the same elements as are found in
# the server config file except port, debug, and tls sections.
#
# Examples:
# fabric-ca-server start -b admin:adminpw --cacount 2
#
# fabric-ca-server start -b admin:adminpw --cafiles ca/ca1/fabric-ca-server-config.yaml
# --cafiles ca/ca2/fabric-ca-server-config.yaml
#
#############################################################################
cacount:
cafiles:
`
)

Expand Down Expand Up @@ -294,8 +318,10 @@ func configInit() (err error) {
sliceFields := []string{
"csr.hosts",
"tls.clientauth.certfiles",
"cafiles",
"db.tls.certfiles",
}
err = util.ViperUnmarshal(serverCfg, sliceFields)
err = util.ViperUnmarshal(serverCfg, sliceFields, viper.GetViper())
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", cfgFileName, err)
}
Expand Down
7 changes: 3 additions & 4 deletions cmd/fabric-ca-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ func RunMain(args []string) error {
// Get a server for the init and start commands
func getServer() *lib.Server {
return &lib.Server{
HomeDir: filepath.Dir(cfgFileName),
Config: serverCfg,
BlockingStart: blockingStart,
ParentServerURL: viper.GetString("url"),
HomeDir: filepath.Dir(cfgFileName),
Config: serverCfg,
BlockingStart: blockingStart,
CA: lib.CA{
Config: &serverCfg.CAcfg,
},
Expand Down
27 changes: 27 additions & 0 deletions cmd/fabric-ca-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,23 @@ func TestDBLocation(t *testing.T) {
checkConfigAndDBLoc(t, args, cfgFile, dsFile)
os.RemoveAll(os.TempDir() + "/config")
os.Remove(dsFile)
os.Unsetenv("FABRIC_CA_SERVER_DB_DATASOURCE")
}

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

err := RunMain([]string{cmdName, "start", "-d", "-p", "7055", "-c", "../../testdata/test.yaml", "-b", "user:pass", "--cafiles", "ca/ca1/fabric-ca-server-config.yaml", "--cafiles", "ca/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)
}

if !util.FileExists("../../testdata/ca/ca2/fabric-ca2-server.db") {
t.Error("Failed to create 2 CA instances")
}

cleanUpMultiCAFiles()

}

// Run server with specified args and check if the configuration and datasource
Expand Down Expand Up @@ -229,3 +246,13 @@ func TestClean(t *testing.T) {
os.Remove("ca-cert.pem")
os.Remove("fabric-ca-server.db")
}

func cleanUpMultiCAFiles() {
os.Remove("../../testdata/test.yaml")
os.Remove("../../testdata/ca/ca1/ca-cert.pem")
os.Remove("../../testdata/ca/ca1/ca-key.pem")
os.Remove("../../testdata/ca/ca1/fabric-ca-server.db")
os.Remove("../../testdata/ca/ca2/ca-cert.pem")
os.Remove("../../testdata/ca/ca2/ca-key.pem")
os.Remove("../../testdata/ca/ca2/fabric-ca2-server.db")
}
18 changes: 11 additions & 7 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ func initCA(ca *CA, homeDir string, config *CAConfig, server *Server, renew bool
ca.Config = config
ca.server = server

ca.Config.CSR.Hosts = util.NormalizeStringSlice(ca.Config.CSR.Hosts)
ca.Config.DB.TLS.CertFiles = util.NormalizeStringSlice(ca.Config.DB.TLS.CertFiles)

err := ca.init(renew)
if err != nil {
return err
Expand Down Expand Up @@ -174,8 +177,8 @@ func (ca *CA) initKeyMaterial(renew bool) error {

// Get the CA certificate and key for this CA
func (ca *CA) getCACertAndKey() (cert, key []byte, err error) {
log.Debugf("Getting CA cert and key; parent server URL is '%s'", ca.server.ParentServerURL)
if ca.server.ParentServerURL != "" {
log.Debugf("Getting CA cert and key; parent server URL is '%s'", ca.Config.ParentServerURL)
if ca.Config.ParentServerURL != "" {
// This is an intermediate CA, so call the parent fabric-ca-server
// to get the key and cert
clientCfg := ca.Config.Client
Expand All @@ -193,7 +196,7 @@ func (ca *CA) getCACertAndKey() (cert, key []byte, err error) {
}
log.Debugf("Intermediate enrollment request: %v", clientCfg.Enrollment)
var resp *EnrollmentResponse
resp, err = clientCfg.Enroll(ca.server.ParentServerURL, ca.HomeDir)
resp, err = clientCfg.Enroll(ca.Config.ParentServerURL, ca.HomeDir)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -261,7 +264,7 @@ func (ca *CA) getCAChain() (chain []byte, err error) {
return util.ReadFile(certAuth.Chainfile)
}
// Otherwise, if this is a root CA, we always return the contents of the CACertfile
if ca.server.ParentServerURL == "" {
if ca.Config.ParentServerURL == "" {
return util.ReadFile(certAuth.Certfile)
}
// If this is an intermediate CA but the ca.Chainfile doesn't exist,
Expand Down Expand Up @@ -415,8 +418,9 @@ func (ca *CA) initEnrollmentSigner() (err error) {
}

// Make sure the policy reflects the new remote
if ca.server.Config.Remote != "" {
err = policy.OverrideRemotes(ca.server.Config.Remote)
ParentServerURL := ca.Config.ParentServerURL
if ParentServerURL != "" {
err = policy.OverrideRemotes(ParentServerURL)
if err != nil {
return fmt.Errorf("Failed initializing enrollment signer: %s", err)
}
Expand All @@ -428,7 +432,7 @@ func (ca *CA) initEnrollmentSigner() (err error) {
"cert-file": c.CA.Certfile,
"key-file": c.CA.Keyfile,
},
ForceRemote: ca.server.Config.Remote != "",
ForceRemote: ParentServerURL != "",
}
ca.enrollSigner, err = universal.NewSigner(root, policy)
if err != nil {
Expand Down
19 changes: 10 additions & 9 deletions lib/caconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ import (
// "help" - the help message to display on the command line;
// "skip" - to skip the field.
type CAConfig struct {
CSP *factory.FactoryOpts
CA CAInfo
Signing *config.Signing
CSR csr.CertificateRequest
Registry CAConfigRegistry
Affiliations map[string]interface{}
LDAP ldap.Config
DB CAConfigDB
Client *ClientConfig
CSP *factory.FactoryOpts
CA CAInfo
ParentServerURL string `skip:"true"`
Signing *config.Signing
CSR csr.CertificateRequest
Registry CAConfigRegistry
Affiliations map[string]interface{}
LDAP ldap.Config
DB CAConfigDB
Client *ClientConfig
}

// CAInfo is the CA information on a fabric-ca-server
Expand Down
6 changes: 3 additions & 3 deletions lib/client_whitebox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,14 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S
},
CA: CA{
Config: &CAConfig{
Affiliations: affiliations,
ParentServerURL: parentURL,
Affiliations: affiliations,
Registry: CAConfigRegistry{
MaxEnrollments: maxEnroll,
},
},
},
HomeDir: home,
ParentServerURL: parentURL,
HomeDir: home,
}
// The bootstrap user's affiliation is the empty string, which
// means the user is at the affiliation root
Expand Down
115 changes: 113 additions & 2 deletions lib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ import (
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/util"
"github.com/spf13/viper"

_ "github.com/go-sql-driver/mysql" // import to support MySQL
_ "github.com/lib/pq" // import to support Postgres
Expand All @@ -48,8 +50,6 @@ type Server struct {
BlockingStart bool
// The server's configuration
Config *ServerConfig
// The parent server URL, which is non-null if this is an intermediate server
ParentServerURL string
// The server mux
mux *http.ServeMux
// The current listener for this server
Expand All @@ -58,6 +58,8 @@ type Server struct {
serveError error
// Server's default CA
CA
// A map of CAs stored by CA name as key
caMap map[string]*CA
}

// Init initializes a fabric-ca server
Expand Down Expand Up @@ -93,6 +95,25 @@ func (s *Server) Start() (err error) {
return err
}

s.Config.TLS.ClientAuth.CertFiles = util.NormalizeStringSlice(s.Config.TLS.ClientAuth.CertFiles)

if len(s.Config.CAfiles) != 0 {
log.Infof("CAs to be started: %s", s.Config.CAfiles)
var caFiles []string

caFiles, err = util.NormalizeFileList(s.Config.CAfiles, s.HomeDir)
if err != nil {
return err
}

for _, caFile := range caFiles {
err = s.loadCA(caFile, false)
if err != nil {
return err
}
}
}

// Register http handlers
s.registerHandlers()

Expand Down Expand Up @@ -171,11 +192,14 @@ func (s *Server) initConfig() (err error) {
}

s.makeFileNamesAbsolute()
s.caMap = make(map[string]*CA)

return nil
}

func (s *Server) initDefaultCA(ca *CA, renew bool) error {
log.Debug("Initializing default ca")

err := initCA(ca, s.HomeDir, s.CA.Config, s, renew)
if err != nil {
return err
Expand All @@ -185,6 +209,93 @@ func (s *Server) initDefaultCA(ca *CA, renew bool) error {
ca.Config.CA.Name = DefaultCAName
}

err = s.addCA(ca)
if err != nil {
return err
}

return nil
}

func (s *Server) loadCA(caFile string, renew bool) error {
log.Infof("Loading CA from %s", caFile)
var err error

caConfig := new(CAConfig)

exists := util.FileExists(caFile)
if !exists {
return fmt.Errorf("%s file does not exist", caFile)
}

// Creating new Viper instance, to prevent any server level environment variables or
// flags from overridding the configuration options specified in the
// CA config file
caViper := viper.New()

caViper.SetConfigFile(caFile)
err = caViper.ReadInConfig()
if err != nil {
return fmt.Errorf("Failed to read config file: %s", err)
}

// Unmarshal the config into 'caConfig'
// 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",
"ldap.tls.certfiles",
"db.tls.certfiles",
"cafiles",
}
err = util.ViperUnmarshal(caConfig, sliceFields, caViper)
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", caFile, err)
}
} else {
err = caViper.Unmarshal(caConfig)
if err != nil {
return fmt.Errorf("Incorrect format in file '%s': %s", caFile, err)
}
}

util.CopyMissingValues(s.CA.Config, caConfig)

if caConfig.DB.Type == defaultDatabaseType {
caConfig.DB.Datasource = filepath.Base(caConfig.DB.Datasource)
}

if !viper.IsSet("registry.maxenrollments") {
caConfig.Registry.MaxEnrollments = s.CA.Config.Registry.MaxEnrollments
}

if !viper.IsSet("db.tls.enabled") {
caConfig.DB.TLS.Enabled = s.CA.Config.DB.TLS.Enabled
}

ca, err := NewCA(filepath.Dir(caFile), caConfig, s, renew)
if err != nil {
return err
}

return s.addCA(ca)

}

func (s *Server) addCA(ca *CA) error {
log.Infof("Adding CA %s to server", ca.Config.CA.Name)

if _, ok := s.caMap[ca.Config.CA.Name]; ok {
return fmt.Errorf("CA by name '%s' already exists", ca.Config.CA.Name)
}

s.caMap[ca.Config.CA.Name] = ca

log.Infof("CA '%s' has been added to server ", ca.Config.CA.Name)

return nil
}

Expand Down
Loading

0 comments on commit 3ab84cb

Please sign in to comment.