Custom Policies for Buckets (#1332)

* custom policies

* fixing error

* add formatting
This commit is contained in:
adfost
2021-12-28 18:21:29 -08:00
committed by GitHub
parent 10b8a93b5e
commit b2f38200f7
9 changed files with 116 additions and 46 deletions

View File

@@ -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"`

View File

@@ -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

View File

@@ -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}
/>
)}

View File

@@ -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>

View File

@@ -41,6 +41,7 @@ export interface Details {
export interface BucketInfo {
name: string;
access: string;
definition: string;
}
export interface BucketList {

View File

@@ -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"
}
}
},

View File

@@ -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,

View File

@@ -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())
}

View File

@@ -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: