diff --git a/cmd/fabric-ca-server/config.go b/cmd/fabric-ca-server/config.go index 4f40c1995..1d27f610f 100644 --- a/cmd/fabric-ca-server/config.go +++ b/cmd/fabric-ca-server/config.go @@ -170,8 +170,8 @@ registry: type: client affiliation: "" attrs: - hf.Registrar.Roles: "peer,orderer,client,user" - hf.Registrar.DelegateRoles: "peer,orderer,client,user" + hf.Registrar.Roles: "*" + hf.Registrar.DelegateRoles: "*" hf.Revoker: true hf.IntermediateCA: true hf.GenCRL: true diff --git a/docs/source/serverconfig.rst b/docs/source/serverconfig.rst index 2171c4060..cc58eb469 100644 --- a/docs/source/serverconfig.rst +++ b/docs/source/serverconfig.rst @@ -133,8 +133,8 @@ Fabric-CA Server's Configuration File type: client affiliation: "" attrs: - hf.Registrar.Roles: "peer,orderer,client,user" - hf.Registrar.DelegateRoles: "peer,orderer,client,user" + hf.Registrar.Roles: "*" + hf.Registrar.DelegateRoles: "*" hf.Revoker: true hf.IntermediateCA: true hf.GenCRL: true diff --git a/lib/attr/attribute.go b/lib/attr/attribute.go index 9a8f052fa..37db36c94 100644 --- a/lib/attr/attribute.go +++ b/lib/attr/attribute.go @@ -238,9 +238,9 @@ func (ac *attributeControl) validateListAttribute(requestedAttr *api.Attribute, return nil } // Make sure the values requested for attribute is equal to or a subset of the registrar's attribute - err := util.IsSubsetOf(requestedAttrValue, callersAttrValue) + err := ac.IsSubsetOf(requestedAttrValue, callersAttrValue) if err != nil { - return errors.WithMessage(err, fmt.Sprintf("The requested values for attribute '%s' is a superset of the caller's attribute value", ac.getName())) + return err } // If requested attribute is 'hf.Registrar.DeletegateRoles', make sure it is equal or a subset of the user's hf.Registrar.Roles attribute if ac.getName() == DelegateRoles { @@ -252,6 +252,17 @@ func (ac *attributeControl) validateListAttribute(requestedAttr *api.Attribute, return nil } +func (ac *attributeControl) IsSubsetOf(requestedAttrValue, callersAttrValue string) error { + if (ac.getName() == Roles || ac.getName() == DelegateRoles) && util.ListContains(callersAttrValue, "*") { + return nil + } + err := util.IsSubsetOf(requestedAttrValue, callersAttrValue) + if err != nil { + return errors.WithMessage(err, fmt.Sprintf("The requested values for attribute '%s' is a superset of the caller's attribute value", ac.getName())) + } + return nil +} + // Check if registrar has the proper authority to register the values for 'hf.Registrar.Attributes'. // Registering 'hf.Registrar.Attributes' with a value that has a 'hf.' prefix requires that the user // being registered to possess that hf. attribute. For example, if attribute is 'hf.Registrar.Attributes=hf.Revoker' @@ -313,6 +324,9 @@ func checkDelegateRoleValues(reqAttrs []api.Attribute, user AttributeControl) er } } } + if util.ListContains(roles, "*") { + return nil + } delegateRoles := GetAttrValue(reqAttrs, DelegateRoles) err := util.IsSubsetOf(delegateRoles, roles) if err != nil { diff --git a/lib/dbaccessor.go b/lib/dbaccessor.go index 726e32299..1e3024852 100644 --- a/lib/dbaccessor.go +++ b/lib/dbaccessor.go @@ -21,6 +21,7 @@ import ( "strings" "github.com/hyperledger/fabric-ca/lib/attr" + "github.com/hyperledger/fabric-ca/util" "github.com/pkg/errors" @@ -626,7 +627,17 @@ func (d *Accessor) GetFilteredUsers(affiliation, types string) (*sqlx.Rows, erro typesArray[i] = strings.TrimSpace(typesArray[i]) } + // If root affiliation, allowed to get back users of all affiliations if affiliation == "" { + if util.ListContains(types, "*") { // If type is '*', allowed to get back of all types + query := "SELECT * FROM users" + rows, err := d.db.Queryx(d.db.Rebind(query)) + if err != nil { + return nil, errors.Wrapf(err, "Failed to execute query '%s' for affiliation '%s' and types '%s'", query, affiliation, types) + } + return rows, nil + } + query := "SELECT * FROM users WHERE (type IN (?))" query, args, err := sqlx.In(query, typesArray) if err != nil { @@ -640,6 +651,15 @@ func (d *Accessor) GetFilteredUsers(affiliation, types string) (*sqlx.Rows, erro } subAffiliation := affiliation + ".%" + if util.ListContains(types, "*") { // If type is '*', allowed to get back of all types for requested affiliation + query := "SELECT * FROM users WHERE ((affiliation = ?) OR (affiliation LIKE ?))" + rows, err := d.db.Queryx(d.db.Rebind(query)) + if err != nil { + return nil, errors.Wrapf(err, "Failed to execute query '%s' for affiliation '%s' and types '%s'", query, affiliation, types) + } + return rows, nil + } + query := "SELECT * FROM users WHERE ((affiliation = ?) OR (affiliation LIKE ?)) AND (type IN (?))" inQuery, args, err := sqlx.In(query, affiliation, subAffiliation, typesArray) if err != nil { diff --git a/lib/server.go b/lib/server.go index 29f8eb907..52c66b4ea 100644 --- a/lib/server.go +++ b/lib/server.go @@ -206,8 +206,8 @@ func (s *Server) RegisterBootstrapUser(user, pass, affiliation string) error { Affiliation: affiliation, MaxEnrollments: 0, // 0 means to use the server's max enrollment setting Attrs: map[string]string{ - attr.Roles: allRoles, - attr.DelegateRoles: allRoles, + attr.Roles: "*", + attr.DelegateRoles: "*", attr.Revoker: "true", attr.IntermediateCA: "true", attr.GenCRL: "true", diff --git a/lib/server_test.go b/lib/server_test.go index 4ec41fda2..58e24e256 100644 --- a/lib/server_test.go +++ b/lib/server_test.go @@ -197,7 +197,7 @@ func TestSRVRootServer(t *testing.T) { Secret: "adminpw", }) if err != nil { - t.Fatalf("Failed to enroll admin/adminpw: %s", err) + t.Fatalf("Failed to enroll admin2/admin2pw: %s", err) } admin = eresp.Identity // test registration permissions wrt roles and affiliation diff --git a/lib/serveridentities_test.go b/lib/serveridentities_test.go index 2f45df1a4..d781b7db9 100644 --- a/lib/serveridentities_test.go +++ b/lib/serveridentities_test.go @@ -839,6 +839,114 @@ func TestDynamicWithMultCA(t *testing.T) { } +func TestBootstrapUserAddingRoles(t *testing.T) { + os.RemoveAll(rootDir) + defer os.RemoveAll(rootDir) + os.RemoveAll("../testdata/msp") + defer os.RemoveAll("../testdata/msp") + + var err error + + srv := TestGetRootServer(t) + err = srv.Start() + util.FatalError(t, err, "Failed to start server") + defer srv.Stop() + + client := getTestClient(7075) + resp, err := client.Enroll(&api.EnrollmentRequest{ + Name: "admin", + Secret: "adminpw", + }) + util.FatalError(t, err, "Failed to enroll user 'admin'") + + admin := resp.Identity + + // Bootstrap user with '*' for hf.Registrar.Roles should be able to register any type + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser", + Type: "newType", + }) + assert.NoError(t, err, "Bootstrap user with '*' for hf.Registrar.Roles should be able to register any type") + + // Bootstrap user with '*' for hf.Registrar.Roles should be able to register any value for hf.Registrar.Roles + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser2", + Type: "client", + Attributes: []api.Attribute{ + api.Attribute{ + Name: "hf.Registrar.Roles", + Value: "peer,client,user,orderer,newType", + }, + }, + }) + assert.NoError(t, err, "Bootstrap user with '*' for hf.Registrar.Roles should be able to register any value for hf.Registrar.Roles") + + // Bootstrap user should be able to register any value for hf.Registrar.Roles, but not be able to specify '*' for + // hf.Registrar.DelegateRoles if hf.Registrar.Roles is not a '*' + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser2", + Type: "client", + Attributes: []api.Attribute{ + api.Attribute{ + Name: "hf.Registrar.Roles", + Value: "peer,client,user,orderer,newType", + }, + api.Attribute{ + Name: "hf.Registrar.DelegateRoles", + Value: "*", + }, + }, + }) + assert.Error(t, err, "Bootstrap user should be able to register any value for hf.Registrar.Roles, but not be able to specify '*' for hf.Registrar.DelegateRoles if hf.Registrar.Roles is not a '*'") + + // Bootstrap should fail to register hf.Registrar.DelegateRoles without giving hf.Registrar.Roles attribute to the identity being registered + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser3", + Type: "client", + Attributes: []api.Attribute{ + api.Attribute{ + Name: "hf.Registrar.DelegateRoles", + Value: "peer,client,user,orderer,newType", + }, + }, + }) + assert.Error(t, err, "Should fail to register hf.Registrar.DelegateRoles without having hf.Registrar.Roles") + + // Bootstrap should be able to register '*' for hf.Registrar.Roles and provide any value for hf.Registrar.DelegateRoles + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser3", + Type: "client", + Attributes: []api.Attribute{ + api.Attribute{ + Name: "hf.Registrar.Roles", + Value: "*", + }, + api.Attribute{ + Name: "hf.Registrar.DelegateRoles", + Value: "peer,client,user,orderer,newType", + }, + }, + }) + assert.NoError(t, err, "Bootstrap should be able to register star for hf.Registrar.Roles and provide any value for hf.Registrar.DelegateRoles") + + // Bootstrap user should be able to register '*' for hf.Registrar.Roles and hf.Registrar.DelegateRoles + _, err = admin.AddIdentity(&api.AddIdentityRequest{ + ID: "testuser4", + Type: "client", + Attributes: []api.Attribute{ + api.Attribute{ + Name: "hf.Registrar.Roles", + Value: "*", + }, + api.Attribute{ + Name: "hf.Registrar.DelegateRoles", + Value: "*", + }, + }, + }) + assert.NoError(t, err, "Bootstrap user should be able to register '*' for hf.Registrar.Roles and hf.Registrar.DelegateRoles") +} + func cleanMultiCADir(t *testing.T) { var err error caFolder := "../testdata/ca" diff --git a/lib/serverregister.go b/lib/serverregister.go index 72a5b26ff..741be66e1 100644 --- a/lib/serverregister.go +++ b/lib/serverregister.go @@ -19,7 +19,6 @@ package lib import ( "fmt" "net/url" - "strings" "github.com/pkg/errors" @@ -116,20 +115,24 @@ func normalizeRegistrationRequest(req *api.RegistrationRequest, registrar spi.Us } func validateAffiliation(req *api.RegistrationRequest, ctx *serverRequestContext) error { - log.Debug("Validate Affiliation") - err := ctx.ContainsAffiliation(req.Affiliation) + affiliation := req.Affiliation + log.Debugf("Validating affiliation: %s", affiliation) + + err := ctx.ContainsAffiliation(affiliation) if err != nil { return err } - return nil -} -func validateID(req *api.RegistrationRequest, ca *CA) error { - log.Debug("Validate ID") - err := isValidAffiliation(req.Affiliation, ca) + // If requested affiliation is for root then don't need to do lookup in affiliation's table + if affiliation == "" { + return nil + } + + _, err = ctx.ca.registry.GetAffiliation(affiliation) if err != nil { - return err + return errors.WithMessage(err, fmt.Sprintf("Failed getting affiliation '%s'", affiliation)) } + return nil } @@ -178,56 +181,19 @@ func registerUserID(req *api.RegistrationRequest, ca *CA) (string, error) { return req.Secret, nil } -func isValidAffiliation(affiliation string, ca *CA) error { - log.Debugf("Validating affiliation: %s", affiliation) - - // If requested affiliation is for root then don't need to do lookup in affiliation's table - if affiliation == "" { - return nil - } - - _, err := ca.registry.GetAffiliation(affiliation) - if err != nil { - return errors.WithMessage(err, fmt.Sprintf("Failed getting affiliation '%s'", affiliation)) - } - - return nil -} - func canRegister(registrar spi.User, req *api.RegistrationRequest, ctx *serverRequestContext) error { log.Debugf("canRegister - Check to see if user '%s' can register", registrar.GetName()) - var roles []string - rolesStr, isRegistrar, err := ctx.isRegistrar() + err := ctx.CanActOnType(req.Type) if err != nil { return err } - if !isRegistrar { - return errors.Errorf("'%s' does not have authority to register identities", registrar) - } - if rolesStr != "" { - roles = strings.Split(rolesStr, ",") - } else { - roles = make([]string, 0) - } - if req.Type == "" { - req.Type = "client" - } - if !util.StrContained(req.Type, roles) { - return fmt.Errorf("Identity '%s' may not register type '%s'", registrar, req.Type) - } - // Check that the affiliation requested is of the appropriate level err = validateAffiliation(req, ctx) if err != nil { return fmt.Errorf("Registration of '%s' failed in affiliation validation: %s", req.Name, err) } - err = validateID(req, ctx.ca) - if err != nil { - return errors.WithMessage(err, fmt.Sprintf("Registration of '%s' to validate", req.Name)) - } - err = attr.CanRegisterRequestedAttributes(req.Attributes, nil, registrar) if err != nil { return newAuthErr(ErrRegAttrAuth, "Failed to register attribute: %s", err) diff --git a/lib/serverrequestcontext.go b/lib/serverrequestcontext.go index a1fdf18c5..c908a47cd 100644 --- a/lib/serverrequestcontext.go +++ b/lib/serverrequestcontext.go @@ -538,13 +538,19 @@ func (ctx *serverRequestContext) canActOnType(requestedType string) (bool, error return false, newAuthErr(ErrRegAttrAuth, "'%s' is not allowed to manage users", caller.GetName()) } + if util.ListContains(typesStr, "*") { + return true, nil + } + var types []string if typesStr != "" { types = strings.Split(typesStr, ",") } else { types = make([]string, 0) } - + if requestedType == "" { + requestedType = "client" + } if !util.StrContained(requestedType, types) { log.Debugf("Caller with types '%s' is not authorized to act on '%s'", types, requestedType) return false, nil diff --git a/scripts/fvt/ident_modify_test.sh b/scripts/fvt/ident_modify_test.sh index 335402dae..6a868fb68 100755 --- a/scripts/fvt/ident_modify_test.sh +++ b/scripts/fvt/ident_modify_test.sh @@ -114,7 +114,7 @@ function testRoleAuthorization() { # the type of the identity being added must be in the user's hf.Registrar.Roles list $FABRIC_CA_CLIENTEXEC identity add userType1 $URI -H $TESTDIR/admin \ --type account --affiliation ${defaultValues[Affiliation]} 2>&1 | - grepPrint "Identity.*admin.*may not register type 'account'" || + grepPrint "Registrar does not have authority to act on type 'account'" || ErrorMsg "admin should not be able to add user of type 'account'" $FABRIC_CA_CLIENTEXEC identity modify admin $URI -H $TESTDIR/admin/ -d \ --attrs '"hf.Registrar.Roles=client,user,peer,validator,auditor,ca,app,role1,role2,role3,role4,role5,role6,role7,role8,apple,orange"' diff --git a/util/util.go b/util/util.go index 2c180873a..17cc30567 100644 --- a/util/util.go +++ b/util/util.go @@ -825,3 +825,15 @@ func ErrorContains(t *testing.T, err error, contains, msg string, args ...interf func GetSliceFromList(split string, delim string) []string { return strings.Split(strings.Replace(split, " ", "", -1), delim) } + +// ListContains looks through a comma separated list to see if a string exists +func ListContains(list, find string) bool { + items := strings.Split(list, ",") + for _, item := range items { + item = strings.TrimPrefix(item, " ") + if item == find { + return true + } + } + return false +} diff --git a/util/util_test.go b/util/util_test.go index 9174e1d25..5a45613d4 100644 --- a/util/util_test.go +++ b/util/util_test.go @@ -774,3 +774,13 @@ func TestValidateAndReturnAbsConf(t *testing.T) { t.Error("Failed to get correct path for configuration file") } } + +func TestListContains(t *testing.T) { + list := "peer, client,orderer, *" + found := ListContains(list, "*") + assert.Equal(t, found, true) + + list = "peer, client,orderer" + found = ListContains(list, "*") + assert.Equal(t, found, false) +}