Skip to content

Commit

Permalink
[FAB-6035] Validate attributes being registered
Browse files Browse the repository at this point in the history
Checks need to be performed to see that the Registrar
has proper authority to register attributes for an
identity.

See [FAB-6035] for checks that are performed.

Change-Id: I130da4b21b0b527a503cb602a0dde578b702c9a7
Signed-off-by: Saad Karim <[email protected]>
  • Loading branch information
Saad Karim committed Oct 10, 2017
1 parent 0a42217 commit aa10999
Show file tree
Hide file tree
Showing 15 changed files with 559 additions and 62 deletions.
4 changes: 2 additions & 2 deletions cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ id:
affiliation:
maxenrollments: -1
attributes:
- name:
value:
# - name:
# value:
#############################################################################
# Enrollment section used to enroll an identity with fabric-ca server
Expand Down
4 changes: 2 additions & 2 deletions docs/source/clientconfig.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ Fabric-CA Client's Configuration File
affiliation:
maxenrollments: -1
attributes:
- name:
value:
# - name:
# value:
#############################################################################
# Enrollment section used to enroll an identity with fabric-ca server
Expand Down
50 changes: 48 additions & 2 deletions docs/source/users-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -979,13 +979,14 @@ The identity performing the register request must be currently enrolled, and
must also have the proper authority to register the type of the identity that is being
registered.

In particular, two authorization checks are made by the Fabric CA server
In particular, three authorization checks are made by the Fabric CA server
during registration as follows:

1. The invoker's identity must have the "hf.Registrar.Roles" attribute with a
comma-separated list of values where one of the value equals the type of
identity being registered; for example, if the invoker's identity has the
"hf.Registrar.Roles" attribute with a value of "peer,app,user", the invoker can register identities of type peer, app, and user, but not orderer.
"hf.Registrar.Roles" attribute with a value of "peer,app,user", the invoker
can register identities of type peer, app, and user, but not orderer.

2. The affiliation of the invoker's identity must be equal to or a prefix of
the affiliation of the identity being registered. For example, an invoker
Expand All @@ -996,6 +997,51 @@ during registration as follows:
If no affiliation is specified in the registration request, the identity being
registered will be given the affiliation of the registrar.

3. The registrar can register a user with attributes if the following conditions
are satisfied:
- Registrar has 'hf.Registar.Attributes' attribute with the value of the
attribute or pattern being registered. The only supported pattern is a
string with a "*" at the end. For example, "a.b.*" is a pattern which
matches all attribute names beginning with "a.b.".
- If the requested attribute name is 'hf.Registrar.Attributes', an additional
check is performed to see if the requested values for this attribute
are equal to or a subset of the registrar's values for 'hf.Registrar.Attributes'.
For this to be true, each requested value must match a value in the registrar's
value for 'hf.Registrar.Attributes' attribute. For example, if the registrar's
value for 'hf.Registrar.Attributes' is 'a.b.*, x.y.z' and the requested attribute
value is 'a.b.c, x.y.z', it is valid because 'a.b.c' matches 'a.b.*' and 'x.y.z'
matches the registrar's 'x.y.z' value.

Examples:
Valid Scenarios:
1. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'a.b.c', it is valid 'a.b.c' matches 'a.b.*'.

2. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'x.y.z', it is valid because 'x.y.z' matches the registrar's
'x.y.z' value.

3. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
the requested attribute value is 'a.b.c, x.y.z', it is valid because 'a.b.c' matches
'a.b.*' and 'x.y.z' matches the registrar's 'x.y.z' value.

Invalid Scenarios:
1. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'hf.Registar.Attributes = a.b.c, x.y.*', it is invalid
because requested attribute 'x.y.*' is not a pattern owned by the registrar. The value
'x.y.*' is a superset of 'x.y.z'.

2. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'hf.Registar.Attributes = a.b.c, x.y.z, attr1', it is invalid
because the registrar's 'hf.Registrar.Attributes' attribute values do not contain 'attr1'.

3. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'a.b', it is invalid because the value 'a.b' is not contained in
'a.b.*'.

4. If the Registrar has the attribute 'hf.Registrar.Attributes = a.b.*, x.y.z' and
is registering attribute 'x.y', it is invalid because 'x.y' is not contained by 'x.y.z'.

The following command uses the **admin** identity's credentials to register a new
user with an enrollment id of "admin2", an affiliation of
"org1.department1", an attribute named "hf.Revoker" with a value of "true", and
Expand Down
12 changes: 10 additions & 2 deletions lib/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,16 @@ func (c *Client) SendReq(req *http.Request, result interface{}) (err error) {
return errors.Wrapf(err, "Failed to parse response: %s", respBody)
}
if len(body.Errors) > 0 {
msg := body.Errors[0].Message
return errors.Errorf("Response from server: %s", msg)
var errorMsg string
for _, err := range body.Errors {
msg := fmt.Sprintf("Response from server: Error Code: %d - %s\n", err.Code, err.Message)
if errorMsg == "" {
errorMsg = msg
} else {
errorMsg = errorMsg + fmt.Sprintf("\n%s", msg)
}
}
return errors.Errorf(errorMsg)
}
}
scode := resp.StatusCode
Expand Down
11 changes: 11 additions & 0 deletions lib/dbaccessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/spi"
"github.com/hyperledger/fabric-ca/lib/tcert"
"golang.org/x/crypto/bcrypt"

