...
 
Commits (2)
......@@ -1533,7 +1533,7 @@ func (a adminAPIHandlers) ServerInfoHandler(w http.ResponseWriter, r *http.Reque
func fetchLambdaInfo(cfg config.Config) []map[string][]madmin.TargetIDStatus {
// Fetch the targets
targetList, err := notify.RegisterNotificationTargets(cfg, GlobalServiceDoneCh, NewCustomHTTPTransport(), nil, true)
targetList, err := notify.RegisterNotificationTargets(cfg, GlobalServiceDoneCh, NewGatewayHTTPTransport(), nil, true)
if err != nil {
return nil
}
......
......@@ -251,7 +251,7 @@ func validateConfig(s config.Config) error {
}
}
{
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewCustomHTTPTransport())
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
if err != nil {
return err
}
......@@ -269,17 +269,27 @@ func validateConfig(s config.Config) error {
}
if _, err := openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody); err != nil {
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
return err
}
if _, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default],
globalRootCAs); err != nil {
return err
{
cfg, err := xldap.Lookup(s[config.IdentityLDAPSubSys][config.Default],
globalRootCAs)
if err != nil {
return err
}
if cfg.Enabled {
conn, cerr := cfg.Connect()
if cerr != nil {
return cerr
}
conn.Close()
}
}
if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody); err != nil {
NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil {
return err
}
......@@ -287,7 +297,7 @@ func validateConfig(s config.Config) error {
return err
}
return notify.TestNotificationTargets(s, GlobalServiceDoneCh, NewCustomHTTPTransport(),
return notify.TestNotificationTargets(s, GlobalServiceDoneCh, NewGatewayHTTPTransport(),
globalNotificationSys.ConfiguredTargetIDs())
}
......@@ -362,7 +372,7 @@ func lookupConfigs(s config.Config) {
}
}
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewCustomHTTPTransport())
kmsCfg, err := crypto.LookupConfig(s, globalCertsCADir.Get(), NewGatewayHTTPTransport())
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to setup KMS config: %w", err))
}
......@@ -381,13 +391,13 @@ func lookupConfigs(s config.Config) {
}
globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody)
NewGatewayHTTPTransport(), xhttp.DrainBody)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize OpenID: %w", err))
}
opaCfg, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default],
NewCustomHTTPTransport(), xhttp.DrainBody)
NewGatewayHTTPTransport(), xhttp.DrainBody)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize OPA: %w", err))
}
......@@ -412,22 +422,23 @@ func lookupConfigs(s config.Config) {
for _, l := range loggerCfg.HTTP {
if l.Enabled {
// Enable http logging
logger.AddTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewCustomHTTPTransport()))
logger.AddTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewGatewayHTTPTransport()))
}
}
for _, l := range loggerCfg.Audit {
if l.Enabled {
// Enable http audit logging
logger.AddAuditTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewCustomHTTPTransport()))
logger.AddAuditTarget(http.New(l.Endpoint, loggerUserAgent, string(logger.All), NewGatewayHTTPTransport()))
}
}
globalConfigTargetList, err = notify.GetNotificationTargets(s, GlobalServiceDoneCh, NewCustomHTTPTransport())
globalConfigTargetList, err = notify.GetNotificationTargets(s, GlobalServiceDoneCh, NewGatewayHTTPTransport())
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
}
globalEnvTargetList, err = notify.GetNotificationTargets(newServerConfig(), GlobalServiceDoneCh, NewCustomHTTPTransport())
globalEnvTargetList, err = notify.GetNotificationTargets(newServerConfig(), GlobalServiceDoneCh, NewGatewayHTTPTransport())
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
}
......
This diff is collapsed.
/*
* MinIO Cloud Storage, (C) 2019 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ldap
import (
"testing"
)
func TestSubstituter(t *testing.T) {
tests := []struct {
KV []string
SubstitutableStr string
SubstitutedStr string
ErrExpected bool
}{
{
KV: []string{"username", "john"},
SubstitutableStr: "uid=${username},cn=users,dc=example,dc=com",
SubstitutedStr: "uid=john,cn=users,dc=example,dc=com",
ErrExpected: false,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "uid={username},cn=users,dc=example,dc=com",
SubstitutedStr: "uid=john,cn=users,dc=example,dc=com",
ErrExpected: false,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "(&(objectclass=group)(member=${username}))",
SubstitutedStr: "(&(objectclass=group)(member=john))",
ErrExpected: false,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "(&(objectclass=group)(member={username}))",
SubstitutedStr: "(&(objectclass=group)(member=john))",
ErrExpected: false,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "uid=${{username}},cn=users,dc=example,dc=com",
ErrExpected: true,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "uid=${usernamedn},cn=users,dc=example,dc=com",
ErrExpected: true,
},
{
KV: []string{"username"},
SubstitutableStr: "uid=${usernamedn},cn=users,dc=example,dc=com",
ErrExpected: true,
},
{
KV: []string{"username", "john"},
SubstitutableStr: "(&(objectclass=user)(sAMAccountName={username})(memberOf=CN=myorg,OU=Rialto,OU=Application Managed,OU=Groups,DC=amr,DC=corp,DC=myorg,DC=com))",
SubstitutedStr: "(&(objectclass=user)(sAMAccountName=john)(memberOf=CN=myorg,OU=Rialto,OU=Application Managed,OU=Groups,DC=amr,DC=corp,DC=myorg,DC=com))",
ErrExpected: false,
},
}
for _, test := range tests {
test := test
t.Run(test.SubstitutableStr, func(t *testing.T) {
subber, err := NewSubstituter(test.KV...)
if err != nil && !test.ErrExpected {
t.Errorf("Unexpected failure %s", err)
}
gotStr, err := subber.Substitute(test.SubstitutableStr)
if err != nil && !test.ErrExpected {
t.Errorf("Unexpected failure %s", err)
}
if gotStr != test.SubstitutedStr {
t.Errorf("Expected %s, got %s", test.SubstitutedStr, gotStr)
}
})
}
}
......@@ -28,24 +28,33 @@ var (
},
config.HelpKV{
Key: UsernameFormat,
Description: `username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"`,
Description: `";" separated list of username bind DNs e.g. "uid=%s,cn=accounts,dc=myldapserver,dc=com"`,
Type: "list",
},
config.HelpKV{
Key: UsernameSearchFilter,
Description: `user search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"`,
Type: "string",
},
config.HelpKV{
Key: GroupSearchFilter,
Description: `search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"`,
Optional: true,
Type: "string",
},
config.HelpKV{
Key: GroupNameAttribute,
Description: `search attribute for group name e.g. "cn"`,
Key: GroupSearchBaseDN,
Description: `";" separated list of group search base DNs e.g. "dc=myldapserver,dc=com"`,
Type: "list",
},
config.HelpKV{
Key: UsernameSearchBaseDN,
Description: `";" separated list of username search DNs`,
Type: "list",
Optional: true,
Type: "string",
},
config.HelpKV{
Key: GroupSearchBaseDN,
Description: `group search base DNs e.g. "dc=myldapserver,dc=com"`,
Key: GroupNameAttribute,
Description: `search attribute for group name e.g. "cn"`,
Optional: true,
Type: "string",
},
......@@ -63,7 +72,7 @@ var (
},
config.HelpKV{
Key: ServerInsecure,
Description: `allow plain text connection to AD/LDAP server, defaults to "off" (TLS)`,
Description: `allow plain text connection to AD/LDAP server, defaults to "off"`,
Optional: true,
Type: "on|off",
},
......
/*
* MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -150,7 +150,7 @@ func (g *Azure) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, erro
metrics := minio.NewMetrics()
t := &minio.MetricsTransport{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
Metrics: metrics,
}
......
/*
* MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -102,7 +102,7 @@ func (g *B2) Name() string {
// talk to B2 remote backend.
func (g *B2) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error) {
ctx := context.Background()
client, err := b2.AuthorizeAccount(ctx, creds.AccessKey, creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
client, err := b2.AuthorizeAccount(ctx, creds.AccessKey, creds.SecretKey, b2.Transport(minio.NewGatewayHTTPTransport()))
if err != nil {
return nil, err
}
......@@ -111,7 +111,7 @@ func (g *B2) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error)
creds: creds,
b2Client: client,
httpClient: &http.Client{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
},
ctx: ctx,
}, nil
......@@ -238,7 +238,7 @@ func (l *b2Objects) MakeBucketWithLocation(ctx context.Context, bucket, location
}
func (l *b2Objects) reAuthorizeAccount(ctx context.Context) error {
client, err := b2.AuthorizeAccount(l.ctx, l.creds.AccessKey, l.creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
client, err := b2.AuthorizeAccount(l.ctx, l.creds.AccessKey, l.creds.SecretKey, b2.Transport(minio.NewGatewayHTTPTransport()))
if err != nil {
return err
}
......
/*
* MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -184,7 +184,7 @@ func (g *GCS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error)
metrics := minio.NewMetrics()
t := &minio.MetricsTransport{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
Metrics: metrics,
}
......
/*
* MinIO Cloud Storage, (C) 2017, 2018 MinIO, Inc.
* MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -126,7 +126,7 @@ func (g *OSS) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error)
return nil, err
}
client.HTTPClient = &http.Client{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
}
return &ossObjects{
Client: client,
......
......@@ -152,7 +152,7 @@ var defaultAWSCredProviders = []credentials.Provider{
&credentials.FileAWSCredentials{},
&credentials.IAM{
Client: &http.Client{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
},
},
&credentials.EnvMinio{},
......@@ -212,7 +212,7 @@ func (g *S3) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error)
metrics := minio.NewMetrics()
t := &minio.MetricsTransport{
Transport: minio.NewCustomHTTPTransport(),
Transport: minio.NewGatewayHTTPTransport(),
Metrics: metrics,
}
......
/*
* MinIO Cloud Storage, (C) 2015, 2016, 2017 MinIO, Inc.
* MinIO Cloud Storage, (C) 2015-2020 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -721,7 +721,7 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
func setBucketForwardingHandler(h http.Handler) http.Handler {
fwd := handlers.NewForwarder(&handlers.Forwarder{
PassHost: true,
RoundTripper: NewCustomHTTPTransport(),
RoundTripper: NewGatewayHTTPTransport(),
Logger: func(err error) {
logger.LogIf(context.Background(), err)
},
......
......@@ -369,6 +369,10 @@ func (sys *IAMSys) Init(objAPI ObjectLayer) error {
return errServerNotInitialized
}
if globalLDAPConfig.Enabled {
sys.EnableLDAPSys()
}
sys.Lock()
if globalEtcdClient == nil {
sys.store = newIAMObjectStore()
......@@ -1791,22 +1795,18 @@ func (sys *IAMSys) removeGroupFromMembershipsMap(group string) {
}
}
// EnableLDAPSys - enable ldap system users type.
func (sys *IAMSys) EnableLDAPSys() {
sys.Lock()
defer sys.Unlock()
sys.usersSysType = LDAPUsersSysType
}
// NewIAMSys - creates new config system object.
func NewIAMSys() *IAMSys {
// Check global server configuration to determine the type of
// users system configured.
// The default users system
var utype UsersSysType
switch {
case globalLDAPConfig.Enabled:
utype = LDAPUsersSysType
default:
utype = MinIOUsersSysType
}
return &IAMSys{
usersSysType: utype,
usersSysType: MinIOUsersSysType,
iamUsersMap: make(map[string]auth.Credentials),
iamPolicyDocsMap: make(map[string]iampolicy.Policy),
iamUserPolicyMap: make(map[string]MappedPolicy),
......
......@@ -784,6 +784,9 @@ func (sys *NotificationSys) RemoveNotification(bucketName string) {
// RemoveAllRemoteTargets - closes and removes all HTTP/PeerRPC client targets.
func (sys *NotificationSys) RemoveAllRemoteTargets() {
sys.Lock()
defer sys.Unlock()
for _, targetMap := range sys.bucketRemoteTargetRulesMap {
for targetID := range targetMap {
sys.targetList.Remove(targetID)
......
......@@ -657,7 +657,7 @@ var getRemoteInstanceClient = func(r *http.Request, host string) (*miniogo.Core,
if err != nil {
return nil, err
}
core.SetCustomTransport(NewCustomHTTPTransport())
core.SetCustomTransport(NewGatewayHTTPTransport())
return core, nil
}
......
......@@ -444,14 +444,16 @@ func (s *storageRESTServer) WalkHandler(w http.ResponseWriter, r *http.Request)
}
leafFile := vars[storageRESTLeafFile]
w.Header().Set(xhttp.ContentType, "text/event-stream")
encoder := gob.NewEncoder(w)
done := keepHTTPResponseAlive(w)
defer done()
fch, err := s.storage.Walk(volume, dirPath, markerPath, recursive, leafFile, readMetadata, r.Context().Done())
if err != nil {
s.writeErrorResponse(w, err)
logger.LogIf(r.Context(), err)
return
}
w.Header().Set(xhttp.ContentType, "text/event-stream")
encoder := gob.NewEncoder(w)
for fi := range fch {
encoder.Encode(&fi)
}
......@@ -472,6 +474,7 @@ func (s *storageRESTServer) ListDirHandler(w http.ResponseWriter, r *http.Reques
s.writeErrorResponse(w, err)
return
}
entries, err := s.storage.ListDir(volume, dirPath, count, leafFile)
if err != nil {
s.writeErrorResponse(w, err)
......@@ -521,20 +524,21 @@ func (s *storageRESTServer) DeleteFileBulkHandler(w http.ResponseWriter, r *http
return
}
dErrsResp := &DeleteFileBulkErrsResp{Errs: make([]error, len(filePaths))}
w.Header().Set(xhttp.ContentType, "text/event-stream")
encoder := gob.NewEncoder(w)
done := keepHTTPResponseAlive(w)
errs, err := s.storage.DeleteFileBulk(volume, filePaths)
done()
if err != nil {
s.writeErrorResponse(w, err)
return
}
dErrsResp := &DeleteFileBulkErrsResp{Errs: make([]error, len(errs))}
for idx, err := range errs {
for idx := range filePaths {
if err != nil {
dErrsResp.Errs[idx] = StorageErr(err.Error())
} else {
if errs[idx] != nil {
dErrsResp.Errs[idx] = StorageErr(errs[idx].Error())
}
}
}
......@@ -567,20 +571,20 @@ func (s *storageRESTServer) DeletePrefixesHandler(w http.ResponseWriter, r *http
return
}
dErrsResp := &DeletePrefixesErrsResp{Errs: make([]error, len(prefixes))}
w.Header().Set(xhttp.ContentType, "text/event-stream")
encoder := gob.NewEncoder(w)
done := keepHTTPResponseAlive(w)
errs, err := s.storage.DeletePrefixes(volume, prefixes)
done()
if err != nil {
s.writeErrorResponse(w, err)
return
}
dErrsResp := &DeletePrefixesErrsResp{Errs: make([]error, len(errs))}
for idx, err := range errs {
for idx := range prefixes {
if err != nil {
dErrsResp.Errs[idx] = StorageErr(err.Error())
} else {
if errs[idx] != nil {
dErrsResp.Errs[idx] = StorageErr(errs[idx].Error())
}
}
}
encoder.Encode(dErrsResp)
......
......@@ -24,14 +24,12 @@ import (
"net/http"
"github.com/gorilla/mux"
xldap "github.com/minio/minio/cmd/config/identity/ldap"
"github.com/minio/minio/cmd/config/identity/openid"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
iampolicy "github.com/minio/minio/pkg/iam/policy"
"github.com/minio/minio/pkg/wildcard"
ldap "gopkg.in/ldap.v3"
)
const (
......@@ -477,61 +475,13 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
}
}
ldapConn, err := globalLDAPConfig.Connect()
groups, err := globalLDAPConfig.Bind(ldapUsername, ldapPassword)
if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("LDAP server connection failure: %w", err))
return
}
if ldapConn == nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("LDAP server not configured: %w", err))
return
}
// Close ldap connection to avoid leaks.
defer ldapConn.Close()
usernameSubs, _ := xldap.NewSubstituter("username", ldapUsername)
// We ignore error below as we already validated the username
// format string at startup.
usernameDN, _ := usernameSubs.Substitute(globalLDAPConfig.UsernameFormat)
// Bind with user credentials to validate the password
if err = ldapConn.Bind(usernameDN, ldapPassword); err != nil {
err = fmt.Errorf("LDAP authentication failure: %w", err)
err = fmt.Errorf("LDAP server connection failure: %w", err)
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
return
}
groups := []string{}
if globalLDAPConfig.GroupSearchFilter != "" {
// Verified user credentials. Now we find the groups they are
// a member of.
searchSubs, _ := xldap.NewSubstituter(
"username", ldapUsername,
"usernamedn", usernameDN,
)
// We ignore error below as we already validated the search string
// at startup.
groupSearchFilter, _ := searchSubs.Substitute(globalLDAPConfig.GroupSearchFilter)
baseDN, _ := searchSubs.Substitute(globalLDAPConfig.GroupSearchBaseDN)
searchRequest := ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
groupSearchFilter,
[]string{globalLDAPConfig.GroupNameAttribute},
nil,
)
sr, err := ldapConn.Search(searchRequest)
if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, fmt.Errorf("LDAP search failure: %w", err))
return
}
for _, entry := range sr.Entries {
// We only queried one attribute, so we only look up
// the first one.
groups = append(groups, entry.Attributes[0].Values...)
}
}
expiryDur := globalLDAPConfig.GetExpiryDuration()
m := map[string]interface{}{
expClaim: UTCNow().Add(expiryDur).Unix(),
......
......@@ -151,10 +151,9 @@ const (
// (Acceptable values range from 1 to 10000 inclusive)
globalMaxPartID = 10000
// Default values used while communicating for
// internode communication.
defaultDialTimeout = 15 * time.Second
defaultDialKeepAlive = 20 * time.Second
// Default values used while communicating for internode communication.
defaultDialTimeout = 5 * time.Second
defaultDialKeepAlive = 15 * time.Second
)
// isMaxObjectSize - verify if max object size
......@@ -452,7 +451,8 @@ func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout, dialKeepAlive ti
Proxy: http.ProxyFromEnvironment,
DialContext: newCustomDialContext(dialTimeout, dialKeepAlive),
MaxIdleConnsPerHost: 256,
IdleConnTimeout: 60 * time.Second,
IdleConnTimeout: time.Minute,
ResponseHeaderTimeout: 3 * time.Minute, // Set conservative timeouts for MinIO internode.
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
......@@ -466,14 +466,17 @@ func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout, dialKeepAlive ti
}
}
// NewCustomHTTPTransport returns a new http configuration
// NewGatewayHTTPTransport returns a new http configuration
// used while communicating with the cloud backends.
// This sets the value for MaxIdleConnsPerHost from 2 (go default)
// to 256.
func NewCustomHTTPTransport() *http.Transport {
return newCustomHTTPTransport(&tls.Config{
func NewGatewayHTTPTransport() *http.Transport {
tr := newCustomHTTPTransport(&tls.Config{
RootCAs: globalRootCAs,
}, defaultDialTimeout, defaultDialKeepAlive)()
// Set aggressive timeouts for gateway
tr.ResponseHeaderTimeout = 30 * time.Second
return tr
}
// Load the json (typically from disk file).
......
......@@ -2050,7 +2050,7 @@ func (web *webAPIHandlers) LoginSTS(r *http.Request, args *LoginSTSArgs, reply *
}
clnt := &http.Client{
Transport: NewCustomHTTPTransport(),
Transport: NewGatewayHTTPTransport(),
}
resp, err := clnt.Do(req)
......@@ -2059,6 +2059,7 @@ func (web *webAPIHandlers) LoginSTS(r *http.Request, args *LoginSTSArgs, reply *
return toJSONError(ctx, err)
}
defer xhttp.DrainBody(resp.Body)
if resp.StatusCode != http.StatusOK {
return toJSONError(ctx, errors.New(resp.Status))
}
......
......@@ -38,45 +38,45 @@ LDAP configuration is designed to be simple for the MinIO administrator. The ful
MinIO can be configured to find the groups of a user from AD/LDAP by specifying the **MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER** and **MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE** environment variables. When a user logs in via the STS API, the MinIO server queries the AD/LDAP server with the given search filter and extracts the given attribute from the search results. These values represent the groups that the user is a member of. On each access MinIO applies the IAM policies attached to these groups in MinIO.
MinIO sends LDAP credentials to LDAP server for validation. So we _strongly recommend_ to use MinIO with AD/LDAP server over TLS _only_. Using plain-text connection between MinIO and LDAP server means _credentials can be compromised_ by anyone listening to network traffic.
LDAP is configured via the following environment variables:
| Variable | Required? | Purpose |
|----------------------------------------------|-------------------------|-------------------------------------------------------------------------|
| **MINIO_IDENTITY_LDAP_SERVER_ADDR** | **YES** | AD/LDAP server address |
| **MINIO_IDENTITY_LDAP_USERNAME_FORMAT** | **YES** | Format of full username DN |
| **MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN** | **NO** | Base DN in AD/LDAP hierarchy to use in search requests |
| **MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER** | **NO** | Search filter to find groups of a user |
| **MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE** | **NO** | Attribute of search results to use as group name |
| **MINIO_IDENTITY_LDAP_STS_EXPIRY** | **NO** (default: "1h") | STS credentials validity duration |
| **MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY** | **NO** (default: "off") | Set this to 'on', to disable client verification of server certificates |
| **MINIO_IDENTITY_LDAP_SERVER_INSECURE** | **NO** (default: "off") | Set this to 'on', to allow plain text connection to LDAP/AD Server (only for testing) |
```
$ mc admin config set myminio/ identity_ldap --env
KEY:
identity_ldap enable LDAP SSO support
ARGS:
MINIO_IDENTITY_LDAP_SERVER_ADDR* (address) AD/LDAP server address e.g. "myldapserver.com:636"
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_USERNAME_SEARCH_FILTER* (string) user search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"
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_USERNAME_SEARCH_BASE_DN (list) ";" separated list of username search DNs
MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE (string) search attribute for group name e.g. "cn"
MINIO_IDENTITY_LDAP_STS_EXPIRY (duration) temporary credentials validity duration in s,m,h,d. Default is "1h"
MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "off" (verify)
MINIO_IDENTITY_LDAP_SERVER_INSECURE (on|off) allow plain text connection to AD/LDAP server, defaults to "off"
MINIO_IDENTITY_LDAP_COMMENT (sentence) optionally add a comment to this setting
```
MinIO sends LDAP credentials to LDAP server for validation. So we _strongly recommend_ to use MinIO with AD/LDAP server over TLS _only_. Using plain-text connection between MinIO and LDAP server means _credentials can be compromised_ by anyone listening to network traffic.
If a self-signed certificate is being used, the certificate can be added to MinIO's certificates directory, so it can be trusted by the server. An example setup for development or experimentation:
``` shell
```shell
export MINIO_IDENTITY_LDAP_SERVER_ADDR=myldapserver.com:636
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT="uid={username},cn=accounts,dc=myldapserver,dc=com"
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT="uid=%s,cn=accounts,dc=myldapserver,dc=com"
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="dc=myldapserver,dc=com"
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(member={usernamedn})$)"
export MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE="cn"
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(memberUid=%s)$)"
export MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE=cn
export MINIO_IDENTITY_LDAP_STS_EXPIRY=60h
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY="on"
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
```
### Variable substitution in AD/LDAP configuration strings
In the configuration values described above, some values support runtime substitutions. The substitution syntax is simply `${variable}` - this substring is replaced with the (string) value of `variable`. The following substitutions will be available:
| Variable | Example Runtime Value | Description |
|--------------|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| *username* | "james" | The AD/LDAP username of a user. |
| *usernamedn* | "uid=james,cn=accounts,dc=myldapserver,dc=com" | The AD/LDAP username DN of a user. This is constructed from the AD/LDAP user DN format string provided to the server and the actual AD/LDAP username. |
The **MINIO_IDENTITY_LDAP_USERNAME_FORMAT** environment variable supports substitution of the *username* variable only.
The **MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER** and **MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN** environment variables support substitution of the *username* and *usernamedn* variables only.
`%s` is replaced with *username* automatically for construction bind_dn, search_filter and group_search_filter.
### Notes on configuring with Microsoft Active Directory (AD)
......@@ -102,19 +102,19 @@ member: CN=John,CN=Users,DC=minioad,DC=local
...
```
The lines with "..." represent skipped content not shown here from brevity. Based on the output above, we see that the username format variable looks like `cn={username},cn=users,dc=minioad,dc=local`.
The lines with "..." represent skipped content not shown here from brevity. Based on the output above, we see that the username format variable looks like `cn=%s,cn=users,dc=minioad,dc=local`.
The group search filter looks like `(&(objectclass=group)(member={usernamedn}))` and the group name attribute is clearly `cn`.
The group search filter looks like `(&(objectclass=group)(memberUid=%s))` and the group name attribute is clearly `cn`.
Thus the key configuration parameters look like:
```
MINIO_IDENTITY_LDAP_SERVER_ADDR='my.ldap-active-dir-server.com:636'
MINIO_IDENTITY_LDAP_USERNAME_FORMAT='cn={username},cn=users,dc=minioad,dc=local'
MINIO_IDENTITY_LDAP_USERNAME_FORMAT='cn=%s,ou=Users,ou=BUS1,ou=LOB,dc=somedomain,dc=com'
MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN='dc=minioad,dc=local'
MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER='(&(objectclass=group)(member={usernamedn}))'
MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER='(&(objectclass=group)(member=%s))'
MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE='cn'
MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY="on"
MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
```
## Managing User/Group Access Policy
......@@ -212,13 +212,15 @@ http://minio.cluster:9000?Action=AssumeRoleWithLDAPIdentity&LDAPUsername=foouser
```
## Testing
With multiple OU heirarchies for users, and multiple group search base DN's.
```
$ export MINIO_ACCESS_KEY=minio
$ export MINIO_SECRET_KEY=minio123
$ export MINIO_IDENTITY_LDAP_SERVER_ADDR='my.ldap-active-dir-server.com:636'
$ export MINIO_IDENTITY_LDAP_USERNAME_FORMAT='cn={username},cn=users,dc=minioad,dc=local'
$ export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN='dc=minioad,dc=local'
$ export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER='(&(objectclass=group)(member={usernamedn}))'
$ 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_GROUP_SEARCH_BASE_DN='dc=minioad,dc=local;dc=somedomain,dc=com'
$ export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER='(&(objectclass=group)(member=%s))'
$ export MINIO_IDENTITY_LDAP_GROUP_NAME_ATTRIBUTE='cn'
$ minio server ~/test
```
......
checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019", "-SA1008", "-U1000"]
checks = ["all", "-ST1005", "-ST1000", "-SA4000", "-SA9004", "-SA1019", "-SA1008", "-U1000", "-ST1016"]