Skip to content

Commit

Permalink
Add LDAP support to COP server
Browse files Browse the repository at this point in the history
This LDAP support includes two primary features:
1) Uses LDAP bind to authenticate users prior to enrollment;
2) Uses LDAP query to retrieve attributes for authorization
   decisions and to place attributes in TCerts.

See the LDAP section in README.md for a description of how to
configure LDAP support and to a general flow description.

You can run scripts/run-ldap-tests to test against OpenLDAP.
Docker is a prereq to run this script.

See https://jira.hyperledger.org/browse/FAB-1144

Change-Id: I25af48a446958c3934b98cfdd41298c2731815d4
Signed-off-by: Keith Smith <[email protected]>
  • Loading branch information
Keith Smith committed Dec 13, 2016
1 parent 690c33c commit 32cba00
Show file tree
Hide file tree
Showing 57 changed files with 5,233 additions and 366 deletions.
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# - unit-tests - Performs checks first and runs the go-test based unit tests
# - checks - runs all check conditions (license, format, imports, lint and vet)

all: checks cop unit-tests
all: unit-tests

checks: license vet lint format imports

Expand All @@ -51,4 +51,9 @@ cop:
unit-tests: checks cop
@scripts/run_tests

container-tests: ldap-tests

ldap-tests:
@scripts/run_ldap_tests

