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>
This commit is contained in:
87
restapi/policy/policies.go
Normal file
87
restapi/policy/policies.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
// ReplacePolicyVariables replaces known variables from policies with known values
|
||||
func ReplacePolicyVariables(claims map[string]interface{}, accountInfo *madmin.AccountInfo) json.RawMessage {
|
||||
// AWS Variables
|
||||
rawPolicy := bytes.ReplaceAll(accountInfo.Policy, []byte("${aws:username}"), []byte(accountInfo.AccountName))
|
||||
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${aws:userid}"), []byte(accountInfo.AccountName))
|
||||
// JWT Variables
|
||||
rawPolicy = replaceJwtVariables(rawPolicy, claims)
|
||||
// LDAP Variables
|
||||
rawPolicy = replaceLDAPVariables(rawPolicy, claims)
|
||||
return rawPolicy
|
||||
}
|
||||
|
||||
func replaceJwtVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
|
||||
// list of valid JWT fields we will replace in policy if they are in the response
|
||||
jwtFields := []string{
|
||||
"sub",
|
||||
"iss",
|
||||
"aud",
|
||||
"jti",
|
||||
"upn",
|
||||
"name",
|
||||
"groups",
|
||||
"given_name",
|
||||
"family_name",
|
||||
"middle_name",
|
||||
"nickname",
|
||||
"preferred_username",
|
||||
"profile",
|
||||
"picture",
|
||||
"website",
|
||||
"email",
|
||||
"gender",
|
||||
"birthdate",
|
||||
"phone_number",
|
||||
"address",
|
||||
"scope",
|
||||
"client_id",
|
||||
}
|
||||
// check which fields are in the claims and replace as variable by casting the value to string
|
||||
for _, field := range jwtFields {
|
||||
if val, ok := claims[field]; ok {
|
||||
variable := fmt.Sprintf("${jwt:%s}", field)
|
||||
fmt.Println("found", variable)
|
||||
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte(variable), []byte(fmt.Sprintf("%v", val)))
|
||||
}
|
||||
}
|
||||
return rawPolicy
|
||||
}
|
||||
|
||||
// ReplacePolicyVariables replaces known variables from policies with known values
|
||||
func replaceLDAPVariables(rawPolicy []byte, claims map[string]interface{}) json.RawMessage {
|
||||
// replace ${ldap:user}
|
||||
if val, ok := claims["ldapUser"]; ok {
|
||||
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:user}"), []byte(fmt.Sprintf("%v", val)))
|
||||
}
|
||||
// replace ${ldap:username}
|
||||
if val, ok := claims["ldapUsername"]; ok {
|
||||
rawPolicy = bytes.ReplaceAll(rawPolicy, []byte("${ldap:username}"), []byte(fmt.Sprintf("%v", val)))
|
||||
}
|
||||
return rawPolicy
|
||||
}
|
||||
112
restapi/policy/policies_test.go
Normal file
112
restapi/policy/policies_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package policy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/madmin-go"
|
||||
minioIAMPolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
func TestReplacePolicyVariables(t *testing.T) {
|
||||
type args struct {
|
||||
claims map[string]interface{}
|
||||
accountInfo *madmin.AccountInfo
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Bad Policy",
|
||||
args: args{
|
||||
claims: nil,
|
||||
accountInfo: &madmin.AccountInfo{
|
||||
AccountName: "test",
|
||||
Server: madmin.BackendInfo{},
|
||||
Policy: []byte(""),
|
||||
Buckets: nil,
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Replace basic AWS",
|
||||
args: args{
|
||||
claims: nil,
|
||||
accountInfo: &madmin.AccountInfo{
|
||||
AccountName: "test",
|
||||
Server: madmin.BackendInfo{},
|
||||
Policy: []byte(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::${aws:username}",
|
||||
"arn:aws:s3:::${aws:userid}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`),
|
||||
Buckets: nil,
|
||||
},
|
||||
},
|
||||
want: `{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::test",
|
||||
"arn:aws:s3:::test"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ReplacePolicyVariables(tt.args.claims, tt.args.accountInfo)
|
||||
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(got))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
wantPolicy, err := minioIAMPolicy.ParseConfig(bytes.NewReader([]byte(tt.want)))
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ReplacePolicyVariables() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if !reflect.DeepEqual(policy, wantPolicy) {
|
||||
t.Errorf("ReplacePolicyVariables() = %s, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,12 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/madmin-go"
|
||||
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
@@ -88,15 +87,13 @@ func login(credentials ConsoleCredentialsI, sessionFeatures *auth.SessionFeature
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// getAccountPolicy will return the associated policy of the current account
|
||||
func getAccountPolicy(ctx context.Context, client MinioAdmin) (*iampolicy.Policy, error) {
|
||||
// Obtain the current policy assigned to this user
|
||||
// necessary for generating the list of allowed endpoints
|
||||
// getAccountInfo will return the current user information
|
||||
func getAccountInfo(ctx context.Context, client MinioAdmin) (*madmin.AccountInfo, error) {
|
||||
accountInfo, err := client.AccountInfo(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iampolicy.ParseConfig(bytes.NewReader(accountInfo.Policy))
|
||||
return &accountInfo, nil
|
||||
}
|
||||
|
||||
// getConsoleCredentials will return ConsoleCredentials interface
|
||||
|
||||
@@ -146,7 +146,7 @@ func Test_validateUserAgainstIDP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getAccountPolicy(t *testing.T) {
|
||||
func Test_getAccountInfo(t *testing.T) {
|
||||
client := adminClientMock{}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -160,7 +160,7 @@ func Test_getAccountPolicy(t *testing.T) {
|
||||
mockFunc func()
|
||||
}{
|
||||
{
|
||||
name: "error getting account policy",
|
||||
name: "error getting account info",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
client: client,
|
||||
@@ -179,13 +179,15 @@ func Test_getAccountPolicy(t *testing.T) {
|
||||
if tt.mockFunc != nil {
|
||||
tt.mockFunc()
|
||||
}
|
||||
got, err := getAccountPolicy(tt.args.ctx, tt.args.client)
|
||||
got, err := getAccountInfo(tt.args.ctx, tt.args.client)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getAccountPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("getAccountInfo() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getAccountPolicy() got = %v, want %v", got, tt.want)
|
||||
if tt.want != nil {
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getAccountInfo() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
@@ -24,6 +25,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
policies "github.com/minio/console/restapi/policy"
|
||||
|
||||
jwtgo "github.com/golang-jwt/jwt/v4"
|
||||
"github.com/minio/pkg/bucket/policy/condition"
|
||||
|
||||
@@ -95,6 +98,7 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
if session == nil {
|
||||
return nil, prepareError(errorGenericInvalidSession)
|
||||
}
|
||||
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
|
||||
|
||||
// initialize admin client
|
||||
mAdminClient, err := NewMinioAdminClient(&models.Principal{
|
||||
@@ -108,7 +112,13 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
userAdminClient := AdminClient{Client: mAdminClient}
|
||||
// Obtain the current policy assigned to this user
|
||||
// necessary for generating the list of allowed endpoints
|
||||
policy, err := getAccountPolicy(ctx, userAdminClient)
|
||||
accountInfo, err := getAccountInfo(ctx, userAdminClient)
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errorGenericInvalidSession)
|
||||
|
||||
}
|
||||
rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo)
|
||||
policy, err := minioIAMPolicy.ParseConfig(bytes.NewReader(rawPolicy))
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errorGenericInvalidSession)
|
||||
}
|
||||
@@ -210,12 +220,12 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
|
||||
resourcePermissions[key] = resourceActions
|
||||
|
||||
}
|
||||
rawPolicy, err := json.Marshal(policy)
|
||||
serializedPolicy, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return nil, prepareError(err, errorGenericInvalidSession)
|
||||
}
|
||||
var sessionPolicy *models.IamPolicy
|
||||
err = json.Unmarshal(rawPolicy, &sessionPolicy)
|
||||
err = json.Unmarshal(serializedPolicy, &sessionPolicy)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
|
||||
errors.ServeError(w, req, errors.New(http.StatusUnauthorized, err.Error()))
|
||||
return
|
||||
}
|
||||
upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// upgrades the HTTP server connection to the WebSocket protocol.
|
||||
conn, err := upgrader.Upgrade(w, req, nil)
|
||||
|
||||
Reference in New Issue
Block a user