Added flag for operator only features (#144)
Added flag to only enable operator endpoints / links in mcs
This commit is contained in:
29
pkg/acl/config.go
Normal file
29
pkg/acl/config.go
Normal 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
21
pkg/acl/const.go
Normal 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"
|
||||||
|
)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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}>
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user