Skip to content

Commit 4382111

Browse files
authored
LDAP authentication support for MCS (#114)
This PR adds ldap authentication support for mcs based on https://github.com/minio/minio/blob/master/docs/sts/ldap.md How to test: ``` $ docker run --rm -p 389:389 -p 636:636 --name my-openldap-container --detach osixia/openldap:1.3.0 ``` Run the `billy.ldif` file using `ldapadd` command to create a new user and assign it to a group. ``` $ cat > billy.ldif << EOF dn: uid=billy,dc=example,dc=org uid: billy cn: billy sn: 3 objectClass: top objectClass: posixAccount objectClass: inetOrgPerson loginShell: /bin/bash homeDirectory: /home/billy uidNumber: 14583102 gidNumber: 14564100 userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A mail: [email protected] gecos: Billy User dn: ou=groups,dc=example,dc=org objectclass:organizationalunit ou: groups description: generic groups branch of s3::*) dn: cn=mcsAdmin,ou=groups,dc=example,dc=org objectClass: top objectClass: posixGroup gidNumber: 678 dn: cn=mcsAdmin,ou=groups,dc=example,dc=org changetype: modify add: memberuid memberuid: billy EOF $ docker cp billy.ldif my-openldap-container:/container/service/slapd/assets/test/billy.ldif $ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost -ZZ ``` Query the ldap server to check the user billy was created correctly and got assigned to the mcsAdmin group, you should get a list containing ldap users and groups. ``` $ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin ``` Query the ldap server again, this time filtering only for the user `billy`, you should see only 1 record. ``` $ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b uid=billy,dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin ``` Change the password for user billy Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password` ``` $ docker exec -it my-openldap-container /bin/bash ldappasswd -H ldap://localhost -x -D "cn=admin,dc=example,dc=org" -W -S "uid=billy,dc=example,dc=org" New password: Re-enter new password: Enter LDAP Password: ``` Add the mcsAdmin policy to user billy on MinIO ``` $ cat > mcsAdmin.json << EOF { "Version": "2012-10-17", "Statement": [ { "Action": [ "admin:*" ], "Effect": "Allow", "Sid": "" }, { "Action": [ "s3:*" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::*" ], "Sid": "" } ] } EOF $ mc admin policy add myminio mcsAdmin mcsAdmin.json $ mc admin policy set myminio mcsAdmin user=billy ``` Run MinIO ``` export MINIO_ACCESS_KEY=minio export MINIO_SECRET_KEY=minio123 export MINIO_IDENTITY_LDAP_SERVER_ADDR='localhost:389' export MINIO_IDENTITY_LDAP_USERNAME_FORMAT='uid=%s,dc=example,dc=org' export MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER='(|(objectclass=posixAccount)(uid=%s))' export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on export MINIO_IDENTITY_LDAP_SERVER_INSECURE=on ./minio server ~/Data ``` Run MCS ``` export MCS_ACCESS_KEY=minio export MCS_SECRET_KEY=minio123 ... export MCS_LDAP_ENABLED=on ./mcs server ```
1 parent 0f77a32 commit 4382111

File tree

7 files changed

+252
-17
lines changed

7 files changed

+252
-17
lines changed

DEVELOPMENT.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# LDAP authentication with MCS
2+
3+
## Setup
4+
5+
Run openLDAP with docker.
6+
7+
```
8+
$ docker run --rm -p 389:389 -p 636:636 --name my-openldap-container --detach osixia/openldap:1.3.0
9+
```
10+
11+
Run the `billy.ldif` file using `ldapadd` command to create a new user and assign it to a group.
12+
13+
```
14+
$ cat > billy.ldif << EOF
15+
# LDIF fragment to create group branch under root
16+
dn: uid=billy,dc=example,dc=org
17+
uid: billy
18+
cn: billy
19+
sn: 3
20+
objectClass: top
21+
objectClass: posixAccount
22+
objectClass: inetOrgPerson
23+
loginShell: /bin/bash
24+
homeDirectory: /home/billy
25+
uidNumber: 14583102
26+
gidNumber: 14564100
27+
userPassword: {SSHA}j3lBh1Seqe4rqF1+NuWmjhvtAni1JC5A
28+
29+
gecos: Billy User
30+
# Create base group
31+
dn: ou=groups,dc=example,dc=org
32+
objectclass:organizationalunit
33+
ou: groups
34+
description: generic groups branch
35+
# create mcsAdmin group (this already exists on minio and have a policy of s3::*)
36+
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
37+
objectClass: top
38+
objectClass: posixGroup
39+
gidNumber: 678
40+
# Assing group to new user
41+
dn: cn=mcsAdmin,ou=groups,dc=example,dc=org
42+
changetype: modify
43+
add: memberuid
44+
memberuid: billy
45+
EOF
46+
47+
$ docker cp billy.ldif my-openldap-container:/container/service/slapd/assets/test/billy.ldif
48+
$ docker exec my-openldap-container ldapadd -x -D "cn=admin,dc=example,dc=org" -w admin -f /container/service/slapd/assets/test/billy.ldif -H ldap://localhost -ZZ
49+
```
50+
51+
Query the ldap server to check the user billy was created correctly and got assigned to the mcsAdmin group, you should get a list
52+
containing ldap users and groups.
53+
54+
```
55+
$ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
56+
```
57+
58+
Query the ldap server again, this time filtering only for the user `billy`, you should see only 1 record.
59+
60+
```
61+
$ docker exec my-openldap-container ldapsearch -x -H ldap://localhost -b uid=billy,dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
62+
```
63+
64+
### Change the password for user billy
65+
66+
Set the new password for `billy` to `minio123` and enter `admin` as the default `LDAP Password`
67+
68+
```
69+
$ docker exec -it my-openldap-container /bin/bash
70+
# ldappasswd -H ldap://localhost -x -D "cn=admin,dc=example,dc=org" -W -S "uid=billy,dc=example,dc=org"
71+
New password:
72+
Re-enter new password:
73+
Enter LDAP Password:
74+
```
75+
76+
### Add the mcsAdmin policy to user billy on MinIO
77+
```
78+
$ cat > mcsAdmin.json << EOF
79+
{
80+
"Version": "2012-10-17",
81+
"Statement": [
82+
{
83+
"Action": [
84+
"admin:*"
85+
],
86+
"Effect": "Allow",
87+
"Sid": ""
88+
},
89+
{
90+
"Action": [
91+
"s3:*"
92+
],
93+
"Effect": "Allow",
94+
"Resource": [
95+
"arn:aws:s3:::*"
96+
],
97+
"Sid": ""
98+
}
99+
]
100+
}
101+
EOF
102+
$ mc admin policy add myminio mcsAdmin mcsAdmin.json
103+
$ mc admin policy set myminio mcsAdmin user=billy
104+
```
105+
106+
## Run MinIO
107+
108+
```
109+
export MINIO_ACCESS_KEY=minio
110+
export MINIO_SECRET_KEY=minio123
111+
export MINIO_IDENTITY_LDAP_SERVER_ADDR='localhost:389'
112+
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT='uid=%s,dc=example,dc=org'
113+
export MINIO_IDENTITY_LDAP_USERNAME_SEARCH_FILTER='(|(objectclass=posixAccount)(uid=%s))'
114+
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
115+
export MINIO_IDENTITY_LDAP_SERVER_INSECURE=on
116+
./minio server ~/Data
117+
```
118+
119+
## Run MCS
120+
121+
```
122+
export MCS_ACCESS_KEY=minio
123+
export MCS_SECRET_KEY=minio123
124+
...
125+
export MCS_LDAP_ENABLED=on
126+
./mcs server
127+
```

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH
115115
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
116116
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
117117
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
118+
github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g=
118119
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
119120
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
120121
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=

pkg/auth/ldap.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2020 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package auth
18+
19+
import (
20+
"errors"
21+
"log"
22+
23+
"github.com/minio/minio-go/v6/pkg/credentials"
24+
)
25+
26+
var (
27+
errInvalidCredentials = errors.New("invalid Credentials")
28+
)
29+
30+
// GetMcsCredentialsFromLDAP authenticates the user against MinIO when the LDAP integration is enabled
31+
// if the authentication succeed *credentials.Credentials object is returned and we continue with the normal STSAssumeRole flow
32+
func GetMcsCredentialsFromLDAP(endpoint, ldapUser, ldapPassword string) (*credentials.Credentials, error) {
33+
creds, err := credentials.NewLDAPIdentity(endpoint, ldapUser, ldapPassword)
34+
if err != nil {
35+
log.Println("LDAP authentication error: ", err)
36+
return nil, errInvalidCredentials
37+
}
38+
return creds, nil
39+
}

pkg/auth/ldap/config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2020 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package ldap
18+
19+
import (
20+
"strings"
21+
22+
"github.com/minio/minio/pkg/env"
23+
)
24+
25+
func GetLDAPEnabled() bool {
26+
return strings.ToLower(env.Get(MCSLDAPEnabled, "off")) == "on"
27+
}

pkg/auth/ldap/const.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2020 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package ldap
18+
19+
const (
20+
// const for ldap configuration
21+
MCSLDAPEnabled = "MCS_LDAP_ENABLED"
22+
)

restapi/client.go

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ package restapi
1818

1919
import (
2020
"context"
21-
"errors"
2221
"fmt"
2322

23+
"errors"
24+
2425
mc "github.com/minio/mc/cmd"
2526
"github.com/minio/mc/pkg/probe"
2627
"github.com/minio/mcs/pkg/auth"
2728
xjwt "github.com/minio/mcs/pkg/auth/jwt"
29+
"github.com/minio/mcs/pkg/auth/ldap"
2830
"github.com/minio/minio-go/v6"
2931
"github.com/minio/minio-go/v6/pkg/credentials"
3032
)
@@ -153,26 +155,43 @@ func (s mcsSTSAssumeRole) IsExpired() bool {
153155
var STSClient = PrepareSTSClient()
154156

155157
func newMcsCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
156-
stsEndpoint := getMinIOServer()
157-
if stsEndpoint == "" {
158+
mcsEndpoint := getMinIOServer()
159+
if mcsEndpoint == "" {
158160
return nil, errors.New("STS endpoint cannot be empty")
159161
}
160162
if accessKey == "" || secretKey == "" {
161163
return nil, errors.New("AssumeRole credentials access/secretkey is mandatory")
162164
}
163-
opts := credentials.STSAssumeRoleOptions{
164-
AccessKey: accessKey,
165-
SecretKey: secretKey,
166-
Location: location,
167-
DurationSeconds: xjwt.GetMcsSTSAndJWTDurationInSeconds(),
168-
}
169-
stsAssumeRole := &credentials.STSAssumeRole{
170-
Client: STSClient,
171-
STSEndpoint: stsEndpoint,
172-
Options: opts,
165+
166+
// Future authentication methods can be added under this switch statement
167+
switch {
168+
// LDAP authentication for MCS
169+
case ldap.GetLDAPEnabled():
170+
{
171+
creds, err := auth.GetMcsCredentialsFromLDAP(mcsEndpoint, accessKey, secretKey)
172+
if err != nil {
173+
return nil, err
174+
}
175+
return creds, nil
176+
}
177+
// default authentication for MCS is via STS (Security Token Service) against MinIO
178+
default:
179+
{
180+
opts := credentials.STSAssumeRoleOptions{
181+
AccessKey: accessKey,
182+
SecretKey: secretKey,
183+
Location: location,
184+
DurationSeconds: xjwt.GetMcsSTSAndJWTDurationInSeconds(),
185+
}
186+
stsAssumeRole := &credentials.STSAssumeRole{
187+
Client: STSClient,
188+
STSEndpoint: mcsEndpoint,
189+
Options: opts,
190+
}
191+
mcsSTSWrapper := mcsSTSAssumeRole{stsAssumeRole: stsAssumeRole}
192+
return credentials.New(mcsSTSWrapper), nil
193+
}
173194
}
174-
mcsSTSWrapper := mcsSTSAssumeRole{stsAssumeRole: stsAssumeRole}
175-
return credentials.New(mcsSTSWrapper), nil
176195
}
177196

178197
// getMcsCredentialsFromJWT returns the *minioCredentials.Credentials associated to the

restapi/user_login.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,14 @@ func registerLoginHandlers(api *operations.McsAPI) {
4949
api.UserAPILoginHandler = user_api.LoginHandlerFunc(func(params user_api.LoginParams) middleware.Responder {
5050
loginResponse, err := getLoginResponse(params.Body)
5151
if err != nil {
52-
return user_api.NewLoginDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
52+
return user_api.NewLoginDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
5353
}
5454
return user_api.NewLoginCreated().WithPayload(loginResponse)
5555
})
5656
api.UserAPILoginOauth2AuthHandler = user_api.LoginOauth2AuthHandlerFunc(func(params user_api.LoginOauth2AuthParams) middleware.Responder {
5757
loginResponse, err := getLoginOauth2AuthResponse(params.Body)
5858
if err != nil {
59-
return user_api.NewLoginOauth2AuthDefault(500).WithPayload(&models.Error{Code: 500, Message: swag.String(err.Error())})
59+
return user_api.NewLoginOauth2AuthDefault(401).WithPayload(&models.Error{Code: 401, Message: swag.String(err.Error())})
6060
}
6161
return user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse)
6262
})

0 commit comments

Comments
 (0)