Skip to content

Commit

Permalink
[FAB-6475] Add well-known attributes to identities
Browse files Browse the repository at this point in the history
Automatically add the following 3 well-known attributes to a user at
registration time:
1) hf.EnrollmentID - the enrollment ID
2) hf.Type - the type of the identity ("user", "peer", etc)
3) hf.Affiliation - the affiliation of the identity

Change-Id: I6c4634c58f946146124640b2390f528023a5d2f1
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed Oct 30, 2017
1 parent 5b815c8 commit 33f3629
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 31 deletions.
57 changes: 40 additions & 17 deletions docs/source/users-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ Table of Contents
4. `Reenrolling an identity`_
5. `Revoking a certificate or identity`_
6. `Generating a CRL (Certificate Revocation List)`_
7. `Enabling TLS`_
8. `Contact specific CA instance`_
7. `Attribute-Based Access Control`_
8. `Enabling TLS`_
9. `Contact specific CA instance`_

6. `HSM`_

Expand Down Expand Up @@ -1324,14 +1325,14 @@ based upon an identity's attributes. This is called
**Attribute-Based Access Control**, or **ABAC** for short.

In order to make this possible, an identity's enrollment certificate (ECert)
may contain one or more attribute names and values. The chaincode then
may contain one or more attribute name and value. The chaincode then
extracts an attribute's value to make an access control decision.

For example, suppose that you are developing application *app1* and want a
particular chaincode operation to be accessible only by app1 administrators.
Your chaincode could verify that the caller's certificate, which was issued by
a CA trusted for the channel, contains an attribute named *app1Admin* with a
value of *true*. Note that the name of the attribute could be anything and the
Your chaincode could verify that the caller's certificate (which was issued by
a CA trusted for the channel) contains an attribute named *app1Admin* with a
value of *true*. Of course the name of the attribute can be anything and the
value need not be a boolean value.

So how do you get an enrollment certificate with an attribute?
Expand All @@ -1346,27 +1347,49 @@ There are two methods:
The following shows how to register *user1* with two attributes:
*app1Admin* and *email*.
The ":ecert" suffix causes the *appAdmin* attribute to be inserted into user1's
enrollment certificate by default. The *email* attribute is not added
enrollment certificate by default, when the user does not explicitly request
attributes at enrollment time. The *email* attribute is not added
to the enrollment certificate by default.

.. code:: bash
fabric-ca-client register --id.name user1 --id.secret user1pw --id.type user --id.affiliation org1 --id.attrs 'app1Admin=true:ecert,[email protected]'
fabric-ca-client register --id.name user1 --id.secret user1pw --id.type user --id.affiliation org1 --id.attrs 'app1Admin=true:ecert,[email protected]'
2. When you enroll an identity, you may explicitly request that one or more attributes
be added to the certificate.
For each attribute requested, you may specify whether the attribute is
optional or not. If it is not requested optionally and the identity does
not possess the attribute, an error will occur.

2. When you enroll an identity, you may request that one or more attributes
be added to the certificate.
For each attribute requested, you may specify whether the attribute is
optional or not. If it is not optional but does not exist for the identity,
enrollment fails.
The following shows how to enroll *user1* with the *email* attribute,
without the *app1Admin* attribute, and optionally with the *phone*
attribute (if the user possesses the *phone* attribute).

The following shows how to enroll *user1* with the *email* attribute,
without the *app1Admin* attribute, and optionally with the *phone*
attribute (if the user possesses the *phone* attribute).
.. code:: bash
fabric-ca-client enroll -u http://user1:user1pw@localhost:7054 --enrollment.attrs "email,phone:opt"
The table below shows the three attributes which are automatically registered for every identity.

=================================== =====================================
Attribute Name Attribute Value
=================================== =====================================
hf.EnrollmentID The enrollment ID of the identity
hf.Type The type of the identity
hf.Affiliation The affiliation of the identity
=================================== =====================================

To add any of the above attributes **by default** to a certificate, you must
explicitly register the attribute with the ":ecert" specification.
For example, the following registers identity 'user1' so that
the 'hf.Affiliation' attribute will be added to an enrollment certificate if
no specific attributes are requested at enrollment time. Note that the
value of the affiliation (which is 'org1') must be the same in both the
'--id.affiliation' and the '--id.attrs' flags.

.. code:: bash
fabric-ca-client enroll -u http://user1:user1pw@localhost:7054 --enrollment.attrs "email,phone:opt"
fabric-ca-client register --id.name user1 --id.secret user1pw --id.type user --id.affiliation org1 --id.attrs 'hf.Affiliation=org1:ecert'
For information on the chaincode library API for Attribute-Based Access Control,
see https://github.com/hyperledger/fabric/tree/release/core/chaincode/lib/cid/README.md
Expand Down
69 changes: 55 additions & 14 deletions lib/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ func TestCLIClient(t *testing.T) {
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
defer server.Stop()

c := getTestClient(ctport1)

Expand Down Expand Up @@ -198,25 +199,22 @@ func testGetCAInfo(c *Client, t *testing.T) {
client2 := new(Client)
client2.Config = new(ClientConfig)
client2.Config.MSPDir = string(make([]byte, 1))
si, err = client2.GetCAInfo(req)
t.Logf("GetCAInfo error %v", err)
_, err = client2.GetCAInfo(req)
if err == nil {
t.Errorf("Should have failed to get server info")
}

client2.Config.MSPDir = ""
client2.Config.URL = "http://localhost:["
si, err = client2.GetCAInfo(req)
t.Logf("GetCAInfo error %v", err)
_, err = client2.GetCAInfo(req)
if err == nil {
t.Errorf("Should have failed due to invalid URL")
}

client2.Config.MSPDir = ""
client2.Config.URL = ""
client2.Config.TLS.Enabled = true
si, err = client2.GetCAInfo(req)
t.Logf("GetCAInfo error %v", err)
_, err = client2.GetCAInfo(req)
if err == nil {
t.Errorf("Should have failed due to invalid TLS config")
}
Expand All @@ -231,7 +229,6 @@ func testRegister(c *Client, t *testing.T) {
}

err := c.CheckEnrollment()
t.Logf("CheckEnrollment error %v", err)
if err == nil {
t.Fatalf("testRegister check enrollment should have failed - client not enrolled")
}
Expand Down Expand Up @@ -300,38 +297,82 @@ func testRegister(c *Client, t *testing.T) {
userName := "MyTestUserWithAttrs"
registerReq = &api.RegistrationRequest{
Name: userName,
Type: "Client",
Type: "client",
Affiliation: "hyperledger",
Attributes: []api.Attribute{
api.Attribute{Name: "attr1", Value: "val1"},
api.Attribute{Name: "hf.EnrollmentID", Value: userName, ECert: true},
api.Attribute{Name: "attr1", Value: "val1", ECert: true},
api.Attribute{Name: "attr2", Value: "val2"},
},
}
resp, err = adminID.Register(registerReq)
if err != nil {
t.Fatalf("Register of %s failed: %s", userName, err)
}
// Request an ECert with attr1 but without attr2.

// Get an ECert with no explict attribute requested and make sure we get the defaults.
req = &api.EnrollmentRequest{
Name: userName,
Secret: resp.Secret,
}
eresp, err = c.Enroll(req)
if err != nil {
t.Fatalf("Enroll with attributes failed: %s", err)
}
// Verify that the ECert has the correct attributes.
attrs, err := eresp.Identity.GetECert().Attributes()
if err != nil {
t.Fatalf("%s", err)
}
checkAttrResult(t, "hf.EnrollmentID", userName, attrs)
checkAttrResult(t, "hf.Type", "", attrs)
checkAttrResult(t, "hf.Affiliation", "", attrs)
checkAttrResult(t, "attr1", "val1", attrs)
checkAttrResult(t, "attr2", "", attrs)

// Another test of registration and enrollment of an identity with attributes
userName = "MyTestUserWithAttrs2"
registerReq = &api.RegistrationRequest{
Name: userName,
Type: "client",
Affiliation: "hyperledger",
Attributes: []api.Attribute{
api.Attribute{Name: "hf.EnrollmentID", Value: userName, ECert: true},
api.Attribute{Name: "attr1", Value: "val1", ECert: true},
api.Attribute{Name: "attr2", Value: "val2"},
},
}
resp, err = adminID.Register(registerReq)
if err != nil {
t.Fatalf("Register of %s failed: %s", userName, err)
}

// Request an ECert with hf.EnrollmentID, hf.Type, hf.Affiliation, attr1 but without attr2.
req = &api.EnrollmentRequest{
Name: userName,
Secret: resp.Secret,
AttrReqs: []*api.AttributeRequest{
&api.AttributeRequest{Name: "hf.Type"},
&api.AttributeRequest{Name: "hf.Affiliation"},
&api.AttributeRequest{Name: "attr1"},
},
}
eresp, err = c.Enroll(req)
if err != nil {
t.Fatalf("Enroll with attributes failed: %s", err)
}
// Verify that the ECert's attributes have correct values for "attr1"
// and "attr2" and that "attr3" is not found.
attrs, err := eresp.Identity.GetECert().Attributes()
// Verify that the ECert has the correct attributes
attrs, err = eresp.Identity.GetECert().Attributes()
if err != nil {
t.Fatalf("%s", err)
}
checkAttrResult(t, "hf.EnrollmentID", "", attrs)
checkAttrResult(t, "hf.Type", "client", attrs)
checkAttrResult(t, "hf.Affiliation", "hyperledger", attrs)
checkAttrResult(t, "attr1", "val1", attrs)
checkAttrResult(t, "attr2", "", attrs)
// Request an ECert with an attribute that the identity does not have (attr4)

// Request an ECert with an attribute that the identity does not have (attr3)
// but we say that it is required. This should result in an error.
req = &api.EnrollmentRequest{
Name: userName,
Expand Down
16 changes: 16 additions & 0 deletions lib/serverregister.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ func registerUserID(req *api.RegistrationRequestNet, ca *CA) (string, error) {
return "", errors.WithMessage(err, "The delegateRoles field is a superset of roles")
}

// Add attributes containing the enrollment ID, type, and affiliation if not
// already defined
addAttributeToRequest("hf.EnrollmentID", req.Name, req)
addAttributeToRequest("hf.Type", req.Type, req)
addAttributeToRequest("hf.Affiliation", req.Affiliation, req)

insert := spi.UserInfo{
Name: req.Name,
Pass: req.Secret,
Expand Down Expand Up @@ -292,3 +298,13 @@ func validateRequestedAttributes(reqAttrs []api.Attribute, registrar spi.User) e

return nil
}

// Add an attribute to the registration request if not already found.
func addAttributeToRequest(name, value string, req *api.RegistrationRequestNet) {
for _, attr := range req.Attributes {
if attr.Name == name {
return
}
}
req.Attributes = append(req.Attributes, api.Attribute{Name: name, Value: value})
}

0 comments on commit 33f3629

Please sign in to comment.