Added flag for operator only features (#144)

Added flag to only enable operator endpoints / links in mcs
This commit is contained in:
Alex
2020-05-26 21:35:44 -05:00
committed by GitHub
parent fa068b6d4a
commit be5cd7f148
8 changed files with 239 additions and 124 deletions

29
pkg/acl/config.go Normal file
View File

@@ -0,0 +1,29 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 acl
import (
"strings"
"github.com/minio/minio/pkg/env"
)
// GetOperatorOnly gets mcs operator mode status set on env variable
//or default one
func GetOperatorOnly() string {
return strings.ToLower(env.Get(McsOperatorOnly, "off"))
}

21
pkg/acl/const.go Normal file
View File

@@ -0,0 +1,21 @@
// This file is part of MinIO Console Server
// Copyright (c) 2020 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 acl
const (
McsOperatorOnly = "MCS_OPERATOR_ONLY"
)

View File

@@ -16,7 +16,9 @@
package acl package acl
import iampolicy "github.com/minio/minio/pkg/iam/policy" import (
iampolicy "github.com/minio/minio/pkg/iam/policy"
)
// endpoints definition // endpoints definition
var ( var (
@@ -221,11 +223,18 @@ var endpointRules = map[string]ConfigurationActionSet{
buckets: bucketsActionSet, buckets: bucketsActionSet,
bucketsDetail: bucketsActionSet, bucketsDetail: bucketsActionSet,
serviceAccounts: serviceAccountsActionSet, serviceAccounts: serviceAccountsActionSet,
clusters: clustersActionSet,
clustersDetail: clustersActionSet,
heal: healActionSet, heal: healActionSet,
} }
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
var operatorRules = map[string]ConfigurationActionSet{
clusters: clustersActionSet,
clustersDetail: clustersActionSet,
}
// operatorOnly ENV variable
var operatorOnly = GetOperatorOnly()
// GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format // GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format
// //
// ie: // ie:
@@ -275,13 +284,19 @@ func actionsStringToActionSet(actions []string) iampolicy.ActionSet {
// GetAuthorizedEndpoints return a list of allowed endpoint based on a provided *iampolicy.Policy // GetAuthorizedEndpoints return a list of allowed endpoint based on a provided *iampolicy.Policy
// ie: pages the user should have access based on his current privileges // ie: pages the user should have access based on his current privileges
func GetAuthorizedEndpoints(actions []string) []string { func GetAuthorizedEndpoints(actions []string) []string {
rangeTake := endpointRules
if operatorOnly == "on" {
rangeTake = operatorRules
}
if len(actions) == 0 { if len(actions) == 0 {
return []string{} return []string{}
} }
// Prepare new ActionSet structure that will hold all the user actions // Prepare new ActionSet structure that will hold all the user actions
userAllowedAction := actionsStringToActionSet(actions) userAllowedAction := actionsStringToActionSet(actions)
allowedEndpoints := []string{} allowedEndpoints := []string{}
for endpoint, rules := range endpointRules { for endpoint, rules := range rangeTake {
// check if user policy matches s3:* or admin:* typesIntersection // check if user policy matches s3:* or admin:* typesIntersection
endpointActionTypes := rules.actionTypes endpointActionTypes := rules.actionTypes
typesIntersection := endpointActionTypes.Intersection(userAllowedAction) typesIntersection := endpointActionTypes.Intersection(userAllowedAction)

View File

@@ -23,21 +23,34 @@ import (
iampolicy "github.com/minio/minio/pkg/iam/policy" iampolicy "github.com/minio/minio/pkg/iam/policy"
) )
func TestGetAuthorizedEndpoints(t *testing.T) { type args struct {
type args struct { actions []string
actions []string }
type endpoint struct {
name string
args args
want int
}
func validateEndpoints(t *testing.T, configs []endpoint) {
for _, tt := range configs {
t.Run(tt.name, func(t *testing.T) {
if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) {
t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want)
}
})
} }
tests := []struct { }
name string
args args func TestGetAuthorizedEndpoints(t *testing.T) {
want int tests := []endpoint{
}{
{ {
name: "dashboard endpoint", name: "dashboard endpoint",
args: args{ args: args{
[]string{"admin:ServerInfo"}, []string{"admin:ServerInfo"},
}, },
want: 4, want: 2,
}, },
{ {
name: "policies endpoint", name: "policies endpoint",
@@ -50,7 +63,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:ListUserPolicies", "admin:ListUserPolicies",
}, },
}, },
want: 4, want: 2,
}, },
{ {
name: "all admin endpoints", name: "all admin endpoints",
@@ -59,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:*", "admin:*",
}, },
}, },
want: 13, want: 11,
}, },
{ {
name: "all s3 endpoints", name: "all s3 endpoints",
@@ -68,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*", "s3:*",
}, },
}, },
want: 6, want: 4,
}, },
{ {
name: "all admin and s3 endpoints", name: "all admin and s3 endpoints",
@@ -78,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*", "s3:*",
}, },
}, },
want: 16, want: 14,
}, },
{ {
name: "no endpoints", name: "no endpoints",
@@ -88,13 +101,52 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
want: 0, want: 0,
}, },
} }
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { validateEndpoints(t, tests)
if got := GetAuthorizedEndpoints(tt.args.actions); !reflect.DeepEqual(len(got), tt.want) { }
t.Errorf("GetAuthorizedEndpoints() = %v, want %v", len(got), tt.want)
} func TestOperatorOnlyEndpoints(t *testing.T) {
}) operatorOnly = "on"
tests := []endpoint{
{
name: "Operator Only - all admin endpoints",
args: args{
[]string{
"admin:*",
},
},
want: 2,
},
{
name: "Operator Only - all s3 endpoints",
args: args{
[]string{
"s3:*",
},
},
want: 2,
},
{
name: "Operator Only - all admin and s3 endpoints",
args: args{
[]string{
"admin:*",
"s3:*",
},
},
want: 2,
},
{
name: "Operator Only - no endpoints",
args: args{
[]string{},
},
want: 0,
},
} }
validateEndpoints(t, tests)
} }
func TestGetActionsStringFromPolicy(t *testing.T) { func TestGetActionsStringFromPolicy(t *testing.T) {

File diff suppressed because one or more lines are too long

View File

@@ -192,7 +192,6 @@ const ListClusters = ({ classes }: IClustersList) => {
</Button> </Button>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
REMOVE THIS:: <Link to={"/clusters/demoCluster"}>Test</Link>
<br /> <br />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>

View File

@@ -145,7 +145,6 @@ class Menu extends React.Component<MenuProps> {
to: "/service-accounts", to: "/service-accounts",
name: "Service Accounts", name: "Service Accounts",
icon: <RoomServiceIcon />, icon: <RoomServiceIcon />,
forceDisplay: true,
}, },
{ {
type: "item", type: "item",
@@ -244,7 +243,6 @@ class Menu extends React.Component<MenuProps> {
to: "/clusters", to: "/clusters",
name: "Clusters", name: "Clusters",
icon: <StorageIcon />, icon: <StorageIcon />,
forceDisplay: true,
}, },
{ {
type: "divider", type: "divider",

View File

@@ -53,6 +53,7 @@ func getSessionResponse(sessionID string) (*models.SessionResponse, error) {
log.Println("error getting claims from JWT", err) log.Println("error getting claims from JWT", err)
return nil, errorGenericInvalidSession return nil, errorGenericInvalidSession
} }
sessionResp := &models.SessionResponse{ sessionResp := &models.SessionResponse{
Pages: acl.GetAuthorizedEndpoints(claims.Actions), Pages: acl.GetAuthorizedEndpoints(claims.Actions),
Status: models.SessionResponseStatusOk, Status: models.SessionResponseStatusOk,