diff --git a/docs/source/users-guide.rst b/docs/source/users-guide.rst index 9a707878a..b14378754 100644 --- a/docs/source/users-guide.rst +++ b/docs/source/users-guide.rst @@ -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`_ @@ -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? @@ -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=user1@gmail.com' + fabric-ca-client register --id.name user1 --id.secret user1pw --id.type user --id.affiliation org1 --id.attrs 'app1Admin=true:ecert,email=user1@gmail.com' +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 diff --git a/lib/client_test.go b/lib/client_test.go index 4de85896e..12a3f03cc 100644 --- a/lib/client_test.go +++ b/lib/client_test.go @@ -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) @@ -198,16 +199,14 @@ 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") } @@ -215,8 +214,7 @@ func testGetCAInfo(c *Client, t *testing.T) { 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") } @@ -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") } @@ -300,10 +297,11 @@ 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"}, }, } @@ -311,11 +309,51 @@ func testRegister(c *Client, t *testing.T) { 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"}, }, } @@ -323,15 +361,18 @@ func testRegister(c *Client, t *testing.T) { 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, diff --git a/lib/serverregister.go b/lib/serverregister.go index be82bdec8..9c029438d 100644 --- a/lib/serverregister.go +++ b/lib/serverregister.go @@ -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, @@ -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}) +}