Skip to content

Commit 6e6aab5

Browse files
authored
Replace aws:username, jwt: and ldap: policy variables in session policies (#1828)
* Replace username variable in session policies Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
1 parent dc5b196 commit 6e6aab5

File tree

8 files changed

+348
-17
lines changed

8 files changed

+348
-17
lines changed

integration/login_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package integration
1818

1919
import (
20+
"bytes"
2021
"encoding/json"
22+
"fmt"
2123
"io/ioutil"
2224
"log"
2325
"net/http"
@@ -70,3 +72,66 @@ func TestLoginStrategy(t *testing.T) {
7072
}
7173

7274
}
75+
76+
func TestLogout(t *testing.T) {
77+
78+
assert := assert.New(t)
79+
80+
// image for now:
81+
// minio: 9000
82+
// console: 9090
83+
84+
client := &http.Client{
85+
Timeout: 2 * time.Second,
86+
}
87+
requestData := map[string]string{
88+
"accessKey": "minioadmin",
89+
"secretKey": "minioadmin",
90+
}
91+
92+
requestDataJSON, _ := json.Marshal(requestData)
93+
94+
requestDataBody := bytes.NewReader(requestDataJSON)
95+
96+
request, err := http.NewRequest("POST", "http://localhost:9090/api/v1/login", requestDataBody)
97+
if err != nil {
98+
log.Println(err)
99+
return
100+
}
101+
102+
request.Header.Add("Content-Type", "application/json")
103+
104+
response, err := client.Do(request)
105+
106+
assert.NotNil(response, "Login response is nil")
107+
assert.Nil(err, "Login errored out")
108+
109+
var loginToken string
110+
111+
for _, cookie := range response.Cookies() {
112+
if cookie.Name == "token" {
113+
loginToken = cookie.Value
114+
break
115+
}
116+
}
117+
118+
if loginToken == "" {
119+
log.Println("authentication token not found in cookies response")
120+
return
121+
}
122+
123+
request, err = http.NewRequest("POST", "http://localhost:9090/api/v1/logout", requestDataBody)
124+
if err != nil {
125+
log.Println(err)
126+
return
127+
}
128+
request.Header.Add("Cookie", fmt.Sprintf("token=%s", loginToken))
129+
request.Header.Add("Content-Type", "application/json")
130+
131+
response, err = client.Do(request)
132+
133+
assert.NotNil(response, "Logout response is nil")
134+
assert.Nil(err, "Logout errored out")
135+
assert.Equal(response.StatusCode, 200)
136+
137+
}

integration/tiers_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 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 integration
18+
19+
import (
20+
"fmt"
21+
"log"
22+
"net/http"
23+
"testing"
24+
"time"
25+
26+
"github.com/stretchr/testify/assert"
27+
)
28+
29+
func TestTiersList(t *testing.T) {
30+
31+
assert := assert.New(t)
32+
33+
// image for now:
34+
// minio: 9000
35+
// console: 9090
36+
37+
client := &http.Client{
38+
Timeout: 2 * time.Second,
39+
}
40+
41+
request, err := http.NewRequest("GET", "http://localhost:9090/api/v1/admin/tiers", nil)
42+
if err != nil {
43+
log.Println(err)
44+
return
45+
}
46+
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
47+
request.Header.Add("Content-Type", "application/json")
48+
49+
response, err := client.Do(request)
50+
51+
assert.NotNil(response, "Tiers List response is nil")
52+
assert.Nil(err, "Tiers List errored out")
53+
assert.Equal(response.StatusCode, 200)
54+
55+
}

restapi/policy/policies.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 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 policy
18+
19+
import (
20+
"bytes"
21+
"encoding/json"
22+
"fmt"
23+
24+
"github.com/minio/madmin-go"
25+
)
26+
27+
// ReplacePolicyVariables replaces known variables from policies with known values
28+
func ReplacePolicyVariables(claims map[string]interface{}, accountInfo *madmin.AccountInfo) json.RawMessage {
29+
// AWS Variables
30+
rawPolicy := bytes.ReplaceAll(accountInfo.Policy, []byte("${aws:username}"), []byte(accountInfo.AccountName))
31+
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${aws:userid}"), []byte(accountInfo.AccountName))
32+
// JWT Variables
33+
rawPolicy = replaceJwtVariables(rawPolicy, claims)
34+
// LDAP Variables
35+
rawPolicy = replaceLDAPVariables(rawPolicy, claims)
36+
return rawPolicy
37+
}
38+
39+
func replaceJwtVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
40+
// list of valid JWT fields we will replace in policy if they are in the response
41+
jwtFields := []string{
42+
"sub",
43+
"iss",
44+
"aud",
45+
"jti",
46+
"upn",
47+
"name",
48+
"groups",
49+
"given_name",
50+
"family_name",
51+
"middle_name",
52+
"nickname",
53+
"preferred_username",
54+
"profile",
55+
"picture",
56+
"website",
57+
"email",
58+
"gender",
59+
"birthdate",
60+
"phone_number",
61+
"address",
62+
"scope",
63+
"client_id",
64+
}
65+
// check which fields are in the claims and replace as variable by casting the value to string
66+
for _, field := range jwtFields {
67+
if val, ok := claims[field]; ok {
68+
variable := fmt.Sprintf("${jwt:%s}", field)
69+
fmt.Println("found", variable)
70+
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte(variable), []byte(fmt.Sprintf("%v", val)))
71+
}
72+
}
73+
return rawPolicy
74+
}
75+
76+
// ReplacePolicyVariables replaces known variables from policies with known values
77+
func replaceLDAPVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
78+
// replace ${ldap:user}
79+
if val, ok := claims["ldapUser"]; ok {
80+
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:user}"), []byte(fmt.Sprintf("%v", val)))
81+
}
82+
// replace ${ldap:username}
83+
if val, ok := claims["ldapUsername"]; ok {
84+
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:username}"), []byte(fmt.Sprintf("%v", val)))
85+
}
86+
return rawPolicy
87+
}