"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -459,6 +460,16 @@ func (u *DBUser) GetAttribute(name string) string {
return u.attrs[name]
}

// GetAttributes returns the requested attributes
func (u *DBUser) GetAttributes(attrNames []string) []tcert.Attribute {
var attrs []tcert.Attribute
for _, name := range attrNames {
value := u.attrs[name]
attrs = append(attrs, tcert.Attribute{Name: name, Value: value})
}
return attrs
}

func dbGetError(err error, prefix string) error {
if err.Error() == "sql: no rows in result set" {
return errors.Errorf("%s not found", prefix)
Expand Down
11 changes: 11 additions & 0 deletions lib/ldap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (

"github.com/cloudflare/cfssl/log"
"github.com/hyperledger/fabric-ca/lib/spi"
"github.com/hyperledger/fabric-ca/lib/tcert"
ctls "github.com/hyperledger/fabric-ca/lib/tls"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp"
Expand Down Expand Up @@ -338,6 +339,16 @@ func (u *User) GetAttribute(name string) string {
return u.attrs[name]
}

// GetAttributes returns the requested attributes
func (u *User) GetAttributes(attrNames []string) []tcert.Attribute {
var attrs []tcert.Attribute
for _, name := range attrNames {
value := u.attrs[name]
attrs = append(attrs, tcert.Attribute{Name: name, Value: value})
}
return attrs
}

// Returns a slice with the elements reversed
func reverse(in []string) []string {
size := len(in)
Expand Down
2 changes: 2 additions & 0 deletions lib/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const (
attrRevoker = "hf.Revoker"
attrIntermediateCA = "hf.IntermediateCA"
attrGenCRL = "hf.GenCRL"
attrRegistrarAttr = "hf.Registrar.Attributes"
)

// Server is the fabric-ca server
Expand Down Expand Up @@ -209,6 +210,7 @@ func (s *Server) RegisterBootstrapUser(user, pass, affiliation string) error {
attrRevoker: "true",
attrIntermediateCA: "true",
attrGenCRL: "true",
attrRegistrarAttr: "*",
},
}

Expand Down
4 changes: 4 additions & 0 deletions lib/servererror.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ const (
ErrGetCASigner = 40
// Failed to generate CRL
ErrGenCRL = 41
// Registrar does not have the authority to register an attribute
ErrRegAttrAuth = 42
// Registrar does not own 'hf.Registrar.Attributes'
ErrMissingRegAttr = 43
)

// Construct a new HTTP error.
Expand Down
111 changes: 86 additions & 25 deletions lib/serverregister.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (

"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/spi"
"github.com/hyperledger/fabric-ca/lib/tcert"
"github.com/hyperledger/fabric-ca/util"
)

Expand Down Expand Up @@ -57,12 +56,8 @@ func registerHandler(ctx *serverRequestContext) (interface{}, error) {
if err != nil {
return nil, err
}
registrarRolesAttr, registrarAffiliation, err := ctx.GetUserInfo([]string{"hf.Registrar.Roles"})
if err != nil {
return "", fmt.Errorf("Failed to get user info for registrar: %s", err)
}
// Register User
secret, err := registerUser(&req, registrarRolesAttr[0], registrarAffiliation, callerID, ca)
secret, err := registerUser(&req, callerID, ca, ctx)
if err != nil {
return nil, err
}
Expand All @@ -74,25 +69,23 @@ func registerHandler(ctx *serverRequestContext) (interface{}, error) {
}

// RegisterUser will register a user and return the secret
func registerUser(req *api.RegistrationRequestNet, registrarRoles tcert.Attribute, registrarAffiliation []string, registrar string, ca *CA) (string, error) {

secret := req.Secret
req.Secret = "<<user-specified>>"
req.Secret = secret

func registerUser(req *api.RegistrationRequestNet, registrar string, ca *CA, ctx *serverRequestContext) (string, error) {
var err error
var registrarUser spi.User

if registrar != "" {
// Check the permissions of member named 'registrar' to perform this registration
err = canRegister(registrarRoles, registrar, req)
if err != nil {
log.Debugf("Registration of '%s' failed: %s", req.Name, err)
return "", err
}
registrarUser, err = ctx.GetCaller()
if err != nil {
return "", err
}
// Check the permissions of member named 'registrar' to perform this registration
err = canRegister(registrar, req, registrarUser)
if err != nil {
log.Debugf("Registration of '%s' failed: %s", req.Name, err)
return "", err
}

// Check that the affiliation requested is of the appropriate level
registrarAff := strings.Join(registrarAffiliation, ".")
registrarAff := strings.Join(registrarUser.GetAffiliationPath(), ".")
err = validateAffiliation(registrarAff, req)
if err != nil {
return "", fmt.Errorf("Registration of '%s' failed in affiliation validation: %s", req.Name, err)
Expand All @@ -103,7 +96,12 @@ func registerUser(req *api.RegistrationRequestNet, registrarRoles tcert.Attribut
return "", errors.WithMessage(err, fmt.Sprintf("Registration of '%s' to validate", req.Name))
}

secret, err = registerUserID(req, ca)
err = validateRequestedAttributes(req.Attributes, registrarUser)
if err != nil {
return "", err
}

secret, err := registerUserID(req, ca)

if err != nil {
return "", errors.WithMessage(err, fmt.Sprintf("Registration of '%s' failed", req.Name))
Expand Down Expand Up @@ -214,12 +212,13 @@ func requireAffiliation(idType string) bool {
return true
}

func canRegister(registrarRoles tcert.Attribute, registrar string, req *api.RegistrationRequestNet) error {
log.Debugf("canRegister - Check to see if registrar '%s' with registrar roles of '%s' can register user type '%s'", registrar, registrarRoles.Value, req.Type)
func canRegister(registrar string, req *api.RegistrationRequestNet, user spi.User) error {
log.Debugf("canRegister - Check to see if user %s can register", registrar)

var roles []string
if registrarRoles.Value != "" {
roles = strings.Split(registrarRoles.Value, ",")
rolesStr := user.GetAttribute("hf.Registrar.Roles")
if rolesStr != "" {
roles = strings.Split(rolesStr, ",")
} else {
roles = make([]string, 0)
}
Expand All @@ -231,3 +230,65 @@ func canRegister(registrarRoles tcert.Attribute, registrar string, req *api.Regi
}
return nil
}

// Validate that the registrar can register the requested attributes
func validateRequestedAttributes(reqAttrs []api.Attribute, registrar spi.User) error {
registrarAttrs := registrar.GetAttribute(attrRegistrarAttr)
log.Debugf("Validating that registrar '%s' with the following value for hf.Registrar.Attributes '%s' is authorized to register the requested attributes '%+v'", registrar.GetName(), registrarAttrs, reqAttrs)
if len(reqAttrs) == 0 {
return nil
}

if registrarAttrs == "" {
return newHTTPErr(401, ErrMissingRegAttr, "Registrar does not have any values for '%s' thus can't register any attributes", attrRegistrarAttr)
}

hfRegistrarAttrsSlice := strings.Split(strings.Replace(registrarAttrs, " ", "", -1), ",") // Remove any whitespace between the values and split on comma

// Function will iterate through the values of registrar's 'hf.Registrar.Attributes' attribute to check if registrar can register the requested attributes
registrarCanRegisterAttr := func(requestedAttr string) error {
for _, regAttr := range hfRegistrarAttrsSlice {
if strings.HasSuffix(regAttr, "*") { // Wildcard matching
if strings.HasPrefix(requestedAttr, strings.TrimRight(regAttr, "*")) {
return nil // Requested attribute found, break out of loop
}
} else {
if requestedAttr == regAttr { // Exact name matching
return nil // Requested attribute found, break out of loop
}
}
}
return errors.Errorf("Attribute is not part of '%s' attribute", attrRegistrarAttr)
}

for _, reqAttr := range reqAttrs {
reqAttrName := reqAttr.Name // Name of the requested attribute

// Requesting 'hf.Registrar.Attributes' attribute
if reqAttrName == attrRegistrarAttr {
// Check if registrar is allowed to register 'hf.Registrar.Attribute' by examining it's value for 'hf.Registrar.Attribute'
err := registrarCanRegisterAttr(attrRegistrarAttr)
if err != nil {
return newHTTPErr(401, ErrRegAttrAuth, "Registrar is not allowed to register attribute '%s': %s", reqAttrName, err)
}

reqRegistrarAttrsSlice := strings.Split(strings.Replace(reqAttr.Value, " ", "", -1), ",") // Remove any whitespace between the values and split on comma
// Loop through the requested values for 'hf.Registrar.Attributes' to see if they can be registered
for _, reqRegistrarAttr := range reqRegistrarAttrsSlice {
err := registrarCanRegisterAttr(reqRegistrarAttr)
if err != nil {
return newHTTPErr(401, ErrRegAttrAuth, "Registrar is not allowed to register attribute '%s': %s", reqAttrName, err)
}
}
continue // Continue to next requested attribute
}

// Iterate through the registrar's value for 'hf.Registrar.Attributes' to check if it can register the requested attribute
err := registrarCanRegisterAttr(reqAttrName)
if err != nil {
return newHTTPErr(401, ErrRegAttrAuth, "Registrar is not allowed to register attribute '%s': %s", reqAttrName, err)
}
}

return nil
}
Loading

0 comments on commit aa10999

Please sign in to comment.