Custom Policies for Buckets (#1332)
* custom policies * fixing error * add formatting
This commit is contained in:
@@ -43,6 +43,9 @@ type Bucket struct {
|
||||
// creation date
|
||||
CreationDate string `json:"creation_date,omitempty"`
|
||||
|
||||
// definition
|
||||
Definition string `json:"definition,omitempty"`
|
||||
|
||||
// details
|
||||
Details *BucketDetails `json:"details,omitempty"`
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ type SetBucketPolicyRequest struct {
|
||||
// access
|
||||
// Required: true
|
||||
Access *BucketAccess `json:"access"`
|
||||
|
||||
// definition
|
||||
Definition string `json:"definition,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this set bucket policy request
|
||||
|
||||
@@ -178,9 +178,11 @@ const BucketSummary = ({
|
||||
const bucketName = match.params["bucketName"];
|
||||
|
||||
let accessPolicy = "n/a";
|
||||
let policyDefinition = "";
|
||||
|
||||
if (bucketInfo !== null) {
|
||||
accessPolicy = bucketInfo.access;
|
||||
policyDefinition = bucketInfo.definition;
|
||||
}
|
||||
|
||||
const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
|
||||
@@ -485,6 +487,7 @@ const BucketSummary = ({
|
||||
bucketName={bucketName}
|
||||
open={accessPolicyScreenOpen}
|
||||
actualPolicy={accessPolicy}
|
||||
actualDefinition={policyDefinition}
|
||||
closeModalAndRefresh={closeSetAccessPolicy}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -31,18 +31,37 @@ import api from "../../../../common/api";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
|
||||
import { ChangeAccessPolicyIcon } from "../../../../icons";
|
||||
import CodeMirrorWrapper from "../../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
codeMirrorContainer: {
|
||||
marginBottom: 20,
|
||||
paddingLeft: 15,
|
||||
"&:nth-child(2) .MuiGrid-root:nth-child(3)": {
|
||||
border: "1px solid #EAEAEA",
|
||||
},
|
||||
"& label": {
|
||||
marginBottom: ".5rem",
|
||||
},
|
||||
"& label + div": {
|
||||
display: "none",
|
||||
},
|
||||
},
|
||||
...modalStyleUtils,
|
||||
...spacingUtils,
|
||||
});
|
||||
createStyles({
|
||||
...modalStyleUtils,
|
||||
...spacingUtils,
|
||||
});
|
||||
|
||||
interface ISetAccessPolicyProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
bucketName: string;
|
||||
actualPolicy: string;
|
||||
actualDefinition: string;
|
||||
closeModalAndRefresh: () => void;
|
||||
setModalErrorSnackMessage: typeof setModalErrorSnackMessage;
|
||||
}
|
||||
@@ -52,11 +71,13 @@ const SetAccessPolicy = ({
|
||||
open,
|
||||
bucketName,
|
||||
actualPolicy,
|
||||
actualDefinition,
|
||||
closeModalAndRefresh,
|
||||
setModalErrorSnackMessage,
|
||||
}: ISetAccessPolicyProps) => {
|
||||
const [addLoading, setAddLoading] = useState<boolean>(false);
|
||||
const [accessPolicy, setAccessPolicy] = useState<string>("");
|
||||
const [policyDefinition, setPolicyDefinition] = useState<string>("");
|
||||
const addRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
if (addLoading) {
|
||||
@@ -66,6 +87,7 @@ const SetAccessPolicy = ({
|
||||
api
|
||||
.invoke("PUT", `/api/v1/buckets/${bucketName}/set-policy`, {
|
||||
access: accessPolicy,
|
||||
definition: policyDefinition,
|
||||
})
|
||||
.then((res) => {
|
||||
setAddLoading(false);
|
||||
@@ -79,7 +101,12 @@ const SetAccessPolicy = ({
|
||||
|
||||
useEffect(() => {
|
||||
setAccessPolicy(actualPolicy);
|
||||
}, [setAccessPolicy, actualPolicy]);
|
||||
setPolicyDefinition(
|
||||
actualDefinition
|
||||
? JSON.stringify(JSON.parse(actualDefinition), null, 4)
|
||||
: ""
|
||||
);
|
||||
}, [setAccessPolicy, actualPolicy, setPolicyDefinition, actualDefinition]);
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
@@ -98,24 +125,34 @@ const SetAccessPolicy = ({
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={`${classes.spacerTop} ${classes.formFieldRow}`}
|
||||
>
|
||||
<SelectWrapper
|
||||
value={accessPolicy}
|
||||
label="Access Policy"
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setAccessPolicy(e.target.value as string);
|
||||
}}
|
||||
options={[
|
||||
{ value: "PRIVATE", label: "Private" },
|
||||
{ value: "PUBLIC", label: "Public" },
|
||||
]}
|
||||
/>
|
||||
<Grid item xs={12} className={classes.modalFormScrollable}>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<SelectWrapper
|
||||
value={accessPolicy}
|
||||
label="Access Policy"
|
||||
id="select-access-policy"
|
||||
name="select-access-policy"
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setAccessPolicy(e.target.value as string);
|
||||
}}
|
||||
options={[
|
||||
{ value: "PRIVATE", label: "Private" },
|
||||
{ value: "PUBLIC", label: "Public" },
|
||||
{ value: "CUSTOM", label: "Custom" },
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
{accessPolicy === "CUSTOM" && (
|
||||
<Grid item xs={12} className={classes.codeMirrorContainer}>
|
||||
<CodeMirrorWrapper
|
||||
label={`Write Policy`}
|
||||
value={policyDefinition}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setPolicyDefinition(value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
@@ -133,7 +170,9 @@ const SetAccessPolicy = ({
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
disabled={
|
||||
addLoading || (accessPolicy === "CUSTOM" && !policyDefinition)
|
||||
}
|
||||
>
|
||||
Set
|
||||
</Button>
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface Details {
|
||||
export interface BucketInfo {
|
||||
name: string;
|
||||
access: string;
|
||||
definition: string;
|
||||
}
|
||||
|
||||
export interface BucketList {
|
||||
|
||||
@@ -3735,6 +3735,9 @@ func init() {
|
||||
"creation_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"definition": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5294,6 +5297,9 @@ func init() {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/bucketAccess"
|
||||
},
|
||||
"definition": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -9626,6 +9632,9 @@ func init() {
|
||||
"creation_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"definition": {
|
||||
"type": "string"
|
||||
},
|
||||
"details": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11185,6 +11194,9 @@ func init() {
|
||||
"properties": {
|
||||
"access": {
|
||||
"$ref": "#/definitions/bucketAccess"
|
||||
},
|
||||
"definition": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -436,7 +436,7 @@ func getMakeBucketResponse(session *models.Principal, br *models.MakeBucketReque
|
||||
}
|
||||
|
||||
// setBucketAccessPolicy set the access permissions on an existing bucket.
|
||||
func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess) error {
|
||||
func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName string, access models.BucketAccess, policyDefinition string) error {
|
||||
if strings.TrimSpace(bucketName) == "" {
|
||||
return fmt.Errorf("error: bucket name not present")
|
||||
}
|
||||
@@ -444,18 +444,21 @@ func setBucketAccessPolicy(ctx context.Context, client MinioClient, bucketName s
|
||||
return fmt.Errorf("error: bucket access not present")
|
||||
}
|
||||
// Prepare policyJSON corresponding to the access type
|
||||
if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC {
|
||||
if access != models.BucketAccessPRIVATE && access != models.BucketAccessPUBLIC && access != models.BucketAccessCUSTOM {
|
||||
return fmt.Errorf("access: `%s` not supported", access)
|
||||
}
|
||||
bucketPolicy := consoleAccess2policyAccess(access)
|
||||
|
||||
bucketAccessPolicy := policy.BucketAccessPolicy{Version: minioIAMPolicy.DefaultVersion}
|
||||
if access == models.BucketAccessCUSTOM {
|
||||
err := client.setBucketPolicyWithContext(ctx, bucketName, policyDefinition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
bucketPolicy := consoleAccess2policyAccess(access)
|
||||
bucketAccessPolicy.Statements = policy.SetPolicy(bucketAccessPolicy.Statements,
|
||||
bucketPolicy, bucketName, "")
|
||||
// implemented like minio/mc/ s3Client.SetAccess()
|
||||
if len(bucketAccessPolicy.Statements) == 0 {
|
||||
return client.setBucketPolicyWithContext(ctx, bucketName, "")
|
||||
}
|
||||
policyJSON, err := json.Marshal(bucketAccessPolicy)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -469,6 +472,7 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
// get updated bucket details and return it
|
||||
mClient, err := newMinioClient(session)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
@@ -476,11 +480,11 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
// set bucket access policy
|
||||
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access); err != nil {
|
||||
|
||||
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access, req.Definition); err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
// get updated bucket details and return it
|
||||
// set bucket access policy
|
||||
bucket, err := getBucketInfo(ctx, minioClient, bucketName)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
@@ -548,19 +552,19 @@ func getBucketInfo(ctx context.Context, client MinioClient, bucketName string) (
|
||||
LogError("error getting bucket policy: %v", err)
|
||||
}
|
||||
|
||||
var policyAccess policy.BucketPolicy
|
||||
if policyStr == "" {
|
||||
policyAccess = policy.BucketPolicyNone
|
||||
bucketAccess = models.BucketAccessPRIVATE
|
||||
} else {
|
||||
var p policy.BucketAccessPolicy
|
||||
if err = json.Unmarshal([]byte(policyStr), &p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyAccess = policy.GetPolicy(p.Statements, bucketName, "")
|
||||
}
|
||||
bucketAccess = policyAccess2consoleAccess(policyAccess)
|
||||
if bucketAccess == models.BucketAccessPRIVATE && policyStr != "" {
|
||||
bucketAccess = models.BucketAccessCUSTOM
|
||||
policyAccess := policy.GetPolicy(p.Statements, bucketName, "")
|
||||
if len(p.Statements) > 0 && policyAccess == policy.BucketPolicyNone {
|
||||
bucketAccess = models.BucketAccessCUSTOM
|
||||
} else {
|
||||
bucketAccess = policyAccess2consoleAccess(policyAccess)
|
||||
}
|
||||
}
|
||||
bucketTags, err := client.GetBucketTagging(ctx, bucketName)
|
||||
if err != nil {
|
||||
@@ -574,6 +578,7 @@ func getBucketInfo(ctx context.Context, client MinioClient, bucketName string) (
|
||||
return &models.Bucket{
|
||||
Name: &bucketName,
|
||||
Access: &bucketAccess,
|
||||
Definition: policyStr,
|
||||
CreationDate: "", // to be implemented
|
||||
Size: 0, // to be implemented
|
||||
Details: bucketDetails,
|
||||
|
||||
@@ -338,8 +338,8 @@ func TestBucketInfo(t *testing.T) {
|
||||
assert.Equal(outputExpected.CreationDate, bucketInfo.CreationDate)
|
||||
assert.Equal(outputExpected.Size, bucketInfo.Size)
|
||||
|
||||
// Test-3: getBucketInfo() get a bucket with CUSTOM access
|
||||
// if bucket has a custom policy set it should return CUSTOM
|
||||
// Test-3: getBucketInfo() get a bucket with PRIVATE access
|
||||
// if bucket has a null statement, the the bucket is PRIVATE
|
||||
mockPolicy = "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
|
||||
minioGetBucketPolicyMock = func(bucketName string) (string, error) {
|
||||
return mockPolicy, nil
|
||||
@@ -347,7 +347,7 @@ func TestBucketInfo(t *testing.T) {
|
||||
bucketToSet = "csbucket"
|
||||
outputExpected = &models.Bucket{
|
||||
Name: swag.String(bucketToSet),
|
||||
Access: models.NewBucketAccess(models.BucketAccessCUSTOM),
|
||||
Access: models.NewBucketAccess(models.BucketAccessPRIVATE),
|
||||
CreationDate: "", // to be implemented
|
||||
Size: 0, // to be implemented
|
||||
}
|
||||
@@ -393,27 +393,27 @@ func TestSetBucketAccess(t *testing.T) {
|
||||
minioSetBucketPolicyWithContextMock = func(ctx context.Context, bucketName, policy string) error {
|
||||
return nil
|
||||
}
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC); err != nil {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
|
||||
// Test-2: setBucketAccessPolicy() set private access
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE); err != nil {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPRIVATE, ""); err != nil {
|
||||
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
|
||||
}
|
||||
|
||||
// Test-3: setBucketAccessPolicy() set invalid access, expected error
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other"); assert.Error(err) {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "other", ""); assert.Error(err) {
|
||||
assert.Equal("access: `other` not supported", err.Error())
|
||||
}
|
||||
|
||||
// Test-4: setBucketAccessPolicy() set access on empty bucket name, expected error
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE); assert.Error(err) {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "", models.BucketAccessPRIVATE, ""); assert.Error(err) {
|
||||
assert.Equal("error: bucket name not present", err.Error())
|
||||
}
|
||||
|
||||
// Test-5: setBucketAccessPolicy() set empty access on bucket, expected error
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", ""); assert.Error(err) {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", "", ""); assert.Error(err) {
|
||||
assert.Equal("error: bucket access not present", err.Error())
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ func TestSetBucketAccess(t *testing.T) {
|
||||
minioSetBucketPolicyWithContextMock = func(ctx context.Context, bucketName, policy string) error {
|
||||
return errors.New("error")
|
||||
}
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC); assert.Error(err) {
|
||||
if err := setBucketAccessPolicy(ctx, minClient, "bucktest1", models.BucketAccessPUBLIC, ""); assert.Error(err) {
|
||||
assert.Equal("error", err.Error())
|
||||
}
|
||||
|
||||
|
||||
@@ -2417,6 +2417,8 @@ definitions:
|
||||
format: int64
|
||||
access:
|
||||
$ref: "#/definitions/bucketAccess"
|
||||
definition:
|
||||
type: string
|
||||
rw_access:
|
||||
type: object
|
||||
properties:
|
||||
@@ -3044,6 +3046,8 @@ definitions:
|
||||
properties:
|
||||
access:
|
||||
$ref: "#/definitions/bucketAccess"
|
||||
definition:
|
||||
type: string
|
||||
bucketQuota:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user