Skip to content

Commit

Permalink
Implement least privilege principle for cloudfront, lambda and db mig…
Browse files Browse the repository at this point in the history
…ration stacks (#1134)

…front permissions

### Feature or Bugfix
- Bugfix

### Relates
[- <URL or Ticket>](#877)

### Security
Please answer the questions below briefly where applicable, or write
`N/A`. Based on
[OWASP 10](https://owasp.org/Top10/en/).

- Does this PR introduce or modify any input fields or queries - this
includes
fetching data from storage outside the application (e.g. a database, an
S3 bucket)? N/A
  - Is the input sanitized? N/A 
- What precautions are you taking before deserializing the data you
consume? N/A
  - Is injection prevented by parametrizing queries? N/A
  - Have you ensured no `eval` or similar functions are used? N/A
- Does this PR introduce any functionality or component that requires
authorization? N/A
- How have you ensured it respects the existing AuthN/AuthZ mechanisms?
N/A
  - Are you logging failed auth attempts? N/A
- Are you using or adding any cryptographic features? N/A
  - Do you use a standard proven implementations? yes
- Are the used keys controlled by the customer? Where are they stored?
N/A
- Are you introducing any new policies/roles/users? N/A
- Have you used the least-privilege principle? How? Yes, by removing the
* for cloudfront permissions and explicitly specifying the distribution
id arn.


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.

---------

Co-authored-by: Noah Paige <noahpaig@amazon.com>
  • Loading branch information
mourya-33 and noah-paige authored Apr 15, 2024
1 parent 905196e commit af46a25
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 20 deletions.
14 changes: 12 additions & 2 deletions deploy/stacks/cloudfront.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ def __init__(
parameter_name=f'/dataall/{envname}/CloudfrontDistributionBucket',
string_value=cloudfront_bucket.bucket_name,
)

cloudfront_resources = [
f'arn:aws:cloudfront::{self.account}:distribution/{cloudfront_distribution.distribution_id}'
]
self.user_docs_bucket = None
if custom_auth is None:
userguide_docs_distribution, user_docs_bucket = self.build_static_site(
Expand All @@ -189,6 +191,9 @@ def __init__(

self.userguide_docs_distribution = userguide_docs_distribution
self.user_docs_bucket = user_docs_bucket
cloudfront_resources += [
f'arn:aws:cloudfront::{self.account}:distribution/{userguide_docs_distribution.distribution_id}'
]

if userguide_alternate_domain:
route53.ARecord(
Expand Down Expand Up @@ -232,10 +237,15 @@ def __init__(
)
cross_account_deployment_role.add_to_policy(
iam.PolicyStatement(
actions=['cloudfront:CreateInvalidation', 's3:List*'],
actions=['s3:List*'],
resources=['*'],
)
)

cross_account_deployment_role.add_to_policy(
iam.PolicyStatement(actions=['cloudfront:CreateInvalidation'], resources=cloudfront_resources)
)

cross_account_deployment_role.add_to_policy(
iam.PolicyStatement(
actions=[
Expand Down
76 changes: 75 additions & 1 deletion deploy/stacks/dbmigration.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,55 @@ def __init__(
iam.AccountPrincipal(tooling_account_id),
),
)
private_subnets = vpc.select_subnets(subnet_type=ec2.SubnetType.PRIVATE_WITH_NAT)

subnet_resources = []

for subnet in private_subnets.subnets:
subnet_resources.append(f'arn:aws:ec2:{self.region}:{self.account}:subnet/{subnet.subnet_id}')

subnet_iam_condition = {'ec2:Subnet': subnet_resources, 'ec2:AuthorizedService': 'codebuild.amazonaws.com'}

self.build_project_role.attach_inline_policy(
iam.Policy(
self,
f'DBMigrationCBProject{envname}PolicyDocument',
statements=[
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
actions=[
'ec2:CreateNetworkInterface',
'ec2:DeleteNetworkInterface',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:*/*',
],
),
iam.PolicyStatement(
actions=[
'ec2:CreateNetworkInterfacePermission',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:network-interface/*',
],
conditions={
'StringEquals': subnet_iam_condition,
},
),
iam.PolicyStatement(
actions=[
'ec2:DescribeNetworkInterfaces',
'ec2:DescribeSubnets',
'ec2:DescribeSecurityGroups',
'ec2:DescribeDhcpOptions',
'ec2:DescribeVpcs',
],
resources=['*'],
),
],
)
)

self.build_project_role.add_to_policy(
iam.PolicyStatement(
actions=[
Expand Down Expand Up @@ -107,6 +156,29 @@ def __init__(
conditions={'StringEquals': {'sts:AWSServiceName': 'codeartifact.amazonaws.com'}},
),
)
self.build_project_role.add_to_policy(
iam.PolicyStatement(
actions=['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
resources=[
f'arn:aws:logs:{self.region}:{self.account}:log-group:/aws/codebuild/{resource_prefix}-{envname}-dbmigration*',
],
)
)

self.build_project_role.add_to_policy(
iam.PolicyStatement(
actions=[
'codebuild:CreateReportGroup',
'codebuild:CreateReport',
'codebuild:UpdateReport',
'codebuild:BatchPutTestCases',
'codebuild:BatchPutCodeCoverages',
],
resources=[
f'arn:aws:codebuild:{self.region}:{self.account}:report-group:{resource_prefix}-{envname}-dbmigration-*',
],
)
)
self.codebuild_sg = ec2.SecurityGroup(
self,
f'DBMigrationCBSG{envname}',
Expand Down Expand Up @@ -135,7 +207,7 @@ def __init__(
environment=codebuild.BuildEnvironment(
build_image=codebuild.LinuxBuildImage.AMAZON_LINUX_2_5,
),
role=self.build_project_role,
role=self.build_project_role.without_policy_updates(),
build_spec=codebuild.BuildSpec.from_object(
dict(
version='0.2',
Expand Down Expand Up @@ -163,3 +235,5 @@ def __init__(
),
security_groups=[self.codebuild_sg],
)

self.db_migration_project.node.add_dependency(self.build_project_role)
74 changes: 60 additions & 14 deletions deploy/stacks/lambda_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,28 @@ def __init__(

self.esproxy_dlq = self.set_dlq(f'{resource_prefix}-{envname}-esproxy-dlq')
esproxy_sg = self.create_lambda_sgs(envname, 'esproxy', resource_prefix, vpc)

esproxy_loggroup = logs.LogGroup.from_log_group_name(
self, 'esproxyloggroup', f'/aws/lambda/{resource_prefix}-{envname}-esproxy'
)
graphql_loggroup = logs.LogGroup.from_log_group_name(
self, 'graphqlloggroup', f'/aws/lambda/{resource_prefix}-{envname}-graphql'
)
awsworker_loggroup = logs.LogGroup.from_log_group_name(
self, 'awsworkerloggroup', f'/aws/lambda/{resource_prefix}-{envname}-awsworker'
)

self.elasticsearch_proxy_handler = _lambda.DockerImageFunction(
self,
'ElasticSearchProxyHandler',
function_name=f'{resource_prefix}-{envname}-esproxy',
log_group=esproxy_loggroup
if esproxy_loggroup is not None
else logs.LogGroup(
self, 'esproxyloggroup', log_group_name=f'/aws/lambda/{resource_prefix}-{envname}-esproxy'
),
description='dataall es search function',
role=self.create_function_role(envname, resource_prefix, 'esproxy', pivot_role_name),
role=self.create_function_role(envname, resource_prefix, 'esproxy', pivot_role_name, vpc),
code=_lambda.DockerImageCode.from_ecr(
repository=ecr_repository, tag=image_tag, cmd=['search_handler.handler']
),
Expand All @@ -100,8 +116,13 @@ def __init__(
self,
'LambdaGraphQL',
function_name=f'{resource_prefix}-{envname}-graphql',
log_group=graphql_loggroup
if graphql_loggroup is not None
else logs.LogGroup(
self, 'graphqlloggroup', log_group_name=f'/aws/lambda/{resource_prefix}-{envname}-graphql'
),
description='dataall graphql function',
role=self.create_function_role(envname, resource_prefix, 'graphql', pivot_role_name),
role=self.create_function_role(envname, resource_prefix, 'graphql', pivot_role_name, vpc),
code=_lambda.DockerImageCode.from_ecr(
repository=ecr_repository, tag=image_tag, cmd=['api_handler.handler']
),
Expand All @@ -127,8 +148,13 @@ def __init__(
self,
'AWSWorker',
function_name=f'{resource_prefix}-{envname}-awsworker',
log_group=awsworker_loggroup
if awsworker_loggroup is not None
else logs.LogGroup(
self, 'awsworkerloggroup', log_group_name=f'/aws/lambda/{resource_prefix}-{envname}-awsworker'
),
description='dataall aws worker for aws asynchronous tasks function',
role=self.create_function_role(envname, resource_prefix, 'awsworker', pivot_role_name),
role=self.create_function_role(envname, resource_prefix, 'awsworker', pivot_role_name, vpc),
code=_lambda.DockerImageCode.from_ecr(
repository=ecr_repository, tag=image_tag, cmd=['aws_handler.handler']
),
Expand Down Expand Up @@ -192,6 +218,11 @@ def __init__(
self,
f'CustomAuthorizerFunction-{envname}',
function_name=f'{resource_prefix}-{envname}-custom-authorizer',
log_group=logs.LogGroup(
self,
'customauthorizerloggroup',
log_group_name=f'/aws/lambda/{resource_prefix}-{envname}-custom-authorizer',
),
handler='custom_authorizer_lambda.lambda_handler',
code=_lambda.Code.from_asset(
path=custom_authorizer_assets,
Expand Down Expand Up @@ -277,7 +308,7 @@ def create_lambda_sgs(self, envname, name, resource_prefix, vpc):
)
return lambda_sg

def create_function_role(self, envname, resource_prefix, fn_name, pivot_role_name):
def create_function_role(self, envname, resource_prefix, fn_name, pivot_role_name, vpc):
role_name = f'{resource_prefix}-{envname}-{fn_name}-role'

role_inline_policy = iam.Policy(
Expand Down Expand Up @@ -347,6 +378,12 @@ def create_function_role(self, envname, resource_prefix, fn_name, pivot_role_nam
'logs:StartQuery',
'logs:DescribeLogGroups',
'logs:DescribeLogStreams',
'logs:DescribeQueries',
'logs:StopQuery',
'logs:GetQueryResults',
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents',
],
resources=[
f'arn:aws:s3:::{resource_prefix}-{envname}-{self.account}-{self.region}-resources/*',
Expand All @@ -357,17 +394,7 @@ def create_function_role(self, envname, resource_prefix, fn_name, pivot_role_nam
),
iam.PolicyStatement(
actions=[
'logs:DescribeQueries',
'logs:StopQuery',
'logs:GetQueryResults',
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents',
'ec2:CreateNetworkInterface',
'ec2:DescribeNetworkInterfaces',
'ec2:DeleteNetworkInterface',
'ec2:AssignPrivateIpAddresses',
'ec2:UnassignPrivateIpAddresses',
'xray:PutTraceSegments',
'xray:PutTelemetryRecords',
'xray:GetSamplingRules',
Expand All @@ -378,6 +405,25 @@ def create_function_role(self, envname, resource_prefix, fn_name, pivot_role_nam
],
resources=['*'],
),
iam.PolicyStatement(
actions=[
'ec2:CreateNetworkInterface',
'ec2:DeleteNetworkInterface',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:*/*',
],
),
iam.PolicyStatement(
actions=[
'ec2:AssignPrivateIpAddresses',
'ec2:UnassignPrivateIpAddresses',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:*/*',
],
conditions={'StringEquals': {'ec2:VpcID': f'{vpc.vpc_id}'}},
),
iam.PolicyStatement(
actions=[
'aoss:APIAccessAll',
Expand Down
23 changes: 20 additions & 3 deletions deploy/stacks/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,33 @@ def set_codebuild_iam_roles(self):
'ecr:GetAuthorizationToken',
'ec2:DescribePrefixLists',
'ec2:DescribeManagedPrefixLists',
'ec2:CreateNetworkInterface',
'ec2:DescribeNetworkInterfaces',
'ec2:DeleteNetworkInterface',
'ec2:DescribeSubnets',
'ec2:DescribeSecurityGroups',
'ec2:DescribeDhcpOptions',
'ec2:DescribeVpcs',
],
resources=['*'],
),
iam.PolicyStatement(
actions=[
'ec2:CreateNetworkInterface',
'ec2:DeleteNetworkInterface',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:*/*',
],
),
iam.PolicyStatement(
actions=[
'ec2:AssignPrivateIpAddresses',
'ec2:UnassignPrivateIpAddresses',
],
resources=[
f'arn:aws:ec2:{self.region}:{self.account}:*/*',
],
conditions={'StringEquals': {'ec2:Vpc': f'{self.vpc.vpc_id}'}},
),
iam.PolicyStatement(
actions=[
'codeartifact:GetAuthorizationToken',
Expand Down Expand Up @@ -794,7 +811,7 @@ def set_cloudfront_stage(self, target_env):
'aws s3 sync site/ s3://$bucket',
"aws cloudfront create-invalidation --distribution-id $distributionId --paths '/*'",
],
role=self.expanded_codebuild_role,
role=self.expanded_codebuild_role.without_policy_updates(),
vpc=self.vpc,
),
)
Expand Down

0 comments on commit af46a25

Please sign in to comment.