INFRA-4031 | Ankit Bhardwaj | add new bucket policy fields (#728)
* INFRA-4031 | Ankit Bhardwaj | add new buckey policy fiels * INFRA-4031 | Ankit Bhardwaj | add old s3_bucket_policies * INFRA-4031 | Ankit Bhardwaj | add conditional render of s3_bucket policy
This commit is contained in:
committed by
GitHub
parent
330d67b096
commit
5b3b802c5d
@@ -22,6 +22,7 @@ export const FormikTable = (props: {
|
||||
headers: Array<string>;
|
||||
className?: string;
|
||||
children: Function;
|
||||
tableRowClass?: string;
|
||||
newItem: Function;
|
||||
label?: string;
|
||||
addOnClick?: string;
|
||||
@@ -29,8 +30,18 @@ export const FormikTable = (props: {
|
||||
deleteAllButton?: boolean;
|
||||
disableAdd?: boolean;
|
||||
}) => {
|
||||
const { name, headers, newItem, label, children, className, addOnClick, reverse, disableAdd } =
|
||||
props;
|
||||
const {
|
||||
name,
|
||||
headers,
|
||||
newItem,
|
||||
label,
|
||||
children,
|
||||
className,
|
||||
tableRowClass,
|
||||
addOnClick,
|
||||
reverse,
|
||||
disableAdd,
|
||||
} = props;
|
||||
const isReverse = (values: Array<any>) => {
|
||||
if (reverse) {
|
||||
return values.reverse();
|
||||
@@ -41,7 +52,7 @@ export const FormikTable = (props: {
|
||||
const classes = makeStyles({
|
||||
deleteButton: {
|
||||
marginLeft: 210,
|
||||
marginTop: 20,
|
||||
padding: 0,
|
||||
},
|
||||
})();
|
||||
|
||||
@@ -77,11 +88,14 @@ export const FormikTable = (props: {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{headers.map((header, i) => (
|
||||
<TableCell key={i}>{header}</TableCell>
|
||||
<TableCell className={tableRowClass} key={i}>
|
||||
<b>{header}</b>
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell align="right">
|
||||
<TableCell className={tableRowClass} align="right">
|
||||
{' '}
|
||||
<IconButton
|
||||
className={tableRowClass}
|
||||
onClick={() =>
|
||||
addOnClick == 'push' ? push(newItem()) : insert(0, newItem())
|
||||
}
|
||||
@@ -98,15 +112,16 @@ export const FormikTable = (props: {
|
||||
getIn(form.values, name, []).map((_, i) => (
|
||||
<TableRow key={i}>
|
||||
{children(i, form.values, insert, form.setFieldValue, push)}
|
||||
<TableCell>
|
||||
<TableCell className={tableRowClass}>
|
||||
<IconButton
|
||||
className={tableRowClass}
|
||||
onClick={async () => {
|
||||
await remove(i);
|
||||
form.setFieldTouched(`environmentVariables.0.name`, true, true);
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
<DeleteIcon />{' '}
|
||||
<DeleteIcon className={tableRowClass} />{' '}
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import { Grid, Typography, Box, FormControlLabel, TableCell } from '@material-ui/core';
|
||||
import { Grid, Typography, Box, FormControlLabel, TableCell, Radio } from '@material-ui/core';
|
||||
import { useField, useFormikContext } from 'formik';
|
||||
import * as manifest from '../../models/Manifest';
|
||||
import { FormikTextField } from '../../components/common/FormikTextField';
|
||||
@@ -24,9 +24,20 @@ import {
|
||||
getBucketsDeploymentStatus,
|
||||
S3StorageClassOptions,
|
||||
disasterRecovery,
|
||||
newBucketPolicyEnvs,
|
||||
} from './constants';
|
||||
import { useStyles } from './useStyles';
|
||||
import { path } from '../../models/Manifest';
|
||||
import {
|
||||
newBucketPolicyStatement,
|
||||
newS3BucketPolicyAction,
|
||||
newS3BucketPolicyItem,
|
||||
newS3BucketPolicyPrincipal,
|
||||
path,
|
||||
} from '../../models/Manifest';
|
||||
import * as _m from '@src/models/Manifest';
|
||||
import { FormikRadioGroup } from '@components/common/FormikRadioGroup';
|
||||
import { values } from 'lodash';
|
||||
import { elasticCacheAlertFields } from '@src/coreform/elasticcache/constant';
|
||||
|
||||
const PolicyBox = (props: {
|
||||
policyHeading: string;
|
||||
@@ -206,6 +217,13 @@ const BucketMetadata = (props: {
|
||||
);
|
||||
};
|
||||
|
||||
interface S3BucketPolicyTableProps {
|
||||
policy: string;
|
||||
type: 'actions' | 'resource' | 'principal';
|
||||
header: string;
|
||||
newItem: any;
|
||||
}
|
||||
|
||||
const S3BucketPolicy = (props: { s3Bucket: string }): React.ReactNode => {
|
||||
const classes = useStyles();
|
||||
const { s3Bucket } = props;
|
||||
@@ -233,6 +251,109 @@ const S3BucketPolicy = (props: { s3Bucket: string }): React.ReactNode => {
|
||||
);
|
||||
};
|
||||
|
||||
const S3BucketPolicyTable = ({ policy, type, header, newItem }: S3BucketPolicyTableProps) => {
|
||||
const classes = useStyles();
|
||||
return (
|
||||
<FormikTable
|
||||
name={`${policy}.${type}`}
|
||||
headers={[header]}
|
||||
newItem={newItem}
|
||||
className={classes.policyStatement}
|
||||
tableRowClass={classes.policyStatementTableRow}
|
||||
>
|
||||
{i => (
|
||||
<TableCell>
|
||||
<FormikTextField
|
||||
name={`${policy}.${type}.${i}`}
|
||||
className={classes.policyStatementTableField}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
</FormikTable>
|
||||
);
|
||||
};
|
||||
const getIsPIIBucket = (values, bucketIndex) => {
|
||||
const bucketDataSensitivity =
|
||||
values?.extraResources?.s3_buckets?.[bucketIndex]?.metadata?.DataSensitivity;
|
||||
const globalDataSensitivity = values?.metadata?.dataSensitivity;
|
||||
|
||||
return (
|
||||
bucketDataSensitivity === 'PII_SPI' ||
|
||||
(bucketDataSensitivity === undefined && globalDataSensitivity === 'PII_SPI')
|
||||
);
|
||||
};
|
||||
|
||||
const BucketPolicy = (props: { s3Bucket: string; bucketIndex: number }): React.ReactNode => {
|
||||
const classes = useStyles();
|
||||
const { s3Bucket, bucketIndex } = props;
|
||||
const { values }: { values } = useFormikContext();
|
||||
const isPIIBucket = getIsPIIBucket(values, bucketIndex);
|
||||
|
||||
return (
|
||||
<CardLayout heading="Bucket Policy" expanded={false}>
|
||||
<Grid container direction="column" className={classes.policyStatement}>
|
||||
<FormikCardList
|
||||
name={`${s3Bucket}.bucketPolicyStatements`}
|
||||
newItem={_m.newBucketPolicyStatement}
|
||||
newItemLabel="Add new Policy Statement"
|
||||
>
|
||||
{i => (
|
||||
<>
|
||||
<div>
|
||||
{!isPIIBucket ? (
|
||||
<FormikRadioGroup name={`${s3Bucket}.bucketPolicyStatements.${i}.effect`} row>
|
||||
<FormControlLabel
|
||||
disabled={isPIIBucket}
|
||||
value={'Allow'}
|
||||
control={<Radio />}
|
||||
label="Allow"
|
||||
/>
|
||||
<FormControlLabel
|
||||
disabled={isPIIBucket}
|
||||
value={'Deny'}
|
||||
control={<Radio />}
|
||||
label="Deny"
|
||||
/>
|
||||
</FormikRadioGroup>
|
||||
) : null}
|
||||
{isPIIBucket ? (
|
||||
<FormControlLabel
|
||||
control={
|
||||
<FormikCheckbox
|
||||
disabled={!isPIIBucket}
|
||||
name={`${s3Bucket}.bucketPolicyStatements.${i}.crossAccount`}
|
||||
label="Cross Account"
|
||||
/>
|
||||
}
|
||||
label="Cross account role"
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<S3BucketPolicyTable
|
||||
policy={`${s3Bucket}.bucketPolicyStatements.${i}`}
|
||||
type="principal"
|
||||
header="Principal"
|
||||
newItem={_m.newS3BucketPolicyItem}
|
||||
/>
|
||||
<S3BucketPolicyTable
|
||||
policy={`${s3Bucket}.bucketPolicyStatements.${i}`}
|
||||
type="actions"
|
||||
header="Actions"
|
||||
newItem={_m.newS3BucketPolicyItem}
|
||||
/>
|
||||
<S3BucketPolicyTable
|
||||
policy={`${s3Bucket}.bucketPolicyStatements.${i}`}
|
||||
type="resource"
|
||||
header="Resources"
|
||||
newItem={_m.newS3BucketPolicyItem}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormikCardList>
|
||||
</Grid>
|
||||
</CardLayout>
|
||||
);
|
||||
};
|
||||
const CorsPolicy = (props: { s3Bucket: string }): React.ReactNode => {
|
||||
const { s3Bucket } = props;
|
||||
return (
|
||||
@@ -323,7 +444,11 @@ const S3BucketForm = (): React.ReactNode => {
|
||||
/>
|
||||
<LifecycleRules s3Bucket={`extraResources.s3_buckets.${i}`} />
|
||||
<CorsPolicy s3Bucket={`extraResources.s3_buckets.${i}`} />
|
||||
<S3BucketPolicy s3Bucket={`extraResources.s3_buckets.${i}`} />
|
||||
{newBucketPolicyEnvs.has(values.environment) ? (
|
||||
<BucketPolicy s3Bucket={`extraResources.s3_buckets.${i}`} bucketIndex={i} />
|
||||
) : (
|
||||
<S3BucketPolicy s3Bucket={`extraResources.s3_buckets.${i}`} />
|
||||
)}
|
||||
<FormControlLabel
|
||||
control={
|
||||
<FormikCheckbox name={`extraResources.s3_buckets.${i}.enableAccessLog`} />
|
||||
|
||||
@@ -28,3 +28,12 @@ export function getBucketsDeploymentStatus(Value: any): boolean[] {
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
export const newBucketPolicyEnvs = new Set([
|
||||
'local',
|
||||
'perf',
|
||||
'dev',
|
||||
'qa',
|
||||
'data-platform-nonprod',
|
||||
'uat',
|
||||
]);
|
||||
|
||||
@@ -29,4 +29,17 @@ export const useStyles = makeStyles({
|
||||
width: 450,
|
||||
height: 280,
|
||||
},
|
||||
policyStatement: {
|
||||
width: 450,
|
||||
padding: 0,
|
||||
},
|
||||
policyStatementTableRow: {
|
||||
height: 17,
|
||||
padding: 1,
|
||||
},
|
||||
policyStatementTableField: {
|
||||
width: 375,
|
||||
height: 20,
|
||||
padding: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -546,10 +546,24 @@ export const newAwsAccessPolicy = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const newBucketPolicyStatement = () => {
|
||||
return {
|
||||
effect: 'Allow',
|
||||
crossAccount: false,
|
||||
actions: [],
|
||||
principal: [],
|
||||
resource: [],
|
||||
};
|
||||
};
|
||||
|
||||
export const newAwsAccessPolicyAction = () => {
|
||||
return '';
|
||||
};
|
||||
|
||||
export const newS3BucketPolicyItem = () => {
|
||||
return '';
|
||||
};
|
||||
|
||||
export const hasS3Bucket = (manifest: any) => {
|
||||
return has(manifest, path.s3Buckets);
|
||||
};
|
||||
@@ -1000,6 +1014,9 @@ export const newSecondaryRegionReadReplica = () => {
|
||||
export const newS3Bucket = () => {
|
||||
return {
|
||||
anonymizedBucketName: '',
|
||||
metadata: {
|
||||
DataSensitivity: 'Internal',
|
||||
},
|
||||
bucketTag: '',
|
||||
lifecycleRules: [],
|
||||
corsPolicy: [],
|
||||
|
||||
@@ -30,6 +30,7 @@ function isS3WildcardAction(action: string | string[]): boolean {
|
||||
function createContextError(context: any, message: string): boolean {
|
||||
return context.createError({ message });
|
||||
}
|
||||
|
||||
function isPrincipalRestrictive(principal: any): boolean {
|
||||
return principal === '*' || principal?.AWS === '*' || principal?.AWS?.includes('*');
|
||||
}
|
||||
@@ -94,6 +95,15 @@ const validateExpirationDays = (value?: number | null | undefined, context?: any
|
||||
}
|
||||
};
|
||||
|
||||
const validateS3BucketPolicyStatements = (value: any, context: any): boolean => {
|
||||
if (value.effect === 'Deny' && value.actions.includes('s3:*') && value.principal.includes('*')) {
|
||||
return context.createError({
|
||||
message: 'Bucket policy is too restrictive, denying all actions for all resources.',
|
||||
});
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const s3BucketsValidationSchema = yup
|
||||
.array()
|
||||
.of(
|
||||
@@ -105,6 +115,34 @@ export const s3BucketsValidationSchema = yup
|
||||
name: 'bucketPolicy',
|
||||
test: validateS3BucketPolicy,
|
||||
}),
|
||||
bucketPolicyStatements: yup.array().of(
|
||||
yup
|
||||
.object({
|
||||
effect: yup.string().required('is required'),
|
||||
actions: yup.array().of(yup.string().required('can not be empty')).min(1),
|
||||
resource: yup.array().of(yup.string().required('can not be empty')).min(1),
|
||||
principal: yup
|
||||
.array()
|
||||
.of(yup.string().required('can not be empty'))
|
||||
.min(1)
|
||||
.test(
|
||||
'validate-principal-based-on-metadata',
|
||||
'Only one principal is allowed for PII_SPI bucket',
|
||||
function (principal) {
|
||||
const parent = this.from[1].value;
|
||||
if (parent.metadata?.DataSensitivity === 'PII_SPI') {
|
||||
return principal.length <= 1;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
),
|
||||
})
|
||||
.test(
|
||||
'is-too-restrictive',
|
||||
'Bucket policy is too restrictive, denying all actions for all resources.',
|
||||
validateS3BucketPolicyStatements,
|
||||
),
|
||||
),
|
||||
anonymizedBucketName: yup
|
||||
.string()
|
||||
.required('is required')
|
||||
@@ -123,7 +161,7 @@ export const s3BucketsValidationSchema = yup
|
||||
.nullable(),
|
||||
metadata: yup
|
||||
.object({
|
||||
DataSensitivity: yup.string(),
|
||||
DataSensitivity: yup.string().required('is required'),
|
||||
DisasterRecovery: yup.string(),
|
||||
})
|
||||
.nullable(),
|
||||
|
||||
Reference in New Issue
Block a user