Skip to content

Commit

Permalink
[FAB-5346] Attribute-based access control (#4)
Browse files Browse the repository at this point in the history
Enhance the "fabric-ca-client enroll" command to specify which attribute
requests should be sent to the server.  This adds support for the
"--enrollment.attrs" option to the client enroll command.  For example:
   fabric-ca-client enroll --enrollment.attrs "a,b:opt"
overrides any default attributes for the identity.  The ":opt" means that
attribute "b" is optional and will not return an error if the user
doesn't have it.

Change-Id: I9774ef707d4e9df4796c08cf079c7afc8a8e00b7
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed Sep 19, 2017
1 parent d7b554c commit f3028c4
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 18 deletions.
5 changes: 5 additions & 0 deletions cmd/fabric-ca-client/clientcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type ClientCmd struct {
// cfgAttrs are the attributes specified via flags or env variables
// and translated to Attributes field in registration
cfgAttrs []string
// cfgAttrReqs are the attribute requests specified via flags or env variables
// and translated to the AttrReqs field in enrollment
cfgAttrReqs []string
// cfgCsrNames are the certificate signing request names specified via flags
// or env variables
cfgCsrNames []string
Expand Down Expand Up @@ -154,6 +157,8 @@ func (c *ClientCmd) registerFlags() {
pflags.StringVarP(&c.homeDirectory, "home", "H", "", fmt.Sprintf("Client's home directory (default \"%s\")", filepath.Dir(cfg)))
pflags.StringSliceVarP(
&c.cfgAttrs, "id.attrs", "", nil, "A list of comma-separated attributes of the form <name>=<value> (e.g. foo=foo1,bar=bar1)")
pflags.StringSliceVarP(
&c.cfgAttrReqs, "enrollment.attrs", "", nil, "A list of comma-separated attribute requests of the form <name>[:opt] (e.g. foo,bar:opt)")
util.FlagString(c.myViper, pflags, "myhost", "m", host,
"Hostname to include in the certificate signing request during enrollment")
pflags.StringSliceVarP(
Expand Down
34 changes: 34 additions & 0 deletions cmd/fabric-ca-client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ func (c *ClientCmd) configInit() error {
return err
}

err = processAttributeRequests(c.cfgAttrReqs, c.clientCfg)
if err != nil {
return err
}

err = c.processCsrNames()
if err != nil {
return err
Expand Down Expand Up @@ -362,6 +367,35 @@ func processAttributes(cfgAttrs []string, cfg *lib.ClientConfig) error {
return nil
}

// processAttributeRequests parses attribute requests from command line or env variable
// Each string is of the form: <attrName>[:opt] where "opt" means the attribute is
// optional and will not return an error if the identity does not possess the attribute.
// The default is that each attribute name listed is required and so the identity must
// possess the attribute.
func processAttributeRequests(cfgAttrReqs []string, cfg *lib.ClientConfig) error {
if len(cfgAttrReqs) == 0 {
return nil
}
reqs := make([]*api.AttributeRequest, len(cfgAttrReqs))
for idx, req := range cfgAttrReqs {
sreq := strings.Split(req, ":")
name := sreq[0]
switch len(sreq) {
case 1:
reqs[idx] = &api.AttributeRequest{Name: name, Require: true}
case 2:
if sreq[1] != "opt" {
return errors.Errorf("Invalid option in attribute request specification at '%s'; the value after the colon must be 'opt'", req)
}
reqs[idx] = &api.AttributeRequest{Name: name, Require: false}
default:
return errors.Errorf("Multiple ':' characters not allowed in attribute request specification; error at '%s'", req)
}
}
cfg.Enrollment.AttrReqs = reqs
return nil
}

// processAttributes parses attributes from command line or env variable
func (c *ClientCmd) processCsrNames() error {
if c.cfgCsrNames != nil {
Expand Down
76 changes: 58 additions & 18 deletions cmd/fabric-ca-client/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,16 @@ func TestRBAC(t *testing.T) {
server := startServer(testDir, 7054, "", t)
defer server.Stop()

// Negative test case to try to enroll with an badly formatted attribute request
err = RunMain([]string{
cmdName, "enroll",
"--enrollment.attrs", "foo,bar:zoo",
"-c", adminUserConfig,
"-u", "http://admin:adminpw@localhost:7054"})
if err == nil {
t.Error("enrollment with badly formatted attribute requests should fail")
}

// Enroll the admin
err = RunMain([]string{
cmdName, "enroll",
Expand Down Expand Up @@ -282,17 +292,52 @@ func TestRBAC(t *testing.T) {
t.Errorf("client register failed: %s", err)
}

// Enroll the test user
// Enroll the test user with no attribute requests and make sure the
// resulting ecert has the default attributes and no extra
err = RunMain([]string{
cmdName, "enroll", "-d",
"-c", testUserConfig,
"-u", fmt.Sprintf("http://%s:%s@localhost:7054", testUser, testPass)})
if err != nil {
t.Fatalf("client enroll of test user failed: %s", err)
}
checkAttrsInCert(t, testUserHome, "admin", "true", "foo")

// Enroll the test user with attribute requests and make sure the
// resulting ecert has the requested attributes only
err = RunMain([]string{
cmdName, "enroll", "-d",
"--enrollment.attrs", "foo,unknown:opt",
"-c", testUserConfig,
"-u", fmt.Sprintf("http://%s:%s@localhost:7054", testUser, testPass)})
if err != nil {
t.Fatalf("client enroll of test user failed: %s", err)
}
checkAttrsInCert(t, testUserHome, "foo", "bar", "admin")

// Negative test case to request an attribute that the identity doesn't have
err = RunMain([]string{
cmdName, "enroll", "-d",
"--enrollment.attrs", "unknown",
"-c", testUserConfig,
"-u", fmt.Sprintf("http://%s:%s@localhost:7054", testUser, testPass)})
if err == nil {
t.Error("enrollment request with unknown required attribute should fail")
}

// Stop the server
err = server.Stop()
if err != nil {
t.Errorf("Server stop failed: %s", err)
}
}

// Verify the certificate has attribute 'name' with a value of 'val'
// and does not have the 'missing' attribute.
func checkAttrsInCert(t *testing.T, home, name, val, missing string) {

// Load the user's ecert
cert, err := util.GetX509CertificateFromPEMFile(path.Join(testUserHome, "msp", "signcerts", "cert.pem"))
cert, err := util.GetX509CertificateFromPEMFile(path.Join(home, "msp", "signcerts", "cert.pem"))
if err != nil {
t.Fatalf("Failed to load test user's cert: %s", err)
}
Expand All @@ -303,34 +348,29 @@ func TestRBAC(t *testing.T) {
t.Fatalf("Failed to get attributes from certificate: %s", err)
}

// Make sure the admin attribute is in the cert
val, ok, err := attrs.Value("admin")
// Make sure the attribute is in the cert
v, ok, err := attrs.Value(name)
if err != nil {
t.Fatalf("Failed to get admin attribute from cert: %s", err)
t.Fatalf("Failed to get '%s' attribute from cert: %s", name, err)
}
if !ok {
t.Fatal("The admin attribute was not found in the cert")
t.Fatalf("The '%s' attribute was not found in the cert", name)
}

// Make sure the value of the admin attribute is true
if val != "true" {
t.Fatalf("The value of the admin attribute is '%s' rather than true", val)
// Make sure the value of the attribute is as expected
if v != val {
t.Fatalf("The value of the '%s' attribute is '%s' rather than '%s'", name, v, val)
}

// Make sure the foo attribute was NOT added by default
_, ok, err = attrs.Value("foo")
// Make sure the missing attribute was NOT found
_, ok, err = attrs.Value(missing)
if err != nil {
t.Fatalf("Failed to get foo attribute from cert: %s", err)
t.Fatalf("Failed to get '%s' attribute from cert: %s", missing, err)
}
if ok {
t.Fatal("The foo attribute was found in the cert but should not be")
t.Fatalf("The '%s' attribute was found in the cert but should not be", missing)
}

// Stop the server
err = server.Stop()
if err != nil {
t.Errorf("Server stop failed: %s", err)
}
}

func testConfigFileTypes(t *testing.T) {
Expand Down

0 comments on commit f3028c4

Please sign in to comment.