Skip to content

Custom Policies for Buckets #1332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions models/bucket.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions models/set_bucket_policy_request.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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, [
Expand Down Expand Up @@ -485,6 +487,7 @@ const BucketSummary = ({
bucketName={bucketName}
open={accessPolicyScreenOpen}
actualPolicy={accessPolicy}
actualDefinition={policyDefinition}
closeModalAndRefresh={closeSetAccessPolicy}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand All @@ -66,6 +87,7 @@ const SetAccessPolicy = ({
api
.invoke("PUT", `/api/v1/buckets/${bucketName}/set-policy`, {
access: accessPolicy,
definition: policyDefinition,
})
.then((res) => {
setAddLoading(false);
Expand All @@ -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
Expand All @@ -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
Expand All @@ -133,7 +170,9 @@ const SetAccessPolicy = ({
type="submit"
variant="contained"
color="primary"
disabled={addLoading}
disabled={
addLoading || (accessPolicy === "CUSTOM" && !policyDefinition)
}
>
Set
</Button>
Expand Down
1 change: 1 addition & 0 deletions portal-ui/src/screens/Console/Buckets/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface Details {
export interface BucketInfo {
name: string;
access: string;
definition: string;
}

export interface BucketList {
Expand Down
12 changes: 12 additions & 0 deletions restapi/embedded_spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 22 additions & 17 deletions restapi/user_buckets.go
Original file line number Diff line number Diff line change
Expand Up @@ -436,26 +436,29 @@ 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")
}
if strings.TrimSpace(string(access)) == "" {
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
Expand All @@ -469,18 +472,19 @@ 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)
}
// 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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
18 changes: 9 additions & 9 deletions restapi/user_buckets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,16 +338,16 @@ 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
}
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
}
Expand Down Expand Up @@ -393,35 +393,35 @@ 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())
}

// Test-5: setBucketAccessPolicy() handle error on setPolicy call
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())
}

Expand Down
Loading