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