Skip to content

Commit

Permalink
[FAB-7882] Need wildcard for bootstrap user
Browse files Browse the repository at this point in the history
Bootstrap user has no way to introduce new types
for hf.Registrar.Roles, and is limited to registering
types that it was bootstrapped with at server
start up. A bootstrap user should have the ability
to introduce new types, this is allowed by having
a wildcard '*' for hf.Registrar.Roles and
hf.Registrar.DelegateRoles attributes.

Change-Id: I80eca0102bcd6c6a48cd2a8adfb1404225c98cf5
Signed-off-by: Saad Karim <[email protected]>
Signed-off-by: Anil Ambati <[email protected]>
  • Loading branch information
Saad Karim committed May 7, 2018
1 parent 45653f2 commit 2b5ed40
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 58 deletions.
4 changes: 2 additions & 2 deletions cmd/fabric-ca-server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/source/serverconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions lib/attr/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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'
Expand Down Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions lib/dbaccessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"

"github.com/hyperledger/fabric-ca/lib/attr"
"github.com/hyperledger/fabric-ca/util"

"github.com/pkg/errors"

Expand Down Expand Up @@ -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 {
Expand All @@ -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))

This comment has been minimized.

Copy link
@nyetwurk

nyetwurk Mar 5, 2019

This won't work without args for Queryx()

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 {
Expand Down
4 changes: 2 additions & 2 deletions lib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion lib/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
108 changes: 108 additions & 0 deletions lib/serveridentities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
60 changes: 13 additions & 47 deletions lib/serverregister.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package lib
import (
"fmt"
"net/url"
"strings"

"github.com/pkg/errors"

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion lib/serverrequestcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion scripts/fvt/ident_modify_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"'
Expand Down
12 changes: 12 additions & 0 deletions util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
10 changes: 10 additions & 0 deletions util/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

0 comments on commit 2b5ed40

Please sign in to comment.