Skip to content

Commit ab83528

Browse files
authored
Add Service Account Policy restriction improvement (#1921)
1 parent 6485718 commit ab83528

18 files changed

+1479
-36
lines changed

integration/users_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,3 +854,68 @@ func TestUsersGroupsBulk(t *testing.T) {
854854
}
855855

856856
}
857+
858+
func Test_GetUserPolicyAPI(t *testing.T) {
859+
assert := assert.New(t)
860+
861+
// 1. Create an active user with valid policy
862+
var groups = []string{}
863+
var policies = []string{"readwrite"}
864+
addUserResponse, addUserError := AddUser(
865+
"getpolicyuser", "secretKey", groups, policies)
866+
if addUserError != nil {
867+
log.Println(addUserError)
868+
return
869+
}
870+
if addUserResponse != nil {
871+
fmt.Println("StatusCode:", addUserResponse.StatusCode)
872+
assert.Equal(
873+
201, addUserResponse.StatusCode, "Status Code is incorrect")
874+
}
875+
876+
type args struct {
877+
api string
878+
}
879+
tests := []struct {
880+
name string
881+
args args
882+
expectedStatus int
883+
expectedError error
884+
}{
885+
{
886+
name: "Get User Policies",
887+
args: args{
888+
api: "/user/policy",
889+
},
890+
expectedStatus: 200,
891+
expectedError: nil,
892+
},
893+
}
894+
895+
for _, tt := range tests {
896+
t.Run(tt.name, func(t *testing.T) {
897+
898+
client := &http.Client{
899+
Timeout: 3 * time.Second,
900+
}
901+
902+
request, err := http.NewRequest(
903+
"GET", fmt.Sprintf("http://localhost:9090/api/v1%s", tt.args.api), nil)
904+
if err != nil {
905+
log.Println(err)
906+
return
907+
}
908+
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
909+
request.Header.Add("Content-Type", "application/json")
910+
response, err := client.Do(request)
911+
if err != nil {
912+
log.Println(err)
913+
return
914+
}
915+
if response != nil {
916+
assert.Equal(tt.expectedStatus, response.StatusCode, tt.name+" Failed")
917+
}
918+
})
919+
}
920+
921+
}

portal-ui/src/screens/Console/Account/AddServiceAccountScreen.tsx

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import api from "../../../../src/common/api";
4444
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
4545
import { setErrorSnackMessage } from "../../../../src/actions";
4646
import SectionTitle from "../Common/SectionTitle";
47-
import { getRandomString } from "../../../screens/Console/Tenants/utils";
47+
import { getRandomString } from "../../../screens/Console/Tenants/utils";
48+
import PanelTitle from "../Common/PanelTitle/PanelTitle";
4849

