Reading policy as json string (#43)

addPolicy endpoint will read policies as json string, this to allow
s3 iam policy compatibility (uppercase in json attributes) and to be
consistent with other mcs apis, once https://github.com/minio/minio/pull/9181
is merged we can return a type struct{}

fix policies test to new refactor

goimports

more golint fixes
This commit is contained in:
Lenin Alevski
2020-04-06 19:10:10 -07:00
committed by GitHub
parent 3dac86d3ce
commit b390ce309a
9 changed files with 58 additions and 471 deletions

View File

@@ -34,12 +34,13 @@ import (
// swagger:model addPolicyRequest
type AddPolicyRequest struct {
// definition
Definition string `json:"definition,omitempty"`
// name
// Required: true
Name *string `json:"name"`
// policy
// Required: true
Policy *string `json:"policy"`
}
// Validate validates this add policy request
@@ -50,6 +51,10 @@ func (m *AddPolicyRequest) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validatePolicy(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -65,6 +70,15 @@ func (m *AddPolicyRequest) validateName(formats strfmt.Registry) error {
return nil
}
func (m *AddPolicyRequest) validatePolicy(formats strfmt.Registry) error {
if err := validate.Required("policy", "body", m.Policy); err != nil {
return err
}
return nil
}
// MarshalBinary interface implementation
func (m *AddPolicyRequest) MarshalBinary() ([]byte, error) {
if m == nil {

View File

@@ -23,9 +23,6 @@ package models
// Editing this file might prove futile when you re-run the swagger generate command
import (
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
@@ -38,49 +35,12 @@ type Policy struct {
// name
Name string `json:"name,omitempty"`
// statements
Statements []*Statement `json:"statements"`
// version
Version string `json:"version,omitempty"`
// policy
Policy string `json:"policy,omitempty"`
}
// Validate validates this policy
func (m *Policy) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateStatements(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *Policy) validateStatements(formats strfmt.Registry) error {
if swag.IsZero(m.Statements) { // not required
return nil
}
for i := 0; i < len(m.Statements); i++ {
if swag.IsZero(m.Statements[i]) { // not required
continue
}
if m.Statements[i] != nil {
if err := m.Statements[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("statements" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}

View File

@@ -81,7 +81,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
api
.invoke("POST", "/api/v1/policies", {
name: policyName,
definition: policyDefinition
policy: policyDefinition
})
.then(res => {
this.setState(
@@ -143,6 +143,7 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
)}
<Grid item xs={12}>
<TextField
defaultValue={policyEdit ? policyEdit.name : "" }
id="standard-basic"
fullWidth
label="Policy Name"
@@ -157,10 +158,9 @@ class AddPolicy extends React.Component<IAddPolicyProps, IAddPolicyState> {
<Grid item xs={12}>
<CodeMirror
className={classes.codeMirror}
value={policyEdit ? JSON.stringify(policyEdit, null, 4) : ""}
value={policyEdit ? JSON.stringify(JSON.parse(policyEdit.policy), null, 4) : ""}
options={{
mode: "javascript",
theme: "material",
lineNumbers: true
}}
onChange={(editor, data, value) => {

View File

@@ -1,209 +0,0 @@
// This file is part of MinIO Kubernetes Cloud
// 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/>.
import React from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import remove from "lodash/remove";
import {
Checkbox,
FormControlLabel,
FormGroup,
FormLabel,
Paper,
Radio,
RadioGroup
} from "@material-ui/core";
import { Statement } from "./types";
const styles = (theme: Theme) =>
createStyles({
root: {
flexGrow: 1
},
errorBlock: {
color: "red"
},
jsonPolicyEditor: {
minHeight: 400,
width: "100%"
},
codeMirror: {
fontSize: 14
},
paper: {
padding: theme.spacing(1),
textAlign: "center",
color: theme.palette.text.secondary
}
});
interface IPolicyBuilderProps {
classes: any;
policyDefinition: string;
}
interface IPolicyBuilderState {
policyString: string;
currentStatement: Statement;
statements: Statement[];
currentStatementWrite: boolean;
currentStatementRead: boolean;
}
class PolicyBuilder extends React.Component<
IPolicyBuilderProps,
IPolicyBuilderState
> {
state: IPolicyBuilderState = {
policyString: "",
statements: [],
currentStatement: {
effect: "",
actions: [],
resources: []
},
currentStatementWrite: false,
currentStatementRead: false
};
render() {
const { classes, policyDefinition } = this.props;
const {
currentStatement,
currentStatementWrite,
currentStatementRead
} = this.state;
console.log(currentStatement);
return (
<div className={classes.root}>
<Grid container spacing={1}>
<Grid container item xs={12} spacing={3}>
<React.Fragment>
<Grid item xs={4}>
<Paper className={classes.paper}>
<FormLabel component="legend">Effect</FormLabel>
<RadioGroup
aria-label="effect"
name="effect"
value={currentStatement.effect}
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
value: string
) => {
this.setState({
currentStatement: { ...currentStatement, effect: value }
});
}}
>
<FormControlLabel
value="Deny"
control={<Radio />}
label="Deny"
/>
<FormControlLabel
value="Allow"
control={<Radio />}
label="Allow"
/>
</RadioGroup>
</Paper>
</Grid>
<Grid item xs={4}>
<Paper className={classes.paper}>
<FormLabel component="legend">Actions</FormLabel>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={currentStatementRead}
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
const readActions = [
"s3:ListBucket",
"s3:GetObject",
"s3:GetBucketLocation"
];
let actions = currentStatement.actions;
if (checked) {
actions.push(...readActions);
} else {
actions = remove(actions, action =>
readActions.includes(action)
);
}
this.setState({
currentStatement: {
...currentStatement,
actions: actions
}
});
this.setState({ currentStatementRead: checked });
}}
name="read"
/>
}
label="Read Only"
/>
<FormControlLabel
control={
<Checkbox
checked={currentStatementWrite}
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
const writeActions = ["s3:PutObject"];
let actions = currentStatement.actions;
if (checked) {
actions.push(...writeActions);
} else {
actions = remove(actions, action =>
writeActions.includes(action)
);
}
this.setState({
currentStatement: {
...currentStatement,
actions: actions
}
});
this.setState({ currentStatementWrite: checked });
}}
name="write"
/>
}
label="Write Only"
/>
</FormGroup>
</Paper>
</Grid>
<Grid item xs={4}>
<Paper className={classes.paper}>
<FormLabel component="legend">Resources</FormLabel>
</Paper>
</Grid>
</React.Fragment>
</Grid>
</Grid>
</div>
);
}
}
export default withStyles(styles)(PolicyBuilder);

View File

@@ -14,16 +14,9 @@
// 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/>.
export interface Statement {
effect: string;
actions: string[];
resources: string[];
}
export interface Policy {
name: string;
version: string;
statements: Statement[];
policy: string;
}
export interface PolicyList {

View File

@@ -18,7 +18,6 @@ package restapi
import (
"context"
"encoding/json"
"log"
"github.com/go-openapi/errors"
@@ -74,39 +73,6 @@ func registersPoliciesHandler(api *operations.McsAPI) {
})
}
type rawStatement struct {
Action []string `json:"Action"`
Effect string `json:"Effect"`
Resource []string `json:"Resource"`
}
type rawPolicy struct {
Name string `json:"Name"`
Statement []*rawStatement `json:"Statement"`
Version string `json:"Version"`
}
// parseRawPolicy() converts from *rawPolicy to *models.Policy
// Iterates over the raw statements and copied them to models.policy
// this is need it until fixed from minio/minio side: https://github.com/minio/minio/issues/9171
func parseRawPolicy(rawPolicy *rawPolicy) *models.Policy {
var statements []*models.Statement
for _, rawStatement := range rawPolicy.Statement {
statement := &models.Statement{
Actions: rawStatement.Action,
Effect: rawStatement.Effect,
Resources: rawStatement.Resource,
}
statements = append(statements, statement)
}
policy := &models.Policy{
Name: rawPolicy.Name,
Version: rawPolicy.Version,
Statements: statements,
}
return policy
}
// listPolicies calls MinIO server to list all policy names present on the server.
// listPolicies() converts the map[string][]byte returned by client.listPolicies()
// to []*models.Policy by iterating over each key in policyRawMap and
@@ -118,13 +84,10 @@ func listPolicies(ctx context.Context, client MinioAdmin) ([]*models.Policy, err
return nil, err
}
for name, policyRaw := range policyRawMap {
var rawPolicy *rawPolicy
if err := json.Unmarshal(policyRaw, &rawPolicy); err != nil {
return nil, err
}
policy := parseRawPolicy(rawPolicy)
policy.Name = name
policies = append(policies, policy)
policies = append(policies, &models.Policy{
Name: name,
Policy: string(policyRaw),
})
}
return policies, nil
}
@@ -217,7 +180,7 @@ func getAddPolicyResponse(params *models.AddPolicyRequest) (*models.Policy, erro
// create a MinIO Admin Client interface implementation
// defining the client to be used
adminClient := adminClient{client: mAdmin}
policy, err := addPolicy(ctx, adminClient, *params.Name, params.Definition)
policy, err := addPolicy(ctx, adminClient, *params.Name, *params.Policy)
if err != nil {
log.Println("error adding policy")
return nil, err
@@ -226,21 +189,19 @@ func getAddPolicyResponse(params *models.AddPolicyRequest) (*models.Policy, erro
}
// policyInfo calls MinIO server to retrieve information of a canned policy.
// policyInfo() takes a policy name, obtains an []byte (represents a string in JSON format)
// from the MinIO server and then convert it to *models.Policy , in the future this will change
// policyInfo() takes a policy name, obtains the []byte (represents a string in JSON format)
// and return it as *models.Policy , in the future this will change
// to a Policy struct{} - https://github.com/minio/minio/issues/9171
func policyInfo(ctx context.Context, client MinioAdmin, name string) (*models.Policy, error) {
policyRaw, err := client.getPolicy(ctx, name)
if err != nil {
return nil, err
}
var rawPolicy *rawPolicy
if err := json.Unmarshal(policyRaw, &rawPolicy); err != nil {
return nil, err
policy := &models.Policy{
Name: name,
Policy: string(policyRaw),
}
policyObject := parseRawPolicy(rawPolicy)
policyObject.Name = name
return policyObject, nil
return policy, nil
}
// getPolicyInfoResponse performs policyInfo() and serializes it to the handler's output

View File

@@ -70,45 +70,16 @@ func TestListPolicies(t *testing.T) {
}
assertPoliciesMap := map[string]models.Policy{
"readonly": {
Name: "readonly",
Statements: []*models.Statement{
{
Actions: []string{"s3:GetBucketLocation", "s3:GetObject"},
Effect: "Allow",
Resources: []string{"arn:aws:s3:::*"},
},
},
Version: "2012-10-17",
Name: "readonly",
Policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetBucketLocation\",\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}",
},
"readwrite": {
Name: "readwrite",
Statements: []*models.Statement{
{
Actions: []string{"s3:*"},
Effect: "Allow",
Resources: []string{"arn:aws:s3:::*"},
},
},
Version: "2012-10-17",
Name: "readwrite",
Policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:*\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}",
},
"diagnostics": {
Name: "diagnostics",
Statements: []*models.Statement{
{
Actions: []string{
"admin:ServerInfo",
"admin:HardwareInfo",
"admin:TopLocksInfo",
"admin:PerfInfo",
"admin:Profiling",
"admin:ServerTrace",
"admin:ConsoleLog",
},
Effect: "Allow",
Resources: []string{"arn:aws:s3:::*"},
},
},
Version: "2012-10-17",
Name: "diagnostics",
Policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"admin:ServerInfo\",\"admin:HardwareInfo\",\"admin:TopLocksInfo\",\"admin:PerfInfo\",\"admin:Profiling\",\"admin:ServerTrace\",\"admin:ConsoleLog\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}",
},
}
// mock function response from listPolicies()
@@ -128,21 +99,10 @@ func TestListPolicies(t *testing.T) {
// as part of each Policy
for _, policy := range policiesList {
assertPolicy := assertPoliciesMap[policy.Name]
// Check if policy statement has the same length as in the assertPoliciesMap
assert.Equal(len(policy.Statements), len(assertPolicy.Statements))
// Check if policy name is the same as in the assertPoliciesMap
assert.Equal(policy.Name, assertPolicy.Name)
// Check if policy version is the same as in the assertPoliciesMap
assert.Equal(policy.Version, assertPolicy.Version)
// Iterate over each policy statement
for i, statement := range policy.Statements {
// Check if each statement effect is the same as in the assertPoliciesMap statement
assert.Equal(statement.Effect, assertPolicy.Statements[i].Effect)
// Check if each statement action is the same as in the assertPoliciesMap statement
assert.Equal(statement.Actions, assertPolicy.Statements[i].Actions)
// Check if each statement resource is the same as in the assertPoliciesMap resource
assert.Equal(statement.Resources, assertPolicy.Statements[i].Resources)
}
// Check if policy definition is the same as in the assertPoliciesMap
assert.Equal(policy.Policy, assertPolicy.Policy)
}
// Test-3 : listPolicies() Return error and see that the error is handled correctly and returned
minioListPoliciesMock = func() (map[string][]byte, error) {
@@ -152,17 +112,6 @@ func TestListPolicies(t *testing.T) {
if assert.Error(err) {
assert.Equal("error", err.Error())
}
//Test-4 : listPolicies() handles malformed json
minioListPoliciesMock = func() (map[string][]byte, error) {
malformedData := map[string][]byte{
"malformed-policy": []byte("asdasdasdasdasd"),
}
return malformedData, nil
}
_, err = listPolicies(ctx, adminClient)
if assert.Error(err) {
assert.NotEmpty(err.Error())
}
}
func TestRemovePolicy(t *testing.T) {
@@ -201,15 +150,8 @@ func TestAddPolicy(t *testing.T) {
return []byte(policyDefinition), nil
}
assertPolicy := models.Policy{
Name: "new-policy",
Statements: []*models.Statement{
{
Actions: []string{"s3:GetBucketLocation", "s3:GetObject", "s3:ListAllMyBuckets"},
Effect: "Allow",
Resources: []string{"arn:aws:s3:::*"},
},
},
Version: "2012-10-17",
Name: "new-policy",
Policy: "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":[\"s3:GetBucketLocation\",\"s3:GetObject\",\"s3:ListAllMyBuckets\"],\"Resource\":[\"arn:aws:s3:::*\"]}]}",
}
// Test-1 : addPolicy() adds a new policy
function := "addPolicy()"
@@ -218,8 +160,7 @@ func TestAddPolicy(t *testing.T) {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
assert.Equal(policy.Name, assertPolicy.Name)
assert.Equal(policy.Version, assertPolicy.Version)
assert.Equal(len(policy.Statements), len(assertPolicy.Statements))
assert.Equal(policy.Policy, assertPolicy.Policy)
// Test-2 : addPolicy() got an error while adding policy
minioAddPolicyMock = func(name, policy string) error {
return errors.New("error")
@@ -237,13 +178,6 @@ func TestAddPolicy(t *testing.T) {
if _, err := addPolicy(ctx, adminClient, policyName, policyDefinition); assert.Error(err) {
assert.Equal("error", err.Error())
}
// Test-4 : addPolicy() got an error while parsing policy
minioGetPolicyMock = func(name string) (bytes []byte, err error) {
return []byte("eaeaeaeae"), nil
}
if _, err := addPolicy(ctx, adminClient, policyName, policyDefinition); assert.Error(err) {
assert.NotEmpty(err.Error())
}
}
func TestSetPolicy(t *testing.T) {

View File

@@ -1039,13 +1039,14 @@ func init() {
"addPolicyRequest": {
"type": "object",
"required": [
"name"
"name",
"policy"
],
"properties": {
"definition": {
"name": {
"type": "string"
},
"name": {
"policy": {
"type": "string"
}
}
@@ -1393,13 +1394,7 @@ func init() {
"name": {
"type": "string"
},
"statements": {
"type": "array",
"items": {
"$ref": "#/definitions/statement"
}
},
"version": {
"policy": {
"type": "string"
}
}
@@ -1524,26 +1519,6 @@ func init() {
}
}
},
"statement": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"type": "string"
}
},
"effect": {
"type": "string"
},
"resources": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"updateGroupRequest": {
"type": "object",
"required": [
@@ -2602,13 +2577,14 @@ func init() {
"addPolicyRequest": {
"type": "object",
"required": [
"name"
"name",
"policy"
],
"properties": {
"definition": {
"name": {
"type": "string"
},
"name": {
"policy": {
"type": "string"
}
}
@@ -2956,13 +2932,7 @@ func init() {
"name": {
"type": "string"
},
"statements": {
"type": "array",
"items": {
"$ref": "#/definitions/statement"
}
},
"version": {
"policy": {
"type": "string"
}
}
@@ -3087,26 +3057,6 @@ func init() {
}
}
},
"statement": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"type": "string"
}
},
"effect": {
"type": "string"
},
"resources": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"updateGroupRequest": {
"type": "object",
"required": [

View File

@@ -780,30 +780,13 @@ definitions:
type: integer
format: int64
title: total number of groups
statement:
type: object
properties:
effect:
type: string
actions:
type: array
items:
type: string
resources:
type: array
items:
type: string
policy:
type: object
properties:
name:
type: string
version:
policy:
type: string
statements:
type: array
items:
$ref: "#/definitions/statement"
policyEntity:
type: string
enum:
@@ -824,10 +807,11 @@ definitions:
type: object
required:
- name
- policy
properties:
name:
type: string
definition:
policy:
type: string
listPoliciesResponse:
type: object