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