diff --git a/cmd/fabric-ca-client/enroll.go b/cmd/fabric-ca-client/enroll.go index 4eebff2b8..abfe679cb 100644 --- a/cmd/fabric-ca-client/enroll.go +++ b/cmd/fabric-ca-client/enroll.go @@ -74,8 +74,6 @@ func runEnroll(cmd *cobra.Command) error { return err } - log.Debugf("Client configuration settings: %+v", clientCfg) - cfgFile, err := ioutil.ReadFile(cfgFileName) if err != nil { return err diff --git a/cmd/fabric-ca-client/revoke.go b/cmd/fabric-ca-client/revoke.go index 6ca0ea054..bac9dfce5 100644 --- a/cmd/fabric-ca-client/revoke.go +++ b/cmd/fabric-ca-client/revoke.go @@ -88,7 +88,7 @@ func runRevoke() error { aki := viper.GetString("aki") if enrollmentID == "" && serial == "" { - return fmt.Errorf("Invalid usage; either ENROLLMENT_ID or both --serial and --aki are required") + return fmt.Errorf("Invalid usage; either --eid or both --serial and --aki are required") } err = id.Revoke( diff --git a/lib/client.go b/lib/client.go index 3ac27b8d2..eab746ed8 100644 --- a/lib/client.go +++ b/lib/client.go @@ -103,12 +103,12 @@ type Client struct { // Enroll enrolls a new identity // @param req The enrollment request func (c *Client) Enroll(req *api.EnrollmentRequest) (*Identity, error) { - log.Debugf("Enrolling %+v", req) + log.Debugf("Enrolling %+v", &req) // Generate the CSR csrPEM, key, err := c.GenCSR(req.CSR, req.Name) if err != nil { - log.Debugf("enroll failure generating CSR: %s", err) + log.Debugf("Enroll failure generating CSR: %s", err) return nil, err } @@ -154,7 +154,7 @@ func (c *Client) newIdentityFromResponse(result interface{}, id string, key []by // GenCSR generates a CSR (Certificate Signing Request) func (c *Client) GenCSR(req *api.CSRInfo, id string) ([]byte, []byte, error) { - log.Debugf("GenCSR %+v", req) + log.Debugf("GenCSR %+v", &req) cr := c.newCertificateRequest(req) cr.CN = id @@ -215,6 +215,7 @@ func (c *Client) GetMyKeyFile() string { if file == "" { file = path.Join(c.GetMyEnrollmentDir(), "key.pem") } + log.Debugf("Key file location: %s", file) return file } @@ -224,6 +225,7 @@ func (c *Client) GetMyCertFile() string { if file == "" { file = path.Join(c.GetMyEnrollmentDir(), "cert.pem") } + log.Debugf("Cert file location: %s", file) return file } @@ -332,7 +334,7 @@ func (c *Client) SendPost(req *http.Request) (interface{}, error) { } if len(body.Errors) > 0 { msg := body.Errors[0].Message - return nil, fmt.Errorf("Error response from server was '%s' for request:\n%s", msg, reqStr) + return nil, fmt.Errorf("Error response from server was: %s", msg) } } scode := resp.StatusCode diff --git a/lib/client_test.go b/lib/client_test.go index b1bdec2ef..4fda73d34 100644 --- a/lib/client_test.go +++ b/lib/client_test.go @@ -34,7 +34,8 @@ import ( var ( tdDir = "../testdata" - fcaDB = path.Join(tdDir, "fabric-ca.db") + fcaDB = path.Join(tdDir, "fabric-ca-server.db") + fcaDB2 = path.Join(tdDir, "fabric-ca.db") cfgFile = path.Join(tdDir, "config.json") testCfgFile = "testconfig.json" clientConfig = path.Join(tdDir, "client-config.json") @@ -79,7 +80,7 @@ func testRegister(c *Client, t *testing.T) { // Register as admin registerReq := &api.RegistrationRequest{ - Name: "TestUser", + Name: "TestUser2", Type: "Client", Affiliation: "bank_a", } @@ -209,6 +210,79 @@ func testLoadBadCSRInfo(c *Client, t *testing.T) { } } +func TestCustomizableMaxEnroll(t *testing.T) { + os.Remove("../testdata/fabric-ca-server.db") + + srv := getServer(rootPort, testdataDir, "", 3, t) + + srv.Config.Registry.MaxEnrollments = 3 + srv.Config.Debug = true + + err := srv.Start() + if err != nil { + t.Errorf("Server start failed: %s", err) + } + + testTooManyEnrollments(t) + testIncorrectEnrollment(t) + + err = srv.Stop() + if err != nil { + t.Errorf("Server stop failed: %s", err) + } +} + +func testTooManyEnrollments(t *testing.T) { + clientConfig := &ClientConfig{ + URL: fmt.Sprintf("http://localhost:%d", rootPort), + } + + rawURL := fmt.Sprintf("http://admin:adminpw@localhost:%d", rootPort) + + _, err := clientConfig.Enroll(rawURL, testdataDir) + if err != nil { + t.Errorf("Failed to enroll: %s", err) + } + + _, err = clientConfig.Enroll(rawURL, testdataDir) + if err != nil { + t.Errorf("Failed to enroll: %s", err) + } + + id, err := clientConfig.Enroll(rawURL, testdataDir) + if err != nil { + t.Errorf("Failed to enroll: %s", err) + } + + _, err = clientConfig.Enroll(rawURL, testdataDir) + if err == nil { + t.Errorf("Enroll should have failed, no more enrollments left") + } + + id.Store() +} + +func testIncorrectEnrollment(t *testing.T) { + c := getTestClient(rootPort) + + id, err := c.LoadMyIdentity() + if err != nil { + t.Error("Failed to load identity") + } + + req := &api.RegistrationRequest{ + Name: "TestUser", + Type: "Client", + Affiliation: "hyperledger", + MaxEnrollments: 4, + } + + _, err = id.Register(req) + if err == nil { + t.Error("Registration should have failed, can't register user with max enrollment greater than server max enrollment setting") + } +} + func TestNormalizeUrl(t *testing.T) { _, err := NormalizeURL("") if err != nil { @@ -256,6 +330,7 @@ func startServer() int { if !serverStarted { os.Remove(fcaDB) + os.Remove(fcaDB2) os.RemoveAll(dir) serverStarted = true fmt.Println("starting fabric-ca server ...") @@ -281,5 +356,8 @@ func runServer() { func TestLast(t *testing.T) { // Cleanup os.Remove(fcaDB) + os.Remove(fcaDB2) + os.Remove("../testdata/cert.pem") + os.Remove("../testdata/key.pem") os.RemoveAll(dir) } diff --git a/lib/dbaccessor.go b/lib/dbaccessor.go index 2b03adf27..e3f867de1 100644 --- a/lib/dbaccessor.go +++ b/lib/dbaccessor.go @@ -355,7 +355,7 @@ func (u *DBUser) Login(pass string) error { // If maxEnrollments is set to 0, user has unlimited enrollment if u.MaxEnrollments != 0 { if u.State >= u.MaxEnrollments { - return fmt.Errorf("The maximum number of enrollments is %d", u.MaxEnrollments) + return fmt.Errorf("No more enrollments left. The maximum number of enrollments is %d", u.MaxEnrollments) } } diff --git a/lib/identity.go b/lib/identity.go index ab62f4f43..b49013bc0 100644 --- a/lib/identity.go +++ b/lib/identity.go @@ -78,7 +78,7 @@ func (i *Identity) Register(req *api.RegistrationRequest) (rr *api.RegistrationR var secret string var resp interface{} - log.Debugf("Register %+v", req) + log.Debugf("Register %+v", &req) if req.Name == "" { return nil, errors.New("Register was called without a Name set") } @@ -113,7 +113,7 @@ func (i *Identity) Register(req *api.RegistrationRequest) (rr *api.RegistrationR // Reenroll reenrolls an existing Identity and returns a new Identity // @param req The reenrollment request func (i *Identity) Reenroll(req *api.ReenrollmentRequest) (*Identity, error) { - log.Debugf("Reenrolling %s", req) + log.Debugf("Reenrolling %s", &req) csrPEM, key, err := i.client.GenCSR(req.CSR, i.GetName()) if err != nil { @@ -142,7 +142,7 @@ func (i *Identity) Reenroll(req *api.ReenrollmentRequest) (*Identity, error) { // Revoke the identity associated with 'id' func (i *Identity) Revoke(req *api.RevocationRequest) error { - log.Debugf("Entering identity.Revoke %+v", req) + log.Debugf("Entering identity.Revoke %+v", &req) reqBody, err := util.Marshal(req, "RevocationRequest") if err != nil { return err diff --git a/lib/server.go b/lib/server.go index fdd430c93..bb62bdebc 100644 --- a/lib/server.go +++ b/lib/server.go @@ -163,7 +163,7 @@ func (s *Server) Stop() error { // Initialize the fabric-ca server's key material func (s *Server) initKeyMaterial(renew bool) error { - log.Debugf("Init with home %s and config %+v", s.HomeDir, s.Config) + log.Debugf("Init with home %s and config %+v", s.HomeDir, *s.Config) // Make the path names absolute in the config s.makeFileNamesAbsolute() @@ -269,11 +269,13 @@ func (s *Server) RegisterBootstrapUser(user, pass, affiliation string) error { if err != nil { return fmt.Errorf("Failed to register bootstrap user '%s': %s", user, err) } + id := ServerConfigIdentity{ - Name: user, - Pass: pass, - Type: "user", - Affiliation: affiliation, + Name: user, + Pass: pass, + Type: "user", + Affiliation: affiliation, + MaxEnrollments: s.Config.Registry.MaxEnrollments, Attrs: map[string]string{ "hf.Registrar.Roles": "client,user,peer,validator,auditor", "hf.Registrar.DelegateRoles": "client,user,validator,auditor", @@ -334,22 +336,25 @@ func (s *Server) initConfig() (err error) { func (s *Server) initDB() error { db := &s.Config.DB - log.Debugf("Initializing '%s' data base at '%s'", db.Type, db.Datasource) - var err error var exists bool + MaxEnrollments = s.Config.Registry.MaxEnrollments + if db.Type == "" { db.Type = "sqlite3" } if db.Datasource == "" { - var ds string - ds, err = util.MakeFileAbs("fabric-ca-server.db", s.HomeDir) - if err != nil { - return err - } - db.Datasource = ds + db.Datasource = "fabric-ca-server.db" } + + db.Datasource, err = util.MakeFileAbs(db.Datasource, s.HomeDir) + if err != nil { + return err + } + + log.Debugf("Initializing '%s' data base at '%s'", db.Type, db.Datasource) + switch db.Type { case "sqlite3": s.db, exists, err = dbutil.NewUserRegistrySQLLite3(db.Datasource) diff --git a/lib/server_test.go b/lib/server_test.go index 87dd367b4..eb815dd2e 100644 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -174,7 +174,7 @@ func TestIntermediateServer(t *testing.T) { } } func TestRunningTLSServer(t *testing.T) { - srv := getServer(rootPort, testdataDir, "", t) + srv := getServer(rootPort, testdataDir, "", 0, t) srv.Config.TLS.Enabled = true srv.Config.TLS.CertFile = "../testdata/tls_server-cert.pem" @@ -244,7 +244,7 @@ func getRootServerURL() string { } func getRootServer(t *testing.T) *lib.Server { - return getServer(rootPort, rootDir, "", t) + return getServer(rootPort, rootDir, "", 0, t) } func getIntermediateServer(idx int, t *testing.T) *lib.Server { @@ -252,10 +252,11 @@ func getIntermediateServer(idx int, t *testing.T) *lib.Server { intermediatePort, path.Join(intermediateDir, strconv.Itoa(idx)), getRootServerURL(), + 0, t) } -func getServer(port int, home, parentURL string, t *testing.T) *lib.Server { +func getServer(port int, home, parentURL string, maxEnroll int, t *testing.T) *lib.Server { if home != testdataDir { os.RemoveAll(home) } @@ -272,6 +273,9 @@ func getServer(port int, home, parentURL string, t *testing.T) *lib.Server { Port: port, Debug: true, Affiliations: affiliations, + Registry: lib.ServerConfigRegistry{ + MaxEnrollments: maxEnroll, + }, }, HomeDir: home, ParentServerURL: parentURL, diff --git a/lib/serverauth.go b/lib/serverauth.go index bdb4372ae..ec3ce9573 100644 --- a/lib/serverauth.go +++ b/lib/serverauth.go @@ -40,7 +40,7 @@ type fcaAuthHandler struct { next http.Handler } -var authError = cerr.NewBadRequest(errors.New("authorization failure")) +var authError = cerr.NewBadRequest(errors.New("Authorization failure")) // NewAuthWrapper is auth wrapper constructor. // Only the "enroll" URI uses basic auth for the enrollment secret, while all diff --git a/lib/serverregister.go b/lib/serverregister.go index 388687cb7..5070590cb 100644 --- a/lib/serverregister.go +++ b/lib/serverregister.go @@ -18,6 +18,7 @@ package lib import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -63,56 +64,58 @@ func (h *registerHandler) Handle(w http.ResponseWriter, r *http.Request) error { // Register User callerID := r.Header.Get(enrollmentIDHdrName) - secret, err := h.RegisterUser(req.Name, req.Type, req.Affiliation, req.Attributes, callerID) + secret, err := h.RegisterUser(&req, callerID) if err != nil { return err } resp := &api.RegistrationResponseNet{RegistrationResponse: api.RegistrationResponse{Secret: secret}} - log.Debugf("Registration completed - sending response %+v", resp) + log.Debugf("Registration completed - sending response %+v", &resp) return cfsslapi.SendResponse(w, resp) } // RegisterUser will register a user -func (h *registerHandler) RegisterUser(id string, userType string, affiliation string, attributes []api.Attribute, registrar string, opt ...string) (string, error) { - log.Debugf("Received request to register user with id: %s, affiliation: %s, attributes: %+v, registrar: %s\n", - id, affiliation, attributes, registrar) +func (h *registerHandler) RegisterUser(req *api.RegistrationRequestNet, registrar string) (string, error) { + + secret := req.Secret + req.Secret = "<>" + log.Debugf("Received registration request from %s: %+v", registrar, req) + req.Secret = secret - var tok string var err error if registrar != "" { // Check the permissions of member named 'registrar' to perform this registration - err = h.canRegister(registrar, userType) + err = h.canRegister(registrar, req.Type) if err != nil { - log.Debugf("Registration of '%s' failed: %s", id, err) + log.Debugf("Registration of '%s' failed: %s", req.Name, err) return "", err } } - err = h.validateID(id, userType, affiliation) + err = h.validateID(req) if err != nil { - log.Debugf("Registration of '%s' failed: %s", id, err) + log.Debugf("Registration of '%s' failed: %s", req.Name, err) return "", err } - tok, err = h.registerUserID(id, userType, affiliation, attributes, opt...) + secret, err = h.registerUserID(req) if err != nil { - log.Debugf("Registration of '%s' failed: %s", id, err) + log.Debugf("Registration of '%s' failed: %s", req.Name, err) return "", err } - return tok, nil + return secret, nil } -func (h *registerHandler) validateID(id string, userType string, affiliation string) error { +func (h *registerHandler) validateID(req *api.RegistrationRequestNet) error { log.Debug("Validate ID") // Check whether the affiliation is required for the current user. - if h.requireAffiliation(userType) { + if h.requireAffiliation(req.Type) { // If yes, is the affiliation valid - err := h.isValidAffiliation(affiliation) + err := h.isValidAffiliation(req.Affiliation) if err != nil { return err } @@ -121,30 +124,32 @@ func (h *registerHandler) validateID(id string, userType string, affiliation str } // registerUserID registers a new user and its enrollmentID, role and state -func (h *registerHandler) registerUserID(id string, userType string, affiliation string, attributes []api.Attribute, opt ...string) (string, error) { - log.Debugf("Registering user id: %s\n", id) +func (h *registerHandler) registerUserID(req *api.RegistrationRequestNet) (string, error) { + log.Debugf("Registering user id: %s\n", req.Name) - var tok string - if len(opt) > 0 && len(opt[0]) > 0 { - tok = opt[0] - } else { - tok = util.RandomString(12) + if req.Secret == "" { + req.Secret = util.RandomString(12) } - // affiliationPath(name, parent) + if req.MaxEnrollments == 0 { + req.MaxEnrollments = MaxEnrollments + } + if MaxEnrollments > 0 && req.MaxEnrollments > MaxEnrollments { + return "", fmt.Errorf("Invalid max enrollment value specified, value must be equal to or less then %d", MaxEnrollments) + } insert := spi.UserInfo{ - Name: id, - Pass: tok, - Type: userType, - Affiliation: affiliation, - Attributes: attributes, - MaxEnrollments: MaxEnrollments, + Name: req.Name, + Pass: req.Secret, + Type: req.Type, + Affiliation: req.Affiliation, + Attributes: req.Attributes, + MaxEnrollments: req.MaxEnrollments, } - _, err := UserRegistry.GetUser(id, nil) + _, err := UserRegistry.GetUser(req.Name, nil) if err == nil { - return "", fmt.Errorf("User '%s' is already registered", id) + return "", fmt.Errorf("User '%s' is already registered", req.Name) } err = UserRegistry.InsertUser(insert) @@ -152,7 +157,7 @@ func (h *registerHandler) registerUserID(id string, userType string, affiliation return "", err } - return tok, nil + return req.Secret, nil } func (h *registerHandler) isValidAffiliation(affiliation string) error { @@ -187,8 +192,12 @@ func (h *registerHandler) canRegister(registrar string, userType string) error { } else { roles = make([]string, 0) } - if !util.StrContained(userType, roles) { - return fmt.Errorf("User '%s' may not register type '%s'", registrar, userType) + if userType != "" { + if !util.StrContained(userType, roles) { + return fmt.Errorf("User '%s' may not register type '%s'", registrar, userType) + } + } else { + return errors.New("No user type provied. Please provide user type") } return nil diff --git a/lib/serverrevoke.go b/lib/serverrevoke.go index becb9f850..a20b03176 100644 --- a/lib/serverrevoke.go +++ b/lib/serverrevoke.go @@ -119,6 +119,11 @@ func (h *revokeHandler) Handle(w http.ResponseWriter, r *http.Request) error { log.Warningf("No certificates were revoked for '%s' but the ID was disabled: %s", req.Name, err) return dbErr(w, err) } + + if len(recs) == 0 { + return fmt.Errorf("Enrollment ID '%s' has no revocable certificates", req.Name) + } + log.Debugf("Revoked the following certificates owned by '%s': %+v", req.Name, recs) } else {