restapi/policy/policies_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 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 policy
18+
19+
import (
20+
"bytes"
21+
"reflect"
22+
"testing"
23+
24+
"github.com/minio/madmin-go"
25+
minioIAMPolicy "github.com/minio/pkg/iam/policy"
26+
)
27+
28+
func TestReplacePolicyVariables(t *testing.T) {
29+
type args struct {
30+
claims map[string]interface{}
31+
accountInfo *madmin.AccountInfo
32+
}
33+
tests := []struct {
34+
name string
35+
args args
36+
want string
37+
wantErr bool
38+
}{
39+
{
40+
name: "Bad Policy",
41+
args: args{
42+
claims: nil,
43+
accountInfo: &madmin.AccountInfo{
44+
AccountName: "test",
45+
Server: madmin.BackendInfo{},
46+
Policy: []byte(""),
47+
Buckets: nil,
48+
},
49+
},
50+
want: "",
51+
wantErr: true,
52+
},
53+
{
54+
name: "Replace basic AWS",
55+
args: args{
56+
claims: nil,
57+
accountInfo: &madmin.AccountInfo{
58+
AccountName: "test",
59+
Server: madmin.BackendInfo{},
60+
Policy: []byte(`{
61+
"Version": "2012-10-17",
62+
"Statement": [
63+
{
64+
"Effect": "Allow",
65+
"Action": [
66+
"s3:ListBucket"
67+
],
68+
"Resource": [
69+
"arn:aws:s3:::${aws:username}",
70+
"arn:aws:s3:::${aws:userid}"
71+
]
72+
}
73+
]
74+
}`),
75+
Buckets: nil,
76+
},
77+
},
78+
want: `{
79+
"Version": "2012-10-17",
80+
"Statement": [
81+
{
82+
"Effect": "Allow",
83+
"Action": [
84+
"s3:ListBucket"
85+
],
86+
"Resource": [
87+
"arn:aws:s3:::test",
88+
"arn:aws:s3:::test"
89+
]
90+
}
91+
]
92+
}`,
93+
wantErr: false,
94+
},
95+
}
96+
for _, tt := range tests {
97+
t.Run(tt.name, func(t *testing.T) {
98+
got := ReplacePolicyVariables(tt.args.claims, tt.args.accountInfo)
99+
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(got))
100+
if (err != nil) != tt.wantErr {
101+
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
102+
}
103+
wantPolicy, err := minioIAMPolicy.ParseConfig(bytes.NewReader([]byte(tt.want)))
104+
if (err != nil) != tt.wantErr {
105+
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
106+
}
107+
if !reflect.DeepEqual(policy, wantPolicy) {
108+
t.Errorf("ReplacePolicyVariables() = %s, want %v", got, tt.want)
109+
}
110+
})
111+
}
112+
}

restapi/user_login.go

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
package restapi
1818

1919
import (
20-
"bytes"
2120
"context"
2221
"net/http"
2322

24-
"github.com/minio/minio-go/v7/pkg/credentials"
23+
"github.com/minio/madmin-go"
2524

26-
iampolicy "github.com/minio/pkg/iam/policy"
25+
"github.com/minio/minio-go/v7/pkg/credentials"
2726

2827
"github.com/go-openapi/runtime"
2928
"github.com/go-openapi/runtime/middleware"
@@ -88,15 +87,13 @@ func login(credentials ConsoleCredentialsI, sessionFeatures *auth.SessionFeature
8887
return &token, nil
8988
}
9089

91-
// getAccountPolicy will return the associated policy of the current account
92-
func getAccountPolicy(ctx context.Context, client MinioAdmin) (*iampolicy.Policy, error) {
93-
// Obtain the current policy assigned to this user
94-
// necessary for generating the list of allowed endpoints
90+
// getAccountInfo will return the current user information
91+
func getAccountInfo(ctx context.Context, client MinioAdmin) (*madmin.AccountInfo, error) {
9592
accountInfo, err := client.AccountInfo(ctx)
9693
if err != nil {
9794
return nil, err
9895
}
99-
return iampolicy.ParseConfig(bytes.NewReader(accountInfo.Policy))
96+
return &accountInfo, nil
10097
}
10198

10299
// getConsoleCredentials will return ConsoleCredentials interface

0 commit comments

Comments
 (0)