4950
interface IAddServiceAccountProps {
5051
classes: any;
@@ -74,32 +75,33 @@ const AddServiceAccount = ({
7475
classes,
7576
setErrorSnackMessage,
7677
}: IAddServiceAccountProps) => {
77-
const [addSending, setAddSending] = useState<boolean>(false);
78-
const [policyDefinition, setPolicyDefinition] = useState<string>("");
78+
const [addSending, setAddSending] = useState<boolean>(false);
7979
const [accessKey, setAccessKey] = useState<string>(getRandomString(16));
8080
const [secretKey, setSecretKey] = useState<string>(getRandomString(32));
8181
const [isRestrictedByPolicy, setIsRestrictedByPolicy] =
8282
useState<boolean>(false);
8383
const [newServiceAccount, setNewServiceAccount] =
8484
useState<NewServiceAccount | null>(null);
85-
const [showPassword, setShowPassword] = useState<boolean>(false);
85+
const [showPassword, setShowPassword] = useState<boolean>(false);
86+
const [policyJSON, setPolicyJSON] = useState<string>("");
8687

8788
useEffect(() => {
8889
if (addSending) {
89-
api
90-
.invoke("POST", `/api/v1/service-account-credentials`, {
91-
policy: policyDefinition,
92-
accessKey: accessKey,
93-
secretKey: secretKey,
94-
})
95-
.then((res) => {
96-
setAddSending(false);
97-
setNewServiceAccount({
98-
accessKey: res.accessKey || "",
99-
secretKey: res.secretKey || "",
100-
url: res.url || "",
101-
});
102-
})
90+
api
91+
.invoke("POST", `/api/v1/service-account-credentials`, {
92+
policy: policyJSON,
93+
accessKey: accessKey,
94+
secretKey: secretKey,
95+
})
96+
.then((res) => {
97+
setAddSending(false);
98+
setNewServiceAccount({
99+
accessKey: res.accessKey || "",
100+
secretKey: res.secretKey || "",
101+
url: res.url || "",
102+
});
103+
})
104+
103105
.catch((err: ErrorResponseHandler) => {
104106
setAddSending(false);
105107
setErrorSnackMessage(err);
@@ -109,18 +111,30 @@ const AddServiceAccount = ({
109111
addSending,
110112
setAddSending,
111113
setErrorSnackMessage,
112-
policyDefinition,
114+
policyJSON,
113115
accessKey,
114116
secretKey,
115117
]);
116118

119+
useEffect(() => {
120+
if(isRestrictedByPolicy){
121+
api
122+
.invoke("GET", `/api/v1/user/policy`)
123+
.then((res: string) => {
124+
setPolicyJSON(JSON.stringify(JSON.parse(res), null, 4));
125+
126+
})
127+
}
128+
}, [isRestrictedByPolicy]);
129+
130+
117131
const addServiceAccount = (e: React.FormEvent) => {
118132
e.preventDefault();
119133
setAddSending(true);
120134
};
121135

122136
const resetForm = () => {
123-
setPolicyDefinition("");
137+
setPolicyJSON("");
124138
setNewServiceAccount(null);
125139
setAccessKey("");
126140
setSecretKey("");
@@ -260,13 +274,19 @@ const AddServiceAccount = ({
260274
xs={12}
261275
className={classes.codeMirrorContainer}
262276
>
277+
<div >
278+
<PanelTitle>Current User Policy - edit the JSON to remove permissions for this service account</PanelTitle>
279+
280+
</div>
281+
<Grid item xs={12} className={classes.formScrollable}>
263282
<CodeMirrorWrapper
264-
label={"Policy "}
265-
value={policyDefinition}
283+
value={policyJSON}
266284
onBeforeChange={(editor, data, value) => {
267-
setPolicyDefinition(value);
285+
setPolicyJSON(value);
268286
}}
287+
editorHeight={"350px"}
269288
/>
289+
</Grid>
270290
</Grid>
271291
)}
272292
</Grid>

restapi/admin_policies.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import (
3131
"github.com/minio/console/models"
3232
"github.com/minio/console/restapi/operations"
3333
iampolicy "github.com/minio/pkg/iam/policy"
34+
35+
policies "github.com/minio/console/restapi/policy"
3436
)
3537

3638
func registersPoliciesHandler(api *operations.ConsoleAPI) {
@@ -121,6 +123,14 @@ func registersPoliciesHandler(api *operations.ConsoleAPI) {
121123
}
122124
return policyApi.NewListGroupsForPolicyOK().WithPayload(policyGroupsResponse)
123125
})
126+
// Gets policies for currently logged in user
127+
api.PolicyGetUserPolicyHandler = policyApi.GetUserPolicyHandlerFunc(func(params policyApi.GetUserPolicyParams, session *models.Principal) middleware.Responder {
128+
userPolicyResponse, err := getUserPolicyResponse(session)
129+
if err != nil {
130+
return policyApi.NewGetUserPolicyDefault(int(err.Code)).WithPayload(err)
131+
}
132+
return policyApi.NewGetUserPolicyOK().WithPayload(userPolicyResponse)
133+
})
124134
}
125135

126136
func getListAccessRulesWithBucketResponse(session *models.Principal, params bucketApi.ListAccessRulesWithBucketParams) (*models.ListAccessRulesResponse, *models.Error) {
@@ -322,16 +332,47 @@ func getListUsersForPolicyResponse(session *models.Principal, params policyApi.L
322332
return filteredUsers, nil
323333
}
324334

335+
func getUserPolicyResponse(session *models.Principal) (string, *models.Error) {
336+
ctx, cancel := context.WithCancel(context.Background())
337+
defer cancel()
338+
// serialize output
339+
if session == nil {
340+
return "nil", ErrorWithContext(ctx, ErrPolicyNotFound)
341+
}
342+
tokenClaims, _ := getClaimsFromToken(session.STSSessionToken)
343+
344+
// initialize admin client
345+
mAdminClient, err := NewMinioAdminClient(&models.Principal{
346+
STSAccessKeyID: session.STSAccessKeyID,
347+
STSSecretAccessKey: session.STSSecretAccessKey,
348+
STSSessionToken: session.STSSessionToken,
349+
})
350+
if err != nil {
351+
return "nil", ErrorWithContext(ctx, err)
352+
}
353+
userAdminClient := AdminClient{Client: mAdminClient}
354+
// Obtain the current policy assigned to this user
355+
// necessary for generating the list of allowed endpoints
356+
accountInfo, err := getAccountInfo(ctx, userAdminClient)
357+
if err != nil {
358+
return "nil", ErrorWithContext(ctx, err)
359+
360+
}
361+
rawPolicy := policies.ReplacePolicyVariables(tokenClaims, accountInfo)
362+
363+
return string(rawPolicy), nil
364+
}
365+
325366
func getListGroupsForPolicyResponse(session *models.Principal, params policyApi.ListGroupsForPolicyParams) ([]string, *models.Error) {
326367
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
327368
defer cancel()
328-
policy := params.Policy
329369
mAdmin, err := NewMinioAdminClient(session)
330370
if err != nil {
331371
return nil, ErrorWithContext(ctx, err)
332372
}
333373
// create a minioClient interface implementation
334374
// defining the client to be used
375+
policy := params.Policy
335376
adminClient := AdminClient{Client: mAdmin}
336377
policies, err := listPolicies(ctx, adminClient)
337378
if err != nil {

restapi/embedded_spec.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)