From ad78449266e00242d0cb41c9543b739593772737 Mon Sep 17 00:00:00 2001 From: vmware Date: Thu, 18 Aug 2016 18:31:41 +0800 Subject: [PATCH] update ldap configuration --- Deploy/harbor.cfg | 17 ++++++- Deploy/prepare | 19 ++++++++ Deploy/templates/ui/env | 5 +++ auth/ldap/ldap.go | 90 ++++++++++++++++++++++++++------------ docs/installation_guide.md | 5 +++ 5 files changed, 107 insertions(+), 29 deletions(-) diff --git a/Deploy/harbor.cfg b/Deploy/harbor.cfg index 1fdc27676..0726fbe73 100644 --- a/Deploy/harbor.cfg +++ b/Deploy/harbor.cfg @@ -26,10 +26,25 @@ auth_mode = db_auth #The url for an ldap endpoint. ldap_url = ldaps://ldap.mydomain.com +#ldap_searchdn, set the user who has the permission to search the LDAP/AD server. If your ldap/AD server does not support anonymous search, you should configure it and ldap_search_pwd. +#ldap_searchdn = cn=admin,ou=people,dc=mydomain,dc=com + +#the password of the ldap_searchdn +#ldap_search_pwd = admin + #The basedn template to look up a user in LDAP and verify the user's password. #For AD server, uses this template: #ldap_basedn = CN=%s,OU=Dept1,DC=mydomain,DC=com -ldap_basedn = uid=%s,ou=people,dc=mydomain,dc=com +ldap_basedn = ou=people,dc=mydomain,dc=com + +#ldap filter, set the attribute to filter a user, you can add as many as you need, be sure the grammar is right. If needed, configure it. +#ldap_filter = objectClass=person + +#the exclusive attribute to distinguish a user, it can be uid or cn or mail or email or sAMAccountName, for example: ldap_uid = uid +ldap_uid = uid + +#ldap_scope, set the scope to search, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE, default is 3 +ldap_scope = 3 #The password for the root user of mysql db, change this before any production use. db_password = root123 diff --git a/Deploy/prepare b/Deploy/prepare index abb7e832a..0edd19251 100755 --- a/Deploy/prepare +++ b/Deploy/prepare @@ -47,7 +47,21 @@ email_ssl = rcp.get("configuration", "email_ssl") harbor_admin_password = rcp.get("configuration", "harbor_admin_password") auth_mode = rcp.get("configuration", "auth_mode") ldap_url = rcp.get("configuration", "ldap_url") +# this two options are either both set or unset +if rcp.has_option("configuration", "ldap_searchdn"): + ldap_searchdn = rcp.get("configuration", "ldap_searchdn") + ldap_search_pwd = rcp.get("configuration", "ldap_search_pwd") +else: + ldap_searchdn = "" + ldap_search_pwd = "" ldap_basedn = rcp.get("configuration", "ldap_basedn") +# ldap_filter is null by default +if rcp.has_option("configuration", "ldap_filter"): + ldap_filter = rcp.get("configuration", "ldap_filter") +else: + ldap_filter = "" +ldap_uid = rcp.get("configuration", "ldap_uid") +ldap_scope = rcp.get("configuration", "ldap_scope") db_password = rcp.get("configuration", "db_password") self_registration = rcp.get("configuration", "self_registration") use_compressed_js = rcp.get("configuration", "use_compressed_js") @@ -118,7 +132,12 @@ render(os.path.join(templates_dir, "ui", "env"), auth_mode=auth_mode, harbor_admin_password=harbor_admin_password, ldap_url=ldap_url, + ldap_searchdn =ldap_searchdn, + ldap_search_pwd =ldap_search_pwd, ldap_basedn=ldap_basedn, + ldap_filter=ldap_filter, + ldap_uid=ldap_uid, + ldap_scope=ldap_scope, self_registration=self_registration, use_compressed_js=use_compressed_js, ui_secret=ui_secret, diff --git a/Deploy/templates/ui/env b/Deploy/templates/ui/env index c4afba44b..92311cc27 100644 --- a/Deploy/templates/ui/env +++ b/Deploy/templates/ui/env @@ -10,7 +10,12 @@ HARBOR_ADMIN_PASSWORD=$harbor_admin_password HARBOR_URL=$ui_url AUTH_MODE=$auth_mode LDAP_URL=$ldap_url +LDAP_SEARCH_DN=$ldap_searchdn +LDAP_SEARCH_PWD=$ldap_search_pwd LDAP_BASE_DN=$ldap_basedn +LDAP_FILTER=$ldap_filter +LDAP_UID=$ldap_uid +LDAP_SCOPE=$ldap_scope UI_SECRET=$ui_secret SELF_REGISTRATION=$self_registration USE_COMPRESSED_JS=$use_compressed_js diff --git a/auth/ldap/ldap.go b/auth/ldap/ldap.go index 3c12ee15b..2681be5f7 100644 --- a/auth/ldap/ldap.go +++ b/auth/ldap/ldap.go @@ -40,62 +40,96 @@ const metaChars = "&|!=~*<>()" // be associated to other entities in the system. func (l *Auth) Authenticate(m models.AuthModel) (*models.User, error) { - ldapURL := os.Getenv("LDAP_URL") - if ldapURL == "" { - return nil, errors.New("Can not get any available LDAP_URL.") - } - log.Debug("ldapURL:", ldapURL) - p := m.Principal for _, c := range metaChars { if strings.ContainsRune(p, c) { return nil, fmt.Errorf("the principal contains meta char: %q", c) } } - + ldapURL := os.Getenv("LDAP_URL") + if ldapURL == "" { + return nil, errors.New("Can not get any available LDAP_URL.") + } + log.Debug("ldapURL:", ldapURL) ldap, err := openldap.Initialize(ldapURL) if err != nil { return nil, err } - ldap.SetOption(openldap.LDAP_OPT_PROTOCOL_VERSION, openldap.LDAP_VERSION3) ldapBaseDn := os.Getenv("LDAP_BASE_DN") if ldapBaseDn == "" { return nil, errors.New("Can not get any available LDAP_BASE_DN.") } + log.Debug("baseDn:", ldapBaseDn) - baseDn := fmt.Sprintf(ldapBaseDn, m.Principal) - log.Debug("baseDn:", baseDn) + ldapSearchDn := os.Getenv("LDAP_SEARCH_DN") + if ldapSearchDn != "" { + log.Debug("Search DN: ", ldapSearchDn) + ldapSearchPwd := os.Getenv("LDAP_SEARCH_PWD") + err = ldap.Bind(ldapSearchDn, ldapSearchPwd) + if err != nil { + log.Debug("Bind search dn error", err) + return nil, err + } + } - err = ldap.Bind(baseDn, m.Password) + attrName := os.Getenv("LDAP_UID") + filter := os.Getenv("LDAP_FILTER") + if filter != "" { + filter = "(&(" + filter + ")(" + attrName + "=" + m.Principal + "))" + } else { + filter = "(" + attrName + "=" + m.Principal + ")" + } + log.Debug("one or more filter", filter) + + ldapScope := os.Getenv("LDAP_SCOPE") + var scope int + if ldapScope == "1" { + scope = openldap.LDAP_SCOPE_BASE + } else if ldapScope == "2" { + scope = openldap.LDAP_SCOPE_ONELEVEL + } else { + scope = openldap.LDAP_SCOPE_SUBTREE + } + attributes := []string{"uid", "cn", "mail", "email"} + result, err := ldap.SearchAll(ldapBaseDn, scope, filter, attributes) if err != nil { return nil, err } + if len(result.Entries()) == 0 { + log.Warningf("Not found an entry.") + return nil, nil + } else if len(result.Entries()) != 1 { + log.Warningf("Found more than one entry.") + return nil, nil + } + en := result.Entries()[0] + bindDN := en.Dn() + log.Debug("found entry:", en) + err = ldap.Bind(bindDN, m.Password) + if err != nil { + log.Debug("Bind user error", err) + return nil, err + } defer ldap.Close() - scope := openldap.LDAP_SCOPE_SUBTREE // LDAP_SCOPE_BASE, LDAP_SCOPE_ONELEVEL, LDAP_SCOPE_SUBTREE - filter := "objectClass=*" - attributes := []string{"mail"} - - result, err := ldap.SearchAll(baseDn, scope, filter, attributes) - if err != nil { - return nil, err - } u := models.User{} - if len(result.Entries()) == 1 { - en := result.Entries()[0] - for _, attr := range en.Attributes() { - val := attr.Values()[0] - if attr.Name() == "mail" { - u.Email = val - } + for _, attr := range en.Attributes() { + val := attr.Values()[0] + switch attr.Name() { + case "uid": + u.Realname = val + case "cn": + u.Realname = val + case "mail": + u.Email = val + case "email": + u.Email = val } } - u.Username = m.Principal log.Debug("username:", u.Username, ",email:", u.Email) - exist, err := dao.UserExists(u, "username") if err != nil { return nil, err diff --git a/docs/installation_guide.md b/docs/installation_guide.md index f4d4c304f..695a56a36 100644 --- a/docs/installation_guide.md +++ b/docs/installation_guide.md @@ -48,7 +48,12 @@ The parameters are described below - note that at the very least, you will need * **harbor_admin_password**: The adminstrator's password. _Note that the default username/password are **admin/Harbor12345** ._ * **auth_mode**: The type of authentication that is used. By default it is **db_auth**, i.e. the credentials are stored in a database. For LDAP authentication, set this to **ldap_auth**. * **ldap_url**: The LDAP endpoint URL (e.g. `ldaps://ldap.mydomain.com`). _Only used when **auth_mode** is set to *ldap_auth* ._ +* **ldap_searchdn**: The dn of the user who has the permission to search a ldap/AD server (e.g. `cn=admin,ou=people,dc=mydomain,dc=com`). +* **ldap_search_pwd**: The password of the user who was set as the ldap_searchdn. * **ldap_basedn**: The basedn template for verifying the user's credential against an LDAP (e.g. `uid=%s,ou=people,dc=mydomain,dc=com` ) or an AD (e.g. `CN=%s,OU=Dept1,DC=mydomain,DC=com`) server. _Only used when **auth_mode** is set to *ldap_auth* ._ +* **ldap_filter**:The attribute to filter a user, you can add as many as you need, be sure the grammar is right. If not needed, comment it (e.g. `ldap_filter = objectClass=person`). +* **ldap_uid**:The exclusive attribute to distinguish a user, it can be uid or cn or mail or email(e.g. `ldap_uid = uid`). +* **ldap_scope**:The scope to search, 1-LDAP_SCOPE_BASE, 2-LDAP_SCOPE_ONELEVEL, 3-LDAP_SCOPE_SUBTREE, default is 3(e.g. `ldap_scope = 3`). * **db_password**: The root password for the mySQL database used for **db_auth**. _Change this password for any production use!_ * **self_registration**: (**on** or **off**. Default is **on**) Enable / Disable the ability for a user to register themselves. When disabled, new users can only be created by the Admin user, only an admin user can create new users in Harbor. _NOTE: When **auth_mode** is set to **ldap_auth**, self-registration feature is **always** disabled, and this flag is ignored._ * **use_compressed_js**: (**on** or **off**. Default is **on**) For production use, turn this flag to **on**. In development mode, set it to **off** so that js files can be modified separately.