fix: remove deprecated LDAP username format support (#13165)

This commit is contained in:
Harshavardhana 2021-09-08 13:31:51 -07:00 committed by GitHub
parent 3c2efd9cf3
commit aaa3fc3805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 235 deletions

View File

@ -18,6 +18,7 @@
package cmd
import (
"runtime"
"runtime/debug"
"github.com/minio/minio/internal/logger"
@ -42,7 +43,7 @@ func setMaxResources() (err error) {
return err
}
if maxLimit < 4096 {
if maxLimit < 4096 && runtime.GOOS != globalWindowsOSName {
logger.Info("WARNING: maximum file descriptor limit %d is too low for production servers. At least 4096 is recommended. Fix with \"ulimit -n 4096\"", maxLimit)
}

View File

@ -1,35 +1,6 @@
# AssumeRoleWithLDAPIdentity [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
**Table of Contents**
- [AssumeRoleWithLDAPIdentity [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io)](#assumerolewithldapidentity-slackhttpsslackminioslacktypesvghttpsslackminio)
- [Introduction](#introduction)
- [Configuring AD/LDAP on MinIO](#configuring-adldap-on-minio)
- [Supported modes of operation](#supported-modes-of-operation)
- [Lookup-Bind Mode](#lookup-bind-mode)
- [Username-Format Mode](#username-format-mode)
- [Group membership search](#group-membership-search)
- [Variable substitution in AD/LDAP configuration strings](#variable-substitution-in-adldap-configuration-strings)
- [Managing User/Group Access Policy](#managing-usergroup-access-policy)
- [API Request Parameters](#api-request-parameters)
- [LDAPUsername](#ldapusername)
- [LDAPPassword](#ldappassword)
- [Version](#version)
- [DurationSeconds](#durationseconds)
- [Policy](#policy)
- [Response Elements](#response-elements)
- [Errors](#errors)
- [Sample `POST` Request](#sample-post-request)
- [Sample Response](#sample-response)
- [Using LDAP STS API](#using-ldap-sts-api)
- [Explore Further](#explore-further)
<!-- markdown-toc end -->
## Introduction
MinIO provides a custom STS API that allows integration with LDAP based corporate environments including Microsoft Active Directory. The MinIO server can be configured in two possible modes: either using a LDAP separate service account, called lookup-bind mode or in username-format mode. In either case the login flow for a user is the same as the STS flow:
1. User provides their AD/LDAP username and password to the STS API.
@ -58,7 +29,6 @@ MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN (string) DN for LDAP read-only se
MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD (string) Password for LDAP read-only service account used to perform DN and group lookups
MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN (string) Base LDAP DN to search for user DN
MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER (string) Search filter to lookup user DN
MINIO_IDENTITY_LDAP_USERNAME_FORMAT (list) ";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"
MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER (string) search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"
MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN (list) ";" separated list of group search base DNs e.g. "dc=myldapserver,dc=com"
MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "off" (verify)
@ -67,19 +37,11 @@ MINIO_IDENTITY_LDAP_SERVER_STARTTLS (on|off) use StartTLS connection
MINIO_IDENTITY_LDAP_COMMENT (sentence) optionally add a comment to this setting
```
### Supported modes of operation ###
The two supported modes of LDAP configuration differ in how the MinIO server derives the Distinguished Name (DN) of the user from their username provided in the STS API. _Exactly one must be used in a valid configuration_.
Once a unique DN for the user is derived, the server verifies the user's credentials with the LDAP server and on success, looks up the user's groups via a configured group search query and finally temporary object storage credentials are generated and returned.
#### Lookup-Bind Mode (Recommended) ####
### Lookup-Bind
In this mode, the a low-privilege read-only LDAP service account is configured in the MinIO server by providing the account's Distinguished Name (DN) and password. It is the new and preferred mode for LDAP integration.
This service account is used by the MinIO server to lookup a user's DN given their username. The lookup is performed via an LDAP search filter query that is also configured by the administrator.
This mode is enabled by setting the following variables:
A low-privilege read-only LDAP service account is configured in the MinIO server by providing the account's Distinguished Name (DN) and password. This service account is used by the MinIO server to lookup a user's DN given their username. The lookup is performed via an LDAP search filter query that is also configured by the administrator.
```
MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN (string) DN for LDAP read-only service account used to perform DN and group lookups
@ -90,26 +52,15 @@ MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER (string) Search filter to lookup
If you set an empty lookup bind password, the lookup bind will use the unauthenticated authentication mechanism, as described in [RFC 4513 Section 5.1.2](https://tools.ietf.org/html/rfc4513#section-5.1.2).
**Automatic LDAP sync:** In Lookup-Bind mode, the server periodically queries the LDAP service to:
**Automatic LDAP sync:** MinIO server also periodically queries the LDAP service to:
1. find accounts (user DNs) that have been removed;
2. find accounts whose group memberships have changed.
- find accounts (user DNs) that have been removed
In case 1 above, any active STS credentials or MinIO service accounts belonging to these users are purged.
Any active STS credentials or MinIO service accounts belonging to these users are purged.
In case 2 above, access policies available to the credential are updated to reflect the change - i.e. they will lose any privileges associated with a group they are removed from, and gain any privileges associated with a group they are added to.
- find accounts whose group memberships have changed
#### Username-Format Mode (Deprecated) ####
In this mode, the server does not use a separate LDAP service account. Instead, the username and password provided in the STS API call are used to login to the LDAP server and also to lookup the user's groups. This mode preserves older behavior for compatibility, but users are encouraged to use the Lookup-Bind mode. Some newer features may not be available in this mode of operation.
The DN to use to login to LDAP is computed from a username format configuration parameter. This is a list of possible DN templates to be used. For each such template, the username is substituted and the DN is generated. Each generated DN is tried by the MinIO server to login to LDAP. If exactly one successful DN is found, it is used to perform the groups lookup as well.
This mode is enabled by setting the following variables:
```
MINIO_IDENTITY_LDAP_USERNAME_FORMAT (list) ";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"
```
Access policies available to the credential are updated to reflect the change - i.e. they will lose any privileges associated with a group they are removed from, and gain any privileges associated with a group they are added to.
### Group membership search
@ -128,19 +79,19 @@ If a self-signed certificate is being used, the certificate can be added to MinI
```shell
export MINIO_IDENTITY_LDAP_SERVER_ADDR=myldapserver.com:636
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT="uid=%s,cn=accounts,dc=myldapserver,dc=com"
export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN='cn=admin,dc=min,dc=io'
export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD=admin
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="dc=myldapserver,dc=com"
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(memberUid=%s)$)"
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
```
### Variable substitution in AD/LDAP configuration strings ###
### Variable substitution in AD/LDAP configuration strings
In the configuration variables, `%s` is substituted with the *username* from the STS request and `%d` is substituted with the *distinguished username (user DN)* of the LDAP user. Please see the following table for which configuration variables support these substitution variables:
| Variable | Supported substitutions |
|---------------------------------------------|-------------------------|
| `MINIO_IDENTITY_LDAP_USERNAME_FORMAT` | `%s` |
| `MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER` | `%s` |
| `MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER` | `%s` and `%d` |
@ -254,7 +205,8 @@ With multiple OU hierarchies for users, and multiple group search base DN's.
$ export MINIO_ROOT_USER=minio
$ export MINIO_ROOT_PASSWORD=minio123
$ export MINIO_IDENTITY_LDAP_SERVER_ADDR='my.ldap-active-dir-server.com:636'
$ export MINIO_IDENTITY_LDAP_USERNAME_FORMAT='cn=%s,ou=Users,ou=BUS1,ou=LOB,dc=somedomain,dc=com;cn=%s,ou=Users,ou=BUS2,ou=LOB,dc=somedomain,dc=com'
$ export MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN='cn=admin,dc=min,dc=io'
$ export MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD=admin
$ export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN='dc=minioad,dc=local;dc=somedomain,dc=com'
$ export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER='(&(objectclass=groupOfNames)(member=%d))'
$ minio server ~/test

View File

@ -22,7 +22,6 @@ import (
"crypto/x509"
"errors"
"fmt"
"math/rand"
"net"
"strconv"
"strings"
@ -32,7 +31,6 @@ import (
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env"
)
@ -52,13 +50,6 @@ type Config struct {
// E.g. "ldap.minio.io:636"
ServerAddr string `json:"serverAddr"`
// STS credentials expiry duration
STSExpiryDuration string `json:"stsExpiryDuration"`
// Format string for usernames
UsernameFormat string `json:"usernameFormat"`
UsernameFormats []string `json:"-"`
// User DN search parameters
UserDNSearchBaseDN string `json:"userDNSearchBaseDN"`
UserDNSearchFilter string `json:"userDNSearchFilter"`
@ -83,12 +74,10 @@ type Config struct {
// LDAP keys and envs.
const (
ServerAddr = "server_addr"
STSExpiry = "sts_expiry"
LookupBindDN = "lookup_bind_dn"
LookupBindPassword = "lookup_bind_password"
UserDNSearchBaseDN = "user_dn_search_base_dn"
UserDNSearchFilter = "user_dn_search_filter"
UsernameFormat = "username_format"
GroupSearchFilter = "group_search_filter"
GroupSearchBaseDN = "group_search_base_dn"
TLSSkipVerify = "tls_skip_verify"
@ -96,7 +85,6 @@ const (
ServerStartTLS = "server_starttls"
EnvServerAddr = "MINIO_IDENTITY_LDAP_SERVER_ADDR"
EnvSTSExpiry = "MINIO_IDENTITY_LDAP_STS_EXPIRY"
EnvTLSSkipVerify = "MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY"
EnvServerInsecure = "MINIO_IDENTITY_LDAP_SERVER_INSECURE"
EnvServerStartTLS = "MINIO_IDENTITY_LDAP_SERVER_STARTTLS"
@ -110,6 +98,8 @@ const (
)
var removedKeys = []string{
"sts_expiry",
"username_format",
"username_search_filter",
"username_search_base_dn",
"group_name_attribute",
@ -122,10 +112,6 @@ var (
Key: ServerAddr,
Value: "",
},
config.KV{
Key: UsernameFormat,
Value: "",
},
config.KV{
Key: UserDNSearchBaseDN,
Value: "",
@ -142,10 +128,6 @@ var (
Key: GroupSearchBaseDN,
Value: "",
},
config.KV{
Key: STSExpiry,
Value: "",
},
config.KV{
Key: TLSSkipVerify,
Value: config.EnableOff,
@ -201,47 +183,6 @@ func (l *Config) lookupBind(conn *ldap.Conn) error {
return err
}
// usernameFormatsBind - Iterates over all given username formats and expects
// that only one will succeed if the credentials are valid. The succeeding
// bindDN is returned or an error.
//
// In the rare case that multiple username formats succeed, implying that two
// (or more) distinct users in the LDAP directory have the same username and
// password, we return an error as we cannot identify the account intended by
// the user.
func (l *Config) usernameFormatsBind(conn *ldap.Conn, username, password string) (string, error) {
var bindDistNames []string
var errs = make([]error, len(l.UsernameFormats))
var successCount = 0
for i, usernameFormat := range l.UsernameFormats {
bindDN := fmt.Sprintf(usernameFormat, username)
// Bind with user credentials to validate the password
errs[i] = conn.Bind(bindDN, password)
if errs[i] == nil {
bindDistNames = append(bindDistNames, bindDN)
successCount++
} else if !ldap.IsErrorWithCode(errs[i], 49) {
return "", fmt.Errorf("LDAP Bind request failed with unexpected error: %w", errs[i])
}
}
if successCount == 0 {
var errStrings []string
for _, err := range errs {
if err != nil {
errStrings = append(errStrings, err.Error())
}
}
outErr := fmt.Sprintf("All username formats failed due to invalid credentials: %s", strings.Join(errStrings, "; "))
return "", errors.New(outErr)
}
if successCount > 1 {
successDistNames := strings.Join(bindDistNames, ", ")
errMsg := fmt.Sprintf("Multiple username formats succeeded - ambiguous user login (succeeded for: %s)", successDistNames)
return "", errors.New(errMsg)
}
return bindDistNames[0], nil
}
// lookupUserDN searches for the DN of the user given their username. conn is
// assumed to be using the lookup bind service account. It is required that the
// search result in at most one result.
@ -339,43 +280,28 @@ func (l *Config) Bind(username, password string) (string, []string, error) {
defer conn.Close()
var bindDN string
if l.isUsingLookupBind {
// Bind to the lookup user account
if err = l.lookupBind(conn); err != nil {
return "", nil, err
}
// Bind to the lookup user account
if err = l.lookupBind(conn); err != nil {
return "", nil, err
}
// Lookup user DN
bindDN, err = l.lookupUserDN(conn, username)
if err != nil {
errRet := fmt.Errorf("Unable to find user DN: %w", err)
return "", nil, errRet
}
// Lookup user DN
bindDN, err = l.lookupUserDN(conn, username)
if err != nil {
errRet := fmt.Errorf("Unable to find user DN: %w", err)
return "", nil, errRet
}
// Authenticate the user credentials.
err = conn.Bind(bindDN, password)
if err != nil {
errRet := fmt.Errorf("LDAP auth failed for DN %s: %w", bindDN, err)
return "", nil, errRet
}
// Authenticate the user credentials.
err = conn.Bind(bindDN, password)
if err != nil {
errRet := fmt.Errorf("LDAP auth failed for DN %s: %w", bindDN, err)
return "", nil, errRet
}
// Bind to the lookup user account again to perform group search.
if err = l.lookupBind(conn); err != nil {
return "", nil, err
}
} else {
// Verify login credentials by checking the username formats.
bindDN, err = l.usernameFormatsBind(conn, username, password)
if err != nil {
return "", nil, err
}
// Bind to the successful bindDN again.
err = conn.Bind(bindDN, password)
if err != nil {
errRet := fmt.Errorf("LDAP conn failed though auth for DN %s succeeded: %w", bindDN, err)
return "", nil, errRet
}
// Bind to the lookup user account again to perform group search.
if err = l.lookupBind(conn); err != nil {
return "", nil, err
}
// User groups lookup.
@ -447,30 +373,11 @@ func (l Config) testConnection() error {
return fmt.Errorf("Error creating connection to LDAP server: %w", err)
}
defer conn.Close()
if l.isUsingLookupBind {
if err = l.lookupBind(conn); err != nil {
return fmt.Errorf("Error connecting as LDAP Lookup Bind user: %w", err)
}
return nil
}
// Generate some random user credentials for username formats mode test.
username := fmt.Sprintf("sometestuser%09d", rand.Int31n(1000000000))
charset := []byte("abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
rand.Shuffle(len(charset), func(i, j int) {
charset[i], charset[j] = charset[j], charset[i]
})
password := string(charset[:20])
_, err = l.usernameFormatsBind(conn, username, password)
if err == nil {
// We don't expect to successfully guess a credential in this
// way.
return fmt.Errorf("Unexpected random credentials success for user=%s password=%s", username, password)
} else if strings.HasPrefix(err.Error(), "All username formats failed due to invalid credentials: ") {
return nil
if err = l.lookupBind(conn); err != nil {
return fmt.Errorf("Error connecting as LDAP Lookup Bind user: %w", err)
}
return fmt.Errorf("LDAP connection test error: %w", err)
return nil
}
// IsLDAPUserDN determines if the given string could be a user DN from LDAP.
@ -588,21 +495,6 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
l.rootCAs = rootCAs
l.ServerAddr = ldapServer
l.stsExpiryDuration = defaultLDAPExpiry
if v := env.Get(EnvSTSExpiry, kvs.Get(STSExpiry)); v != "" {
logger.Info("DEPRECATION WARNING: Support for configuring the default LDAP credentials expiry duration will be removed by October 2021. Please use the `DurationSeconds` parameter in the LDAP STS API instead.")
expDur, err := time.ParseDuration(v)
if err != nil {
return l, errors.New("LDAP expiry time err:" + err.Error())
}
if expDur < minLDAPExpiry {
return l, fmt.Errorf("LDAP expiry time must be at least %s", minLDAPExpiry)
}
if expDur > maxLDAPExpiry {
return l, fmt.Errorf("LDAP expiry time may not exceed %s", maxLDAPExpiry)
}
l.STSExpiryDuration = v
l.stsExpiryDuration = expDur
}
// LDAP connection configuration
if v := env.Get(EnvServerInsecure, kvs.Get(ServerInsecure)); v != "" {
@ -642,26 +534,9 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
l.UserDNSearchFilter = userDNSearchFilter
}
// Username format configuration.
if v := env.Get(EnvUsernameFormat, kvs.Get(UsernameFormat)); v != "" {
if !strings.Contains(v, "%s") {
return l, errors.New("LDAP username format does not support '%s' substitution")
}
l.UsernameFormats = strings.Split(v, dnDelimiter)
}
if len(l.UsernameFormats) > 0 {
logger.Info("DEPRECATION WARNING: Support for %s will be removed by October 2021, please migrate your LDAP settings to lookup bind mode", UsernameFormat)
}
// Either lookup bind mode or username format is supported, but not both.
if l.isUsingLookupBind && len(l.UsernameFormats) > 0 {
return l, errors.New("Lookup Bind mode and Username Format mode are not supported at the same time")
}
// At least one of bind mode or username format must be used.
if !l.isUsingLookupBind && len(l.UsernameFormats) == 0 {
return l, errors.New("Either Lookup Bind mode or Username Format mode is required")
// Lookup bind mode is mandatory
if !l.isUsingLookupBind {
return l, errors.New("Lookup Bind mode is required")
}
// Test connection to LDAP server.

View File

@ -28,12 +28,6 @@ var (
Type: "address",
Sensitive: true,
},
config.HelpKV{
Key: STSExpiry,
Description: `[DEPRECATED] temporary credentials validity duration in s,m,h,d. Default is "1h"`,
Optional: true,
Type: "duration",
},
config.HelpKV{
Key: LookupBindDN,
Description: `DN for LDAP read-only service account used to perform DN and group lookups`,
@ -60,13 +54,6 @@ var (
Optional: true,
Type: "string",
},
config.HelpKV{
Key: UsernameFormat,
Description: `[DEPRECATED] ";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"`,
Optional: true,
Type: "list",
Sensitive: true,
},
config.HelpKV{
Key: GroupSearchFilter,
Description: `search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"`,

View File

@ -30,14 +30,6 @@ func SetIdentityLDAP(s config.Config, ldapArgs Config) {
Key: ServerAddr,
Value: ldapArgs.ServerAddr,
},
config.KV{
Key: STSExpiry,
Value: ldapArgs.STSExpiryDuration,
},
config.KV{
Key: UsernameFormat,
Value: ldapArgs.UsernameFormat,
},
config.KV{
Key: GroupSearchFilter,
Value: ldapArgs.GroupSearchFilter,

View File

@ -23,6 +23,7 @@ import (
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/env"
)
@ -89,7 +90,10 @@ func Lookup(kvs config.KVS) (Config, error) {
if err != nil {
return Config{}, err
}
enabled, err := config.ParseBool(env.Get(EnvEnabled, "on"))
if insecureSkipVerify {
logger.Info("CRITICAL: enabling MINIO_IDENTITY_TLS_SKIP_VERIFY is not recommended in a production environment")
}
enabled, err := config.ParseBool(env.Get(EnvEnabled, config.EnableOn))
if err != nil {
return Config{}, err
}