.FORCE:
67 changes: 62 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,22 @@ The following command gets an ecert for the admin user.
# ./cop client enroll admin adminpw http://localhost:8888
```

The enrollment material is stored in the `$COP_HOME/client.json` file.
Note that this stores the enrollment material in the `$COP_HOME/client.json` file.

### Reenroll the admin client
### Reenroll

The following command renews the enrollment certificate of a client.
Suppose your enrollment certificate is about to expire. You can issue the reenroll command
to renew your enrollment certificate as follows. Note that this is identical to the enroll
command except no username or password is required. Instead, your previously stored private
key is used to authenticate to the COP server.

```
# cd $COP/bin
# ./cop client reenroll http://localhost:8888
# ./cop client reenroll ../testdata/csr.json http://localhost:8888
```

Note that this updates the enrollment material in the `$COP_HOME/client.json` file.

### Run the cop tests

### Register a new user

Expand All @@ -150,6 +152,7 @@ For example, the attributes for a registrar might look like this:
The registrar should then create a JSON file as defined below for the user being registered.

registerrequest.json:

```
{
"id": "User1",
Expand All @@ -160,11 +163,65 @@ registerrequest.json:
```

The following command will register the user.

```
# cd $COP/bin
# ./cop client register ../testdata/registerrequest.json http://localhost:8888
```

### LDAP

The COP server can be configured to read from an LDAP server.

In particular, the COP server may connect to an LDAP server to do the following:

* authenticate a user prior to enrollment, and
* retrieve a user's attribute values which is used for authorization.

In order to configure the COP server to connect to an LDAP server, add a section of the following form to your COP server's configuration file:

```
{
"ldap": {
"url": "scheme://adminDN:pass@host[:port][/base]"
"userfilter": "filter"
}
```

where:
* `scheme` is one of *ldap* or *ldaps*;
* `adminDN` is the distinquished name of the admin user;
* `pass` is the password of the admin user;
* `host` is the hostname or IP address of the LDAP server;
* `port` is the optional port number, where default 389 for *ldap* and 636 for *ldaps*;
* `base` is the optional root of the LDAP tree to use for searches;
* `filter` is a filter to use when searching to convert a login user name to a distinquished name. For example, a value of `(uid=%s)` searches for LDAP entries with the value of a `uid` attribute whose value is the login user name. Similarly, `(email=%s)` may be used to login with an email address.

The following is a sample configuration section for the default settings for the OpenLDAP server whose docker image is at `https://github.com/osixia/docker-openldap`.

```
"ldap": {
"url": "ldap://cn=admin,dc=example,dc=org:admin@localhost:10389/dc=example,dc=org",
"userfilter": "(uid=%s)"
},
```

See `COP/testdata/testconfig-ldap.json` for the complete configuration file with this section. Also see `COP/scripts/run-ldap-tests` for a script which starts an OpenLDAP docker image, configures it, runs the LDAP tests in COP/cli/server/ldap/ldap_test.go, and stops the OpenLDAP server.

##### When LDAP is configured, enrollment works as follows:

* A COP client or client SDK sends an enrollment request with a basic authorization header.
* The COP server receives the enrollment request, decodes the user/pass in the authorization header, looks up the DN (Distinquished Name) associated with the user using the "userfilter" from the configuration file, and then attempts an LDAP bind with the user's password. If successful, the enrollment processing is authorized and can proceed.

##### When LDAP is configured, attribute retrieval works as follows:

* A client SDK sends a request for a batch of tcerts *with one or more attributes*to the COP server.
* The COP server receives the tcert request and does as follows:
* extracts the enrollment ID from the token in the authorization header (after validating the token);
* does an LDAP search/query to the LDAP server, requesting all of the attribute names received in the tcert request;
* the attribute values are placed in the tcert as normal


### Setting up a cluster

Set up a proxy server. Haproxy is used in this example. Below is a basic configuration file that can be used to get haproxy up and running. Change hostname and port to reflect the settings of your COP servers.
Expand Down
7 changes: 5 additions & 2 deletions cli/server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ func (ah *copAuthHandler) serveHTTP(w http.ResponseWriter, r *http.Request) erro
log.Debugf("Basic auth is not allowed; found %s", authHdr)
return errBasicAuthNotAllowed
}
_, err := cfg.UserRegistry.LoginUserBasicAuth(user, pwd)
u, err := userRegistry.GetUser(user)
if err != nil {
return err
}
err = u.Login(pwd)
if err != nil {
log.Errorf("Failed authorizing user, [error: %s]", err)
return err
}

Expand Down
3 changes: 2 additions & 1 deletion cli/server/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package server
import (
"errors"
"path/filepath"
"strconv"
"strings"

"github.com/cloudflare/cfssl/log"
Expand All @@ -41,7 +42,7 @@ func (b *Bootstrap) PopulateUsersTable() error {
for name, info := range CFG.Users {

reg := NewRegisterUser()
reg.RegisterUser(name, info.Type, info.Group, info.Attributes, "", info.Pass)
reg.RegisterUser(name, info.Type, info.Group, info.Attributes, "", info.Pass, strconv.Itoa(CFG.UsrReg.MaxEnrollments))
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion cli/server/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func prepBootstrap() (*Bootstrap, error) {
bootCFG.Home = bootPath
bootCFG.DataSource = bootCFG.Home + "/cop.db"

CFG.UserRegistry, err = NewUserRegistry(bootCFG.DBdriver, bootCFG.DataSource)
err = InitUserRegistry(bootCFG)
if err != nil {
return nil, err
}
Expand Down
2 changes: 2 additions & 0 deletions cli/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/cloudflare/cfssl/cli"
"github.com/cloudflare/cfssl/log"
"github.com/cloudflare/cfssl/signer"
"github.com/hyperledger/fabric-cop/cli/server/ldap"
"github.com/hyperledger/fabric-cop/cli/server/spi"
"github.com/hyperledger/fabric-cop/idp"
"github.com/jmoiron/sqlx"
Expand All @@ -40,6 +41,7 @@ type Config struct {
DBdriver string `json:"driver"`
DataSource string `json:"data_source"`
UsrReg UserReg `json:"user_registry"`
LDAP *ldap.Config `json:"ldap,omitempty"`
Home string
ConfigFile string
CACert string
Expand Down
17 changes: 6 additions & 11 deletions cli/server/dasqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,12 @@ func testInsertAndGetUser(ta TestAccessor, t *testing.T) {
t.Errorf("Error occured during insert query of ID: %s, error: %s", insert.Name, err)
}

result, err := ta.Accessor.GetUser(insert.Name)
user, err := ta.Accessor.GetUser(insert.Name)
if err != nil {
t.Errorf("Error occured during querying of id: %s, error: %s", insert.Name, err)
}
userInfo := result.(*spi.UserInfo)

if userInfo.Name == "" {
t.Error("No results returned")
}
if userInfo.Name != insert.Name {
if user.GetName() != insert.Name {
t.Error("Incorrect ID retrieved")
}
}
Expand Down Expand Up @@ -182,15 +178,14 @@ func testUpdateUser(ta TestAccessor, t *testing.T) {
t.Errorf("Error occured during update query of ID: %s, error: %s", insert.Name, err)
}

result, err := ta.Accessor.GetUser(insert.Name)
user, err := ta.Accessor.GetUser(insert.Name)
if err != nil {
t.Errorf("Error occured during querying of ID: %s, error: %s", insert.Name, err)
}

userInfo := result.(*spi.UserInfo)

if userInfo.Pass != insert.Pass {
t.Error("Failed to update user")
err = user.Login(insert.Pass)
if err != nil {
t.Error("Failed to update user's password")
}

}
Expand Down
Loading

0 comments on commit 32cba00

Please sign in to comment.