diff --git a/cmd/fabric-ca-client/main_test.go b/cmd/fabric-ca-client/main_test.go index 3d0591e3b..4fd858805 100644 --- a/cmd/fabric-ca-client/main_test.go +++ b/cmd/fabric-ca-client/main_test.go @@ -162,7 +162,7 @@ func TestClientCommandsNoTLS(t *testing.T) { aff["company1"] = []string{} aff["company2"] = []string{} - srv.Config.Affiliations = aff + srv.CA.Config.Affiliations = aff err = srv.Start() if err != nil { @@ -594,6 +594,10 @@ func getServer() *lib.Server { return &lib.Server{ HomeDir: ".", Config: getServerConfig(), + CA: lib.CA{ + HomeDir: ".", + Config: getCAConfig(), + }, } } @@ -601,7 +605,12 @@ func getServerConfig() *lib.ServerConfig { return &lib.ServerConfig{ Debug: true, Port: 7054, - CA: lib.ServerConfigCA{ + } +} + +func getCAConfig() *lib.CAConfig { + return &lib.CAConfig{ + CA: lib.CAInfo{ Keyfile: keyfile, Certfile: certfile, }, @@ -612,7 +621,7 @@ func getServerConfig() *lib.ServerConfig { } func getSerialAKIByID(id string) (serial, aki string, err error) { - testdb, _, _ := dbutil.NewUserRegistrySQLLite3(srv.Config.DB.Datasource) + testdb, _, _ := dbutil.NewUserRegistrySQLLite3(srv.CA.Config.DB.Datasource) acc := lib.NewCertDBAccessor(testdb) certs, _ := acc.GetCertificatesByID(id) diff --git a/cmd/fabric-ca-server/config.go b/cmd/fabric-ca-server/config.go index b785db7c6..808b7a8cf 100644 --- a/cmd/fabric-ca-server/config.go +++ b/cmd/fabric-ca-server/config.go @@ -101,7 +101,7 @@ tls: keyfile: ca-key.pem clientauth: type: noclientcert - certfiles: # Comma Separated list of root certificate files (e.g. root.pem, root2.pem) + certfiles: ############################################################################# # The CA section contains information related to the Certificate Authority @@ -299,14 +299,22 @@ func configInit() (err error) { 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.CA.Name == "" { + if serverCfg.CAcfg.CA.Name == "" { return fmt.Errorf(caNameReqMsg) } diff --git a/cmd/fabric-ca-server/main.go b/cmd/fabric-ca-server/main.go index a47a129c4..64146abd4 100644 --- a/cmd/fabric-ca-server/main.go +++ b/cmd/fabric-ca-server/main.go @@ -68,7 +68,12 @@ func init() { "help.csr.serialnumber": "The serial number in a certificate signing request to a parent fabric-ca-server", "help.csr.hosts": "A list of space-separated host names in a certificate signing request to a parent fabric-ca-server", } - err := util.RegisterFlags(pflags, serverCfg, tags) + err := util.RegisterFlags(pflags, serverCfg, nil) + if err != nil { + panic(err) + } + caCfg := &lib.CAConfig{} + err = util.RegisterFlags(pflags, caCfg, tags) if err != nil { panic(err) } @@ -104,5 +109,8 @@ func getServer() *lib.Server { Config: serverCfg, BlockingStart: blockingStart, ParentServerURL: viper.GetString("url"), + CA: lib.CA{ + Config: &serverCfg.CAcfg, + }, } } diff --git a/lib/ca.go b/lib/ca.go index 3b394d216..2faeae45f 100644 --- a/lib/ca.go +++ b/lib/ca.go @@ -55,9 +55,7 @@ type CA struct { // The home directory for the CA HomeDir string // The CA's configuration - Config *ServerConfig - // The parent server URL, which is non-null if this is an intermediate server - ParentServerURL string + Config *CAConfig // The database handle used to store certificates and optionally // the user registry information, unless LDAP it enabled for the // user registry function. @@ -70,18 +68,15 @@ type CA struct { registry spi.UserRegistry // The signer used for enrollment enrollSigner signer.Signer + + server *Server } // NewCA creates a new CA with the specified // home directory, parent server URL, and config -func NewCA(homeDir, parentServerURL string, config *ServerConfig, renew bool) (*CA, error) { - ca := &CA{ - HomeDir: homeDir, - ParentServerURL: parentServerURL, - Config: config, - } - - err := ca.init(renew) +func NewCA(homeDir string, config *CAConfig, server *Server, renew bool) (*CA, error) { + ca := new(CA) + err := initCA(ca, homeDir, config, server, renew) if err != nil { return nil, err } @@ -90,10 +85,10 @@ func NewCA(homeDir, parentServerURL string, config *ServerConfig, renew bool) (* } // initCA will initialize the passed in pointer to a CA struct -func initCA(ca *CA, homeDir, parentServerURL string, config *ServerConfig, renew bool) error { +func initCA(ca *CA, homeDir string, config *CAConfig, server *Server, renew bool) error { ca.HomeDir = homeDir - ca.ParentServerURL = parentServerURL ca.Config = config + ca.server = server err := ca.init(renew) if err != nil { @@ -179,8 +174,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.ParentServerURL) - if ca.ParentServerURL != "" { + log.Debugf("Getting CA cert and key; parent server URL is '%s'", ca.server.ParentServerURL) + if ca.server.ParentServerURL != "" { // This is an intermediate CA, so call the parent fabric-ca-server // to get the key and cert clientCfg := ca.Config.Client @@ -198,7 +193,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.ParentServerURL, ca.HomeDir) + resp, err = clientCfg.Enroll(ca.server.ParentServerURL, ca.HomeDir) if err != nil { return nil, nil, err } @@ -266,7 +261,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.ParentServerURL == "" { + if ca.server.ParentServerURL == "" { return util.ReadFile(certAuth.Certfile) } // If this is an intermediate CA but the ca.Chainfile doesn't exist, @@ -283,9 +278,10 @@ 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) // Init config if not set if ca.Config == nil { - ca.Config = new(ServerConfig) + ca.Config = new(CAConfig) } // Set config defaults cfg := ca.Config @@ -299,7 +295,7 @@ func (ca *CA) initConfig() (err error) { cfg.CSR.CN = "fabric-ca-server" } // Set log level if debug is true - if cfg.Debug { + if ca.server.Config.Debug { log.Level = log.LevelDebug } // Init the BCCSP @@ -419,8 +415,8 @@ func (ca *CA) initEnrollmentSigner() (err error) { } // Make sure the policy reflects the new remote - if c.Remote != "" { - err = policy.OverrideRemotes(c.Remote) + if ca.server.Config.Remote != "" { + err = policy.OverrideRemotes(ca.server.Config.Remote) if err != nil { return fmt.Errorf("Failed initializing enrollment signer: %s", err) } @@ -432,7 +428,7 @@ func (ca *CA) initEnrollmentSigner() (err error) { "cert-file": c.CA.Certfile, "key-file": c.CA.Keyfile, }, - ForceRemote: c.Remote != "", + ForceRemote: ca.server.Config.Remote != "", } ca.enrollSigner, err = universal.NewSigner(root, policy) if err != nil { @@ -514,7 +510,7 @@ func (ca *CA) loadAffiliationsTableR(val interface{}, parentPath string) (err er } // Add an identity to the registry -func (ca *CA) addIdentity(id *ServerConfigIdentity, errIfFound bool) error { +func (ca *CA) addIdentity(id *CAConfigIdentity, errIfFound bool) error { var err error user, _ := ca.registry.GetUser(id.Name, nil) if user != nil { @@ -598,8 +594,6 @@ func (ca *CA) makeFileNamesAbsolute() error { &ca.Config.CA.Certfile, &ca.Config.CA.Keyfile, &ca.Config.CA.Chainfile, - &ca.Config.TLS.CertFile, - &ca.Config.TLS.KeyFile, } for _, namePtr := range fields { abs, err := util.MakeFileAbs(*namePtr, ca.HomeDir) diff --git a/lib/caconfig.go b/lib/caconfig.go new file mode 100644 index 000000000..16861be07 --- /dev/null +++ b/lib/caconfig.go @@ -0,0 +1,80 @@ +/* +Copyright IBM Corp. 2017 All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package lib + +import ( + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/csr" + "github.com/hyperledger/fabric-ca/lib/ldap" + "github.com/hyperledger/fabric-ca/lib/tls" + "github.com/hyperledger/fabric-ca/util" + "github.com/hyperledger/fabric/bccsp/factory" +) + +// 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: +// "def" - the default value of the field; +// "opt" - the optional one character short name to use on the command line; +// "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 +} + +// CAInfo is the CA information on a fabric-ca-server +type CAInfo struct { + Name string `opt:"n" help:"Certificate Authority name"` + Keyfile string `def:"ca-key.pem" help:"PEM-encoded CA key file"` + Certfile string `def:"ca-cert.pem" help:"PEM-encoded CA certificate file"` + Chainfile string `def:"ca-chain.pem" help:"PEM-encoded CA chain file"` +} + +// CAConfigDB is the database part of the server's config +type CAConfigDB struct { + Type string `def:"sqlite3" help:"Type of database; one of: sqlite3, postgres, mysql"` + Datasource string `def:"fabric-ca-server.db" help:"Data source which is database specific"` + TLS tls.ClientTLSConfig +} + +// CAConfigRegistry is the registry part of the server's config +type CAConfigRegistry struct { + MaxEnrollments int `def:"0" help:"Maximum number of enrollments; valid if LDAP not enabled"` + Identities []CAConfigIdentity +} + +// CAConfigIdentity is identity information in the server's config +type CAConfigIdentity struct { + Name string + Pass string `secret:"password"` + Type string + Affiliation string + MaxEnrollments int + Attrs map[string]string +} + +func (cc *CAConfigIdentity) String() string { + return util.StructToString(cc) +} diff --git a/lib/client_test.go b/lib/client_test.go index be4a6eee8..9c5f1c706 100644 --- a/lib/client_test.go +++ b/lib/client_test.go @@ -255,7 +255,7 @@ func TestCustomizableMaxEnroll(t *testing.T) { return } - srv.Config.Registry.MaxEnrollments = 3 + srv.CA.Config.Registry.MaxEnrollments = 3 srv.Config.Debug = true err := srv.Start() diff --git a/lib/client_whitebox_test.go b/lib/client_whitebox_test.go index 021e148e6..04730b58a 100644 --- a/lib/client_whitebox_test.go +++ b/lib/client_whitebox_test.go @@ -134,11 +134,15 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S } srv := &Server{ Config: &ServerConfig{ - Port: port, - Debug: true, - Affiliations: affiliations, - Registry: ServerConfigRegistry{ - MaxEnrollments: maxEnroll, + Port: port, + Debug: true, + }, + CA: CA{ + Config: &CAConfig{ + Affiliations: affiliations, + Registry: CAConfigRegistry{ + MaxEnrollments: maxEnroll, + }, }, }, HomeDir: home, diff --git a/lib/server.go b/lib/server.go index 3a113d43e..e8ca0501f 100644 --- a/lib/server.go +++ b/lib/server.go @@ -28,6 +28,7 @@ import ( "strings" "github.com/cloudflare/cfssl/log" + "github.com/hyperledger/fabric-ca/util" _ "github.com/go-sql-driver/mysql" // import to support MySQL _ "github.com/lib/pq" // import to support Postgres @@ -67,7 +68,7 @@ func (s *Server) Init(renew bool) (err error) { return err } - err = s.initCA(&s.CA, renew) + err = s.initDefaultCA(&s.CA, renew) if err != nil { return err } @@ -121,12 +122,13 @@ func (s *Server) RegisterBootstrapUser(user, pass, affiliation string) error { if user == "" || pass == "" { return errors.New("Empty identity name and/or pass not allowed") } - id := ServerConfigIdentity{ + + id := CAConfigIdentity{ Name: user, Pass: pass, Type: "user", Affiliation: affiliation, - MaxEnrollments: s.Config.Registry.MaxEnrollments, + MaxEnrollments: s.CA.Config.Registry.MaxEnrollments, Attrs: map[string]string{ "hf.Registrar.Roles": "client,user,peer,validator,auditor", "hf.Registrar.DelegateRoles": "client,user,validator,auditor", @@ -134,8 +136,10 @@ func (s *Server) RegisterBootstrapUser(user, pass, affiliation string) error { "hf.IntermediateCA": "true", }, } - registry := &s.Config.Registry + + registry := &s.CA.Config.Registry registry.Identities = append(registry.Identities, id) + log.Debugf("Registered bootstrap identity: %+v", &id) return nil } @@ -166,17 +170,19 @@ func (s *Server) initConfig() (err error) { log.Level = log.LevelDebug } + s.makeFileNamesAbsolute() + return nil } -func (s *Server) initCA(ca *CA, renew bool) error { - err := initCA(ca, s.HomeDir, s.ParentServerURL, s.Config, renew) +func (s *Server) initDefaultCA(ca *CA, renew bool) error { + err := initCA(ca, s.HomeDir, s.CA.Config, s, renew) if err != nil { return err } - if s.CA.Config.CA.Name == "" { - s.CA.Config.CA.Name = DefaultCAName + if ca.Config.CA.Name == "" { + ca.Config.CA.Name = DefaultCAName } return nil @@ -297,3 +303,19 @@ func (s *Server) serve() error { } return s.serveError } + +// Make all file names in the config absolute +func (s *Server) makeFileNamesAbsolute() error { + fields := []*string{ + &s.Config.TLS.CertFile, + &s.Config.TLS.KeyFile, + } + for _, namePtr := range fields { + abs, err := util.MakeFileAbs(*namePtr, s.HomeDir) + if err != nil { + return err + } + *namePtr = abs + } + return nil +} diff --git a/lib/server_test.go b/lib/server_test.go index e75239bd1..818cc1d5e 100644 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -497,11 +497,15 @@ func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *S } srv := &Server{ Config: &ServerConfig{ - Port: port, - Debug: true, - Affiliations: affiliations, - Registry: ServerConfigRegistry{ - MaxEnrollments: maxEnroll, + Port: port, + Debug: true, + }, + CA: CA{ + Config: &CAConfig{ + Affiliations: affiliations, + Registry: CAConfigRegistry{ + MaxEnrollments: maxEnroll, + }, }, }, HomeDir: home, diff --git a/lib/serverconfig.go b/lib/serverconfig.go index e1c7c4488..eadc74f19 100644 --- a/lib/serverconfig.go +++ b/lib/serverconfig.go @@ -17,12 +17,7 @@ limitations under the License. package lib import ( - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/csr" - "github.com/hyperledger/fabric-ca/lib/ldap" "github.com/hyperledger/fabric-ca/lib/tls" - "github.com/hyperledger/fabric-ca/util" - "github.com/hyperledger/fabric/bccsp/factory" ) const ( @@ -41,53 +36,12 @@ const ( // "help" - the help message to display on the command line; // "skip" - to skip the field. type ServerConfig struct { - Port int `def:"7054" opt:"p" help:"Listening port of fabric-ca-server"` - Address string `def:"0.0.0.0" help:"Listening address of fabric-ca-server"` - Debug bool `def:"false" opt:"d" help:"Enable debug level logging"` - TLS tls.ServerTLSConfig - CSP *factory.FactoryOpts - CA ServerConfigCA - Signing *config.Signing - CSR csr.CertificateRequest - Registry ServerConfigRegistry - Affiliations map[string]interface{} - LDAP ldap.Config - DB ServerConfigDB - Remote string `skip:"true"` - Client *ClientConfig -} - -// ServerConfigCA is the CA config for the fabric-ca server -type ServerConfigCA struct { - Name string `opt:"n" help:"Certificate Authority name"` - Keyfile string `def:"ca-key.pem" help:"PEM-encoded CA key file"` - Certfile string `def:"ca-cert.pem" help:"PEM-encoded CA certificate file"` - Chainfile string `def:"ca-chain.pem" help:"PEM-encoded CA chain file"` -} - -// ServerConfigDB is the database part of the server's config -type ServerConfigDB struct { - Type string `def:"sqlite3" help:"Type of database; one of: sqlite3, postgres, mysql"` - Datasource string `def:"fabric-ca-server.db" help:"Data source which is database specific"` - TLS tls.ClientTLSConfig -} - -// ServerConfigRegistry is the registry part of the server's config -type ServerConfigRegistry struct { - MaxEnrollments int `def:"0" help:"Maximum number of enrollments; valid if LDAP not enabled"` - Identities []ServerConfigIdentity -} - -// ServerConfigIdentity is identity information in the server's config -type ServerConfigIdentity struct { - Name string - Pass string `secret:"password"` - Type string - Affiliation string - MaxEnrollments int - Attrs map[string]string -} - -func (sc *ServerConfigIdentity) String() string { - return util.StructToString(sc) + Port int `def:"7054" opt:"p" help:"Listening port of fabric-ca-server"` + Address string `def:"0.0.0.0" help:"Listening address of fabric-ca-server"` + Debug bool `def:"false" opt:"d" help:"Enable debug level logging"` + TLS tls.ServerTLSConfig + Remote string `skip:"true"` + Client *ClientConfig + // caCfg is the default CA's config + CAcfg CAConfig `skip:"true"` } diff --git a/lib/serverregister.go b/lib/serverregister.go index 5c409e677..68e5e7723 100644 --- a/lib/serverregister.go +++ b/lib/serverregister.go @@ -132,7 +132,7 @@ func (h *registerHandler) registerUserID(req *api.RegistrationRequestNet) (strin req.Secret = util.RandomString(12) } - maxEnrollments := h.server.Config.Registry.MaxEnrollments + maxEnrollments := h.server.CA.Config.Registry.MaxEnrollments if (req.MaxEnrollments > maxEnrollments && maxEnrollments != 0) || (req.MaxEnrollments < 0) { return "", fmt.Errorf("Invalid max enrollment value specified, value must be equal to or less then %d", maxEnrollments) diff --git a/lib/servertcert.go b/lib/servertcert.go index 7594fd8d9..0d393f2a2 100644 --- a/lib/servertcert.go +++ b/lib/servertcert.go @@ -49,8 +49,8 @@ func newTCertHandler(server *Server) (h http.Handler, err error) { func initTCertHandler(server *Server) (h http.Handler, err error) { log.Debug("Initializing TCert handler") - keyfile := server.Config.CA.Keyfile - certfile := server.Config.CA.Certfile + keyfile := server.CA.Config.CA.Keyfile + certfile := server.CA.Config.CA.Certfile mgr, err := tcert.LoadMgr(keyfile, certfile) if err != nil { return nil, err diff --git a/util/struct.go b/util/struct.go index dc18a85a8..c1636fdd0 100644 --- a/util/struct.go +++ b/util/struct.go @@ -81,6 +81,11 @@ func parse(ptr interface{}, cb func(*Field) error, parent *Field) error { return err } if kind == reflect.Struct { + // Skip parsing the entire struct if "skip" tag is present on a struct field + if tf.Tag.Get(TagSkip) == "true" { + continue + } + err := parse(field.Addr, cb, field) if err != nil { return err