github.com/silveraid/fabric-ca@v1.1.0-preview.0.20180127000700-71974f53ab08/lib/ldap/client.go (about) 1 /* 2 Copyright IBM Corp. 2016 All Rights Reserved. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package ldap 18 19 import ( 20 "fmt" 21 "net" 22 "net/url" 23 "regexp" 24 "strconv" 25 "strings" 26 27 "github.com/jmoiron/sqlx" 28 "github.com/pkg/errors" 29 30 "github.com/Knetic/govaluate" 31 "github.com/cloudflare/cfssl/log" 32 "github.com/hyperledger/fabric-ca/api" 33 "github.com/hyperledger/fabric-ca/lib/spi" 34 ctls "github.com/hyperledger/fabric-ca/lib/tls" 35 "github.com/hyperledger/fabric-ca/util" 36 "github.com/hyperledger/fabric/bccsp" 37 ldap "gopkg.in/ldap.v2" 38 ) 39 40 var ( 41 errNotSupported = errors.New("Not supported") 42 ldapURLRegex = regexp.MustCompile("ldaps*://(\\S+):(\\S+)@") 43 ) 44 45 // Config is the configuration object for this LDAP client 46 type Config struct { 47 Enabled bool `def:"false" help:"Enable the LDAP client for authentication and attributes"` 48 URL string `help:"LDAP client URL of form ldap://adminDN:adminPassword@host[:port]/base" mask:"url"` 49 UserFilter string `def:"(uid=%s)" help:"The LDAP user filter to use when searching for users"` 50 GroupFilter string `def:"(memberUid=%s)" help:"The LDAP group filter for a single affiliation group"` 51 Attribute AttrConfig 52 TLS ctls.ClientTLSConfig 53 } 54 55 // AttrConfig is attribute configuration information 56 type AttrConfig struct { 57 Names []string `help:"The names of LDAP attributes to request on an LDAP search"` 58 Converters []NameVal // Used to convert an LDAP entry into a fabric-ca-server attribute 59 Maps map[string][]NameVal // Use to map an LDAP response to fabric-ca-server names 60 } 61 62 // NameVal is a name and value pair 63 type NameVal struct { 64 Name string 65 Value string 66 } 67 68 // Implements Stringer interface for ldap.Config 69 // Calls util.StructToString to convert the Config struct to 70 // string. 71 func (c Config) String() string { 72 return util.StructToString(&c) 73 } 74 75 // NewClient creates an LDAP client 76 func NewClient(cfg *Config, csp bccsp.BCCSP) (*Client, error) { 77 log.Debugf("Creating new LDAP client for %+v", cfg) 78 if cfg == nil { 79 return nil, errors.New("LDAP configuration is nil") 80 } 81 if cfg.URL == "" { 82 return nil, errors.New("LDAP configuration requires a 'URL'") 83 } 84 u, err := url.Parse(cfg.URL) 85 if err != nil { 86 return nil, err 87 } 88 var defaultPort string 89 switch u.Scheme { 90 case "ldap": 91 defaultPort = "389" 92 case "ldaps": 93 defaultPort = "636" 94 default: 95 return nil, errors.Errorf("Invalid LDAP scheme: %s", u.Scheme) 96 } 97 var host, port string 98 if strings.Index(u.Host, ":") < 0 { 99 host = u.Host 100 port = defaultPort 101 } else { 102 host, port, err = net.SplitHostPort(u.Host) 103 if err != nil { 104 return nil, errors.Wrapf(err, "Invalid LDAP host:port (%s)", u.Host) 105 } 106 } 107 portVal, err := strconv.Atoi(port) 108 if err != nil { 109 return nil, errors.Wrapf(err, "Invalid LDAP port (%s)", port) 110 } 111 c := new(Client) 112 c.Host = host 113 c.Port = portVal 114 c.UseSSL = u.Scheme == "ldaps" 115 if u.User != nil { 116 c.AdminDN = u.User.Username() 117 c.AdminPassword, _ = u.User.Password() 118 } 119 c.Base = u.Path 120 if c.Base != "" && strings.HasPrefix(c.Base, "/") { 121 c.Base = c.Base[1:] 122 } 123 c.UserFilter = cfgVal(cfg.UserFilter, "(uid=%s)") 124 c.GroupFilter = cfgVal(cfg.GroupFilter, "(memberUid=%s)") 125 c.attrNames = cfg.Attribute.Names 126 c.attrExprs = map[string]*userExpr{} 127 for _, ele := range cfg.Attribute.Converters { 128 ue, err := newUserExpr(c, ele.Name, ele.Value) 129 if err != nil { 130 return nil, err 131 } 132 c.attrExprs[ele.Name] = ue 133 log.Debugf("Added LDAP mapping expression for attribute '%s'", ele.Name) 134 } 135 c.attrMaps = map[string]map[string]string{} 136 for mapName, value := range cfg.Attribute.Maps { 137 c.attrMaps[mapName] = map[string]string{} 138 for _, ele := range value { 139 c.attrMaps[mapName][ele.Name] = ele.Value 140 log.Debugf("Added '%s' -> '%s' to LDAP map '%s'", ele.Name, ele.Value, mapName) 141 } 142 } 143 c.TLS = &cfg.TLS 144 c.CSP = csp 145 log.Debug("LDAP client was successfully created") 146 return c, nil 147 } 148 149 func cfgVal(val1, val2 string) string { 150 if val1 != "" { 151 return val1 152 } 153 return val2 154 } 155 156 // Client is an LDAP client 157 type Client struct { 158 Host string 159 Port int 160 UseSSL bool 161 AdminDN string 162 AdminPassword string 163 Base string 164 UserFilter string // e.g. "(uid=%s)" 165 GroupFilter string // e.g. "(memberUid=%s)" 166 attrNames []string // Names of attributes to request on an LDAP search 167 attrExprs map[string]*userExpr // Expressions to evaluate to get attribute value 168 attrMaps map[string]map[string]string 169 AdminConn *ldap.Conn 170 TLS *ctls.ClientTLSConfig 171 CSP bccsp.BCCSP 172 } 173 174 // GetUser returns a user object for username and attribute values 175 // for the requested attribute names 176 func (lc *Client) GetUser(username string, attrNames []string) (spi.User, error) { 177 178 var sresp *ldap.SearchResult 179 var err error 180 181 log.Debugf("Getting user '%s'", username) 182 183 // Search for the given username 184 sreq := ldap.NewSearchRequest( 185 lc.Base, ldap.ScopeWholeSubtree, 186 ldap.NeverDerefAliases, 0, 0, false, 187 fmt.Sprintf(lc.UserFilter, username), 188 lc.attrNames, 189 nil, 190 ) 191 192 // Try to search using the cached connection, if there is one 193 conn := lc.AdminConn 194 if conn != nil { 195 log.Debugf("Searching for user '%s' using cached connection", username) 196 sresp, err = conn.Search(sreq) 197 if err != nil { 198 log.Debugf("LDAP search failed but will close connection and try again; error was: %s", err) 199 conn.Close() 200 lc.AdminConn = nil 201 } 202 } 203 204 // If there was no cached connection or the search failed for any reason 205 // (including because the server may have closed the cached connection), 206 // try with a new connection. 207 if sresp == nil { 208 log.Debugf("Searching for user '%s' using new connection", username) 209 conn, err = lc.newConnection() 210 if err != nil { 211 return nil, err 212 } 213 sresp, err = conn.Search(sreq) 214 if err != nil { 215 conn.Close() 216 return nil, errors.Wrapf(err, "LDAP search failure; search request: %+v", sreq) 217 } 218 // Cache the connection 219 lc.AdminConn = conn 220 } 221 222 // Make sure there was exactly one match found 223 if len(sresp.Entries) < 1 { 224 return nil, errors.Errorf("User '%s' does not exist in LDAP directory", username) 225 } 226 if len(sresp.Entries) > 1 { 227 return nil, errors.Errorf("Multiple users with name '%s' exist in LDAP directory", username) 228 } 229 230 entry := sresp.Entries[0] 231 if entry == nil { 232 return nil, errors.Errorf("No entry was returned for user '%s'", username) 233 } 234 235 // Construct the user object 236 user := &user{ 237 name: username, 238 entry: entry, 239 client: lc, 240 } 241 242 log.Debugf("Successfully retrieved user '%s', DN: %s", username, entry.DN) 243 244 return user, nil 245 } 246 247 // InsertUser inserts a user 248 func (lc *Client) InsertUser(user *spi.UserInfo) error { 249 return errNotSupported 250 } 251 252 // UpdateUser updates a user 253 func (lc *Client) UpdateUser(user *spi.UserInfo, updatePass bool) error { 254 return errNotSupported 255 } 256 257 // DeleteUser deletes a user 258 func (lc *Client) DeleteUser(id string) (spi.User, error) { 259 return nil, errNotSupported 260 } 261 262 // GetAffiliation returns an affiliation group 263 func (lc *Client) GetAffiliation(name string) (spi.Affiliation, error) { 264 return nil, errNotSupported 265 } 266 267 // GetAllAffiliations gets affiliation and any sub affiliation from the database 268 func (lc *Client) GetAllAffiliations(name string) (*sqlx.Rows, error) { 269 return nil, errNotSupported 270 } 271 272 // GetRootAffiliation returns the root affiliation group 273 func (lc *Client) GetRootAffiliation() (spi.Affiliation, error) { 274 return nil, errNotSupported 275 } 276 277 // InsertAffiliation adds an affiliation group 278 func (lc *Client) InsertAffiliation(name string, prekey string, version int) error { 279 return errNotSupported 280 } 281 282 // DeleteAffiliation deletes an affiliation group 283 func (lc *Client) DeleteAffiliation(name string, force, identityRemoval, isRegistrar bool) (*spi.DbTxResult, error) { 284 return nil, errNotSupported 285 } 286 287 // ModifyAffiliation renames the affiliation and updates all identities to use the new affiliation 288 func (lc *Client) ModifyAffiliation(oldAffiliation, newAffiliation string, force, isRegistrar bool) (*spi.DbTxResult, error) { 289 return nil, errNotSupported 290 } 291 292 // GetProperties returns the properties from the database 293 func (lc *Client) GetProperties(name []string) (map[string]string, error) { 294 return nil, errNotSupported 295 } 296 297 // GetUserLessThanLevel returns all identities that are less than the level specified 298 func (lc *Client) GetUserLessThanLevel(version int) ([]spi.User, error) { 299 return nil, errNotSupported 300 } 301 302 // GetFilteredUsers returns all identities that fall under the affiliation and types 303 func (lc *Client) GetFilteredUsers(affiliation, types string) (*sqlx.Rows, error) { 304 return nil, errNotSupported 305 } 306 307 // GetAffiliationTree returns the requested affiliations and all affiliations below it 308 func (lc *Client) GetAffiliationTree(name string) (*spi.DbTxResult, error) { 309 return nil, errNotSupported 310 } 311 312 // Connect to the LDAP server and bind as user as admin user as specified in LDAP URL 313 func (lc *Client) newConnection() (conn *ldap.Conn, err error) { 314 address := fmt.Sprintf("%s:%d", lc.Host, lc.Port) 315 if !lc.UseSSL { 316 log.Debug("Connecting to LDAP server over TCP") 317 conn, err = ldap.Dial("tcp", address) 318 if err != nil { 319 return conn, errors.Wrapf(err, "Failed to connect to LDAP server over TCP at %s", address) 320 } 321 } else { 322 log.Debug("Connecting to LDAP server over TLS") 323 tlsConfig, err2 := ctls.GetClientTLSConfig(lc.TLS, lc.CSP) 324 if err2 != nil { 325 return nil, errors.WithMessage(err2, "Failed to get client TLS config") 326 } 327 328 tlsConfig.ServerName = lc.Host 329 330 conn, err = ldap.DialTLS("tcp", address, tlsConfig) 331 if err != nil { 332 return conn, errors.Wrapf(err, "Failed to connect to LDAP server over TLS at %s", address) 333 } 334 } 335 // Bind with a read only user 336 if lc.AdminDN != "" && lc.AdminPassword != "" { 337 log.Debugf("Binding to the LDAP server as admin user %s", lc.AdminDN) 338 err := conn.Bind(lc.AdminDN, lc.AdminPassword) 339 if err != nil { 340 return nil, errors.Wrapf(err, "LDAP bind failure as %s", lc.AdminDN) 341 } 342 } 343 return conn, nil 344 } 345 346 // A user represents a single user or identity from LDAP 347 type user struct { 348 name string 349 entry *ldap.Entry 350 client *Client 351 } 352 353 // GetName returns the user's enrollment ID, which is the DN (Distinquished Name) 354 func (u *user) GetName() string { 355 return u.entry.DN 356 } 357 358 // GetType returns the type of the user 359 func (u *user) GetType() string { 360 return "client" 361 } 362 363 // GetMaxEnrollments returns the max enrollments of the user 364 func (u *user) GetMaxEnrollments() int { 365 return 0 366 } 367 368 // GetLevel returns the level of the user 369 func (u *user) GetLevel() int { 370 return 0 371 } 372 373 // SetLevel sets the level of the user 374 func (u *user) SetLevel(level int) error { 375 return errNotSupported 376 } 377 378 // Login logs a user in using password 379 func (u *user) Login(password string, caMaxEnrollment int) error { 380 381 // Get a connection to use to bind over as the user to check the password 382 conn, err := u.client.newConnection() 383 if err != nil { 384 return err 385 } 386 defer conn.Close() 387 388 // Bind calls the LDAP server to check the user's password 389 err = conn.Bind(u.entry.DN, password) 390 if err != nil { 391 return errors.Wrapf(err, "LDAP authentication failure for user '%s' (DN=%s)", u.name, u.entry.DN) 392 } 393 394 return nil 395 396 } 397 398 // LoginComplete requires no action on LDAP 399 func (u *user) LoginComplete() error { 400 return nil 401 } 402 403 // GetAffiliationPath returns the affiliation path for this user. 404 // We convert the OU hierarchy to an array of strings, orderered 405 // from top-to-bottom. 406 func (u *user) GetAffiliationPath() []string { 407 dn := u.entry.DN 408 path := []string{} 409 parts := strings.Split(dn, ",") 410 for i := len(parts) - 1; i >= 0; i-- { 411 p := parts[i] 412 if strings.HasPrefix(p, "OU=") { 413 path = append(path, strings.Trim(p[3:], " ")) 414 } 415 } 416 log.Debugf("Affilation path for DN '%s' is '%+v'", dn, path) 417 return path 418 } 419 420 // GetAttribute returns the value of an attribute, or "" if not found 421 func (u *user) GetAttribute(name string) (*api.Attribute, error) { 422 expr := u.client.attrExprs[name] 423 if expr == nil { 424 log.Debugf("Getting attribute '%s' from LDAP user '%s'", name, u.name) 425 vals := u.entry.GetAttributeValues(name) 426 if len(vals) == 0 { 427 vals = make([]string, 0) 428 } 429 return &api.Attribute{Name: name, Value: strings.Join(vals, ",")}, nil 430 } 431 log.Debugf("Evaluating expression for attribute '%s' from LDAP user '%s'", name, u.name) 432 value, err := expr.evaluate(u) 433 if err != nil { 434 return nil, errors.Wrap(err, "Failed to evaluate LDAP expression") 435 } 436 return &api.Attribute{Name: name, Value: fmt.Sprintf("%v", value)}, nil 437 } 438 439 // GetAttributes returns the requested attributes 440 func (u *user) GetAttributes(attrNames []string) ([]api.Attribute, error) { 441 attrs := []api.Attribute{} 442 if attrNames == nil { 443 attrNames = u.client.attrNames 444 } 445 for _, name := range attrNames { 446 attr, err := u.GetAttribute(name) 447 if err != nil { 448 return nil, err 449 } 450 attrs = append(attrs, *attr) 451 } 452 for name := range u.client.attrExprs { 453 attr, err := u.GetAttribute(name) 454 if err != nil { 455 return nil, err 456 } 457 attrs = append(attrs, *attr) 458 } 459 return attrs, nil 460 } 461 462 // Revoke is not supported for LDAP 463 func (u *user) Revoke() error { 464 return errNotSupported 465 } 466 467 // ModifyAttributes adds a new attribute or modifies existing attribute 468 func (u *user) ModifyAttributes(attrs []api.Attribute) error { 469 return errNotSupported 470 } 471 472 // Returns a slice with the elements reversed 473 func reverse(in []string) []string { 474 size := len(in) 475 out := make([]string, size) 476 for i := 0; i < size; i++ { 477 out[i] = in[size-i-1] 478 } 479 return out 480 } 481 482 func newUserExpr(client *Client, attr, expr string) (*userExpr, error) { 483 ue := &userExpr{client: client, attr: attr, expr: expr} 484 err := ue.parse() 485 if err != nil { 486 return nil, err 487 } 488 return ue, nil 489 } 490 491 type userExpr struct { 492 client *Client 493 attr, expr string 494 eval *govaluate.EvaluableExpression 495 user *user 496 } 497 498 func (ue *userExpr) parse() error { 499 eval, err := govaluate.NewEvaluableExpression(ue.expr) 500 if err == nil { 501 // We were able to parse 'expr' without reference to any defined 502 // functions, so we can reuse this evaluator across multiple users. 503 ue.eval = eval 504 return nil 505 } 506 // Try to parse 'expr' with defined functions 507 _, err = govaluate.NewEvaluableExpressionWithFunctions(ue.expr, ue.functions()) 508 if err != nil { 509 return errors.Wrapf(err, "Invalid expression for attribute '%s'", ue.attr) 510 } 511 return nil 512 } 513 514 func (ue *userExpr) evaluate(user *user) (interface{}, error) { 515 var err error 516 parms := map[string]interface{}{ 517 "DN": user.entry.DN, 518 "affiliation": user.GetAffiliationPath(), 519 } 520 eval := ue.eval 521 if eval == nil { 522 ue2 := &userExpr{ 523 client: ue.client, 524 attr: ue.attr, 525 expr: ue.expr, 526 user: user, 527 } 528 eval, err = govaluate.NewEvaluableExpressionWithFunctions(ue2.expr, ue2.functions()) 529 if err != nil { 530 return nil, errors.Wrapf(err, "Invalid expression for attribute '%s'", ue.attr) 531 } 532 } 533 result, err := eval.Evaluate(parms) 534 if err != nil { 535 log.Debugf("Error evaluating expression for attribute '%s'; parms: %+v; error: %+v", ue.attr, parms, err) 536 return nil, err 537 } 538 log.Debugf("Evaluated expression for attribute '%s'; parms: %+v; result: %+v", ue.attr, parms, result) 539 return result, nil 540 } 541 542 func (ue *userExpr) functions() map[string]govaluate.ExpressionFunction { 543 return map[string]govaluate.ExpressionFunction{ 544 "attr": ue.attrFunction, 545 "map": ue.mapFunction, 546 "if": ue.ifFunction, 547 } 548 } 549 550 // Get an LDAP attribute's value. 551 // The usage is: 552 // attrFunction <attrName> [<separator>] 553 // If attribute <attrName> has multiple values, return the values in a single 554 // string separated by the <separator> string, which is a comma by default. 555 // Example: 556 // Assume attribute "foo" has two values "bar1" and "bar2". 557 // attrFunction("foo") returns "bar1,bar2" 558 // attrFunction("foo",":") returns "bar1:bar2" 559 func (ue *userExpr) attrFunction(args ...interface{}) (interface{}, error) { 560 if len(args) < 1 || len(args) > 2 { 561 return nil, fmt.Errorf("Expecting 1 or 2 arguments for 'attr' but found %d", len(args)) 562 } 563 attrName, ok := args[0].(string) 564 if !ok { 565 return nil, errors.Errorf("First argument to 'attr' must be a string; '%s' is not a string", args[0]) 566 } 567 sep := "," 568 if len(args) == 2 { 569 sep, ok = args[1].(string) 570 if !ok { 571 return nil, errors.Errorf("Second argument to 'attr' must be a string; '%s' is not a string", args[1]) 572 } 573 } 574 vals := ue.user.entry.GetAttributeValues(attrName) 575 log.Debugf("Values for LDAP attribute '%s' are '%+v'", attrName, vals) 576 if len(vals) == 0 { 577 vals = make([]string, 0) 578 } 579 return strings.Join(vals, sep), nil 580 } 581 582 // Map function performs string substitutions on the 1st argument for each 583 // entry in the map referenced by the 2nd argument. 584 // 585 // For example, assume that a user's LDAP attribute named 'myLDAPAttr' has 586 // three values: "foo1", "foo2", and "foo3". Further assume the following 587 // LDAP configuration. 588 // 589 // converters: 590 // - name: myAttr 591 // value: map(attr("myLDAPAttr"), myMap) 592 // maps: 593 // myMap: 594 // foo1: bar1 595 // foo2: bar2 596 // 597 // The value of the user's "myAttr" attribute is then "bar1,bar2,foo3". 598 // This value is computed as follows: 599 // 1) The value of 'attr("myLDAPAttr")' is "foo1,foo2,foo3" by joining 600 // the values using the default separator character ",". 601 // 2) The value of 'map("foo1,foo2,foo3", "myMap")' is "foo1,foo2,foo3" 602 // because it maps or substitutes "bar1" for "foo1" and "bar2" for "foo2" 603 // according to the entries in the "myMap" map. 604 func (ue *userExpr) mapFunction(args ...interface{}) (interface{}, error) { 605 if len(args) != 2 { 606 return nil, errors.Errorf("Expecting two arguments but found %d", len(args)) 607 } 608 str, ok := args[0].(string) 609 if !ok { 610 return nil, errors.Errorf("First argument to 'map' must be a string; '%s' is not a string", args[0]) 611 } 612 mapName := args[1].(string) 613 if !ok { 614 return nil, errors.Errorf("Second argument to 'map' must be a string; '%s' is not a string", args[1]) 615 } 616 mapName = strings.ToLower(mapName) 617 // Get the map 618 maps := ue.client.attrMaps 619 if maps == nil { 620 return nil, errors.Errorf("No maps are defined; unknown map name: '%s'", mapName) 621 } 622 myMap := maps[mapName] 623 if myMap == nil { 624 return nil, errors.Errorf("Unknown map name: '%s'", mapName) 625 } 626 // Iterate through all of the entries in the map and perform string substitution 627 // from the name to the value. 628 for name, val := range myMap { 629 str = strings.Replace(str, name, val, -1) 630 } 631 return str, nil 632 } 633 634 // The "ifFunction" returns the 2nd arg if the 1st boolean arg is true; otherwise it 635 // returns the 3rd arg. 636 func (ue *userExpr) ifFunction(args ...interface{}) (interface{}, error) { 637 if len(args) != 3 { 638 return nil, fmt.Errorf("Expecting 3 arguments for 'if' but found %d", len(args)) 639 } 640 cond, ok := args[0].(bool) 641 if !ok { 642 return nil, errors.New("Expecting first argument to 'if' to be a boolean") 643 } 644 if cond { 645 return args[1], nil 646 } 647 return args[2], nil 648 }