diff --git a/policyJsonSchema.json b/policyJsonSchema.json new file mode 100644 index 000000000..34a92a08c --- /dev/null +++ b/policyJsonSchema.json @@ -0,0 +1,628 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "type": "object", + "title": "AWS User Policy schema.", + "description": "This schema describes a user policy per AWS policy grammar rules", + "definitions": { + "principalService": { + "type": "object", + "properties": { + "Service": { + "type": "string", + "const": "backbeat" + } + }, + "additionalProperties": false + }, + "principalAnonymous": { + "type": "string", + "pattern": "^\\*$" + }, + "principalAWSAccountID": { + "type": "string", + "pattern": "^[0-9]{12}$" + }, + "principalAWSAccountArn": { + "type": "string", + "pattern": "^arn:aws:iam::[0-9]{12}:root$" + }, + "principalAWSUserArn": { + "type": "string", + "pattern": "^arn:aws:iam::[0-9]{12}:user/(?!\\*)[\\w+=,.@ -/]{1,64}$" + }, + "principalAWSRoleArn": { + "type": "string", + "pattern": "^arn:aws:iam::[0-9]{12}:role/[\\w+=,.@ -]{1,64}$" + }, + "principalFederatedSamlIdp": { + "type": "string", + "pattern": "^arn:aws:iam::[0-9]{12}:saml-provider/[\\w._-]{1,128}$" + }, + "principalFederatedOidcIdp": { + "type": "string", + "pattern": "^(?:http(s)?://)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$" + }, + "principalAWSItem": { + "type": "object", + "properties": { + "AWS": { + "oneOf": [ + { + "$ref": "#/definitions/principalAWSAccountID" + }, + { + "$ref": "#/definitions/principalAnonymous" + }, + { + "$ref": "#/definitions/principalAWSAccountArn" + }, + { + "$ref": "#/definitions/principalAWSUserArn" + }, + { + "$ref": "#/definitions/principalAWSRoleArn" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/principalAWSAccountID" + } + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/principalAWSAccountArn" + } + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/principalAWSRoleArn" + } + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/principalAWSUserArn" + } + } + ] + } + }, + "additionalProperties": false + }, + "principalFederatedItem": { + "type": "object", + "properties": { + "Federated": { + "oneOf": [ + { + "$ref": "#/definitions/principalFederatedSamlIdp" + }, + { + "$ref": "#/definitions/principalFederatedOidcIdp" + } + ] + } + }, + "additionalProperties": false + }, + "principalItem": { + "oneOf": [ + { + "$ref": "#/definitions/principalAWSItem" + }, + { + "$ref": "#/definitions/principalAnonymous" + }, + { + "$ref": "#/definitions/principalFederatedItem" + }, + { + "$ref": "#/definitions/principalService" + } + ] + }, + "actionItem": { + "enum": [ + "s3:*", + "s3:DeleteBucket", + "s3:PutEncryptionConfiguration", + "s3:DeleteBucketPolicy", + "s3:DeleteBucketWebsite", + "s3:DeleteBucketTagging", + "s3:ListBucket", + "s3:GetBucketAcl", + "s3:GetBucketCORS", + "s3:GetEncryptionConfiguration", + "s3:GetLifecycleConfiguration", + "s3:GetBucketLocation", + "s3:GetBucketNotificationConfiguration", + "s3:GetBucketObjectLockConfiguration", + "s3:GetBucketPolicy", + "s3:GetReplicationConfiguration", + "s3:GetBucketVersioning", + "s3:GetBucketWebsite", + "s3:GetBucketTagging", + "s3:PutBucketAcl", + "s3:PutBucketCORS", + "s3:PutLifecycleConfiguration", + "s3:PutBucketNotificationConfiguration", + "s3:PutBucketObjectLockConfiguration", + "s3:PutBucketPolicy", + "s3:PutReplicationConfiguration", + "s3:PutBucketVersioning", + "s3:PutBucketWebsite", + "s3:PutBucketTagging", + "s3:BypassGovernanceRetention", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts", + "s3:MetadataSearch", + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:DeleteObjectTagging", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectLegalHold", + "s3:GetObjectRetention", + "s3:GetObjectTagging", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:RestoreObject", + "s3:PutObjectVersion", + "s3:CreateBucket", + "s3:DeleteReplicationConfiguration", + "s3:DeleteLifecycleConfiguration", + "s3:DeleteObjectVersion", + "s3:DeleteObjectVersionTagging", + "s3:GetObjectVersion", + "s3:GetObjectVersionAcl", + "s3:GetObjectVersionTagging", + "s3:PutObjectVersionAcl", + "s3:PutObjectVersionTagging", + "s3:ListAllMyBuckets", + "s3:ReplicateObject", + "s3:GetObjectVersionRetention", + "s3:PutObjectVersionRetention", + "s3:GetObjectVersionLegalHold", + "s3:PutObjectVersionLegalHold", + "s3:GetBucketNotification", + "s3:PutBucketNotification", + "iam:*", + "iam:AttachGroupPolicy", + "iam:AttachUserPolicy", + "iam:CreateAccessKey", + "iam:CreateGroup", + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:CreateUser", + "iam:DeleteAccessKey", + "iam:DeleteGroup", + "iam:DeleteGroupPolicy", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:DeleteUser", + "iam:DetachGroupPolicy", + "iam:DetachUserPolicy", + "iam:GetGroup", + "iam:GetGroupPolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetUser", + "iam:ListAccessKeys", + "iam:ListEntitiesForPolicy", + "iam:ListGroupPolicies", + "iam:ListGroups", + "iam:ListGroupsForUser", + "iam:ListPolicies", + "iam:ListPolicyVersions", + "iam:ListUsers", + "iam:PutGroupPolicy", + "iam:RemoveUserFromGroup", + "iam:UpdateAccessKey", + "iam:UpdateGroup", + "iam:UpdateUser", + "iam:GetAccessKeyLastUsed", + "iam:GenerateCredentialReport", + "iam:GetCredentialReport", + "iam:TagUser", + "iam:UntagUser", + "iam:ListUserTags", + "sso:*", + "sso:Authorize", + "sts:*", + "sts:AssumeRole", + "metadata:*", + "metadata:admin", + "metadata:bucketd" + ] + }, + "resourceItem": { + "type": "string", + "pattern": "^\\*|arn:(aws|scality)(:(\\*{1}|[a-z0-9\\*\\-]{2,})*?){3}:((?!\\$\\{\\}).)*?$" + }, + "conditions": { + "type": "object", + "properties": { + "StringEquals": { + "type": "object" + }, + "StringNotEquals": { + "type": "object" + }, + "StringEqualsIgnoreCase": { + "type": "object" + }, + "StringNotEqualsIgnoreCase": { + "type": "object" + }, + "StringLike": { + "type": "object" + }, + "StringNotLike": { + "type": "object" + }, + "NumericEquals": { + "type": "object" + }, + "NumericNotEquals": { + "type": "object" + }, + "NumericLessThan": { + "type": "object" + }, + "NumericLessThanEquals": { + "type": "object" + }, + "NumericGreaterThan": { + "type": "object" + }, + "NumericGreaterThanEquals": { + "type": "object" + }, + "DateEquals": { + "type": "object" + }, + "DateNotEquals": { + "type": "object" + }, + "DateLessThan": { + "type": "object" + }, + "DateLessThanEquals": { + "type": "object" + }, + "DateGreaterThan": { + "type": "object" + }, + "DateGreaterThanEquals": { + "type": "object" + }, + "Bool": { + "type": "object" + }, + "BinaryEquals": { + "type": "object" + }, + "BinaryNotEquals": { + "type": "object" + }, + "IpAddress": { + "type": "object" + }, + "NotIpAddress": { + "type": "object" + }, + "ArnEquals": { + "type": "object" + }, + "ArnNotEquals": { + "type": "object" + }, + "ArnLike": { + "type": "object" + }, + "ArnNotLike": { + "type": "object" + }, + "Null": { + "type": "object" + }, + "StringEqualsIfExists": { + "type": "object" + }, + "StringNotEqualsIfExists": { + "type": "object" + }, + "StringEqualsIgnoreCaseIfExists": { + "type": "object" + }, + "StringNotEqualsIgnoreCaseIfExists": { + "type": "object" + }, + "StringLikeIfExists": { + "type": "object" + }, + "StringNotLikeIfExists": { + "type": "object" + }, + "NumericEqualsIfExists": { + "type": "object" + }, + "NumericNotEqualsIfExists": { + "type": "object" + }, + "NumericLessThanIfExists": { + "type": "object" + }, + "NumericLessThanEqualsIfExists": { + "type": "object" + }, + "NumericGreaterThanIfExists": { + "type": "object" + }, + "NumericGreaterThanEqualsIfExists": { + "type": "object" + }, + "DateEqualsIfExists": { + "type": "object" + }, + "DateNotEqualsIfExists": { + "type": "object" + }, + "DateLessThanIfExists": { + "type": "object" + }, + "DateLessThanEqualsIfExists": { + "type": "object" + }, + "DateGreaterThanIfExists": { + "type": "object" + }, + "DateGreaterThanEqualsIfExists": { + "type": "object" + }, + "BoolIfExists": { + "type": "object" + }, + "BinaryEqualsIfExists": { + "type": "object" + }, + "BinaryNotEqualsIfExists": { + "type": "object" + }, + "IpAddressIfExists": { + "type": "object" + }, + "NotIpAddressIfExists": { + "type": "object" + }, + "ArnEqualsIfExists": { + "type": "object" + }, + "ArnNotEqualsIfExists": { + "type": "object" + }, + "ArnLikeIfExists": { + "type": "object" + }, + "ArnNotLikeIfExists": { + "type": "object" + } + }, + "additionalProperties": false + } + }, + "properties": { + "Version": { + "type": "string", + "const": "2012-10-17" + }, + "Statement": { + "oneOf": [ + { + "type": ["array"], + "minItems": 1, + "items": { + "type": "object", + "properties": { + "Sid": { + "type": "string", + "pattern": "^[a-zA-Z0-9]+$" + }, + "Effect": { + "type": "string", + "enum": ["Allow", "Deny"] + }, + "Principal": { + "$ref": "#/definitions/principalItem" + }, + "NotPrincipal": { + "$ref": "#/definitions/principalItem" + }, + "Action": { + "oneOf": [ + { + "$ref": "#/definitions/actionItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/actionItem" + } + } + ] + }, + "NotAction": { + "oneOf": [ + { + "$ref": "#/definitions/actionItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/actionItem" + } + } + ] + }, + "Resource": { + "oneOf": [ + { + "$ref": "#/definitions/resourceItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/resourceItem" + }, + "minItems": 1 + } + ] + }, + "NotResource": { + "oneOf": [ + { + "$ref": "#/definitions/resourceItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/resourceItem" + }, + "minItems": 1 + } + ] + }, + "Condition": { + "$ref": "#/definitions/conditions" + } + }, + "oneOf": [ + { + "required": ["Effect", "Action", "Resource"] + }, + { + "required": ["Effect", "Action", "NotResource"] + }, + { + "required": ["Effect", "NotAction", "Resource"] + }, + { + "required": ["Effect", "NotAction", "NotResource"] + }, + { + "required": ["Effect", "Action", "Principal"] + }, + { + "required": ["Effect", "Action", "NotPrincipal"] + } + ] + } + }, + { + "type": ["object"], + "properties": { + "Sid": { + "type": "string", + "pattern": "^[a-zA-Z0-9]+$" + }, + "Effect": { + "type": "string", + "enum": ["Allow", "Deny"] + }, + "Principal": { + "$ref": "#/definitions/principalItem" + }, + "Action": { + "oneOf": [ + { + "$ref": "#/definitions/actionItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/actionItem" + } + } + ] + }, + "NotAction": { + "oneOf": [ + { + "$ref": "#/definitions/actionItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/actionItem" + } + } + ] + }, + "Resource": { + "oneOf": [ + { + "$ref": "#/definitions/resourceItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/resourceItem" + }, + "minItems": 1 + } + ] + }, + "NotResource": { + "oneOf": [ + { + "$ref": "#/definitions/resourceItem" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/resourceItem" + }, + "minItems": 1 + } + ] + }, + "Condition": { + "$ref": "#/definitions/conditions" + } + }, + "oneOf": [ + { + "required": ["Action", "Effect", "Resource"] + }, + { + "required": ["Action", "Effect", "NotResource"] + }, + { + "required": ["Effect", "NotAction", "Resource"] + }, + { + "required": ["Effect", "NotAction", "NotResource"] + }, + { + "required": ["Effect", "Action", "Principal"] + }, + { + "required": ["Effect", "Action", "NotPrincipal"] + } + ] + } + ] + } + }, + "required": ["Version", "Statement"], + "additionalProperties": false +} diff --git a/src/react/account/AccountEditCommonLayout.tsx b/src/react/account/AccountEditCommonLayout.tsx index db3a8dce0..d2826e3b3 100644 --- a/src/react/account/AccountEditCommonLayout.tsx +++ b/src/react/account/AccountEditCommonLayout.tsx @@ -7,6 +7,8 @@ import CopyButton from '../ui-elements/CopyButton'; import { Clipboard } from '../ui-elements/Clipboard'; import Editor from '../ui-elements/Editor'; import styled from 'styled-components'; +import { Monaco } from '@monaco-editor/react'; +import policySchema from '../../../policyJsonSchema.json'; const FooterWrapper = styled.div` position: fixed; @@ -39,124 +41,140 @@ const StyledFieldSet = styled(F.Fieldset)` `; export const CommonPolicyLayout = ({ - onSubmit, - policyArn, - policyNameField, - isReadOnly, - control, - policyDocument, - errors, - isDirty, - handleCancel, - }: { - policyArn?: string; - policyNameField: JSX.Element; - onSubmit: (e: FormEvent) => void; - isReadOnly?: boolean; - control: Control<{ policyDocument: string }>; - policyDocument: string; - errors?: { policyDocument?: { message?: string } }; - isDirty: boolean; - handleCancel: (e: MouseEvent) => void; - }) => { - const isCreateMode = !policyArn; - return ( - - - - Policy {isCreateMode ? 'Creation' : !isReadOnly ? 'Edition' : ''} - - - {isCreateMode && ( - All * are mandatory fields - )} + onSubmit, + policyArn, + policyNameField, + isReadOnly, + control, + policyDocument, + errors, + isDirty, + handleCancel, +}: { + policyArn?: string; + policyNameField: JSX.Element; + onSubmit: (e: FormEvent) => void; + isReadOnly?: boolean; + control: Control<{ policyDocument: string }>; + policyDocument: string; + errors?: { policyDocument?: { message?: string } }; + isDirty: boolean; + handleCancel: (e: MouseEvent) => void; +}) => { + const isCreateMode = !policyArn; + return ( + + + + Policy {isCreateMode ? 'Creation' : !isReadOnly ? 'Edition' : ''} + + + {isCreateMode && ( + All * are mandatory fields + )} + + + Policy Name{isCreateMode && <>*} + + {policyNameField} + + {policyArn && ( - - Policy Name{isCreateMode && <>*} + + Policy ARN - {policyNameField} + {policyArn} + - {policyArn && ( - - - Policy ARN - - {policyArn} - - - )} - - Policy Document{isCreateMode && <>*} - - ( - - )} - /> - - - - - - We are supporting AWS IAM standards. - - - - <> {errors?.policyDocument?.message} - - - - - {isReadOnly && ( - -