[AWS][AppSec][Medium] Bucket logging not enabled in sample template (#2243)

Add S3 bucket logging not enabled in sample template
This commit is contained in:
Junbo Liang
2021-07-23 10:29:03 -07:00
committed by GitHub
parent 1e10ee61ba
commit 5b8b51924a
8 changed files with 126 additions and 56 deletions
+6 -7
View File
@@ -37,7 +37,7 @@ env = core.Environment(account=ACCOUNT, region=REGION)
app = core.App()
core = AWSCore(
core_construct = AWSCore(
app,
id_=f'{PROJECT_FEATURE_NAME}-Construct',
project_name=PROJECT_NAME,
@@ -46,20 +46,19 @@ core = AWSCore(
)
# Below is the Core example stack which is provided for working with AWSCore ScriptCanvas examples.
# It also provided as an example how to reference properties across stacks in the same CDK applications
# Note: This will make the consuming stack a dependent stack on core
# CDK will deploy the dependent stack first and then the core stack
# It also provided as an example how to reference resources across stacks via stack outputs.
# See https://docs.aws.amazon.com/cdk/latest/guide/resources.html#resource_stack
core_properties = core.properties
example = ExampleResources(
example_stack = ExampleResources(
app,
id_=f'{PROJECT_FEATURE_NAME}-Example-{env.region}',
props_=core_properties,
project_name=f'{PROJECT_NAME}',
feature_name=FEATURE_NAME,
tags={Constants.O3DE_PROJECT_TAG_NAME: PROJECT_NAME, Constants.O3DE_FEATURE_TAG_NAME: FEATURE_NAME},
env=env
)
#
# Add the common stack as a dependency of the feature stack
example_stack.add_dependency(core_construct.common_stack)
app.synth()
+4
View File
@@ -42,3 +42,7 @@ class AWSCore(core.Construct):
@property
def properties(self):
return self._feature_stack.properties
@property
def common_stack(self):
return self._feature_stack
+19 -7
View File
@@ -8,11 +8,11 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
from aws_cdk import (
core,
aws_iam as iam,
aws_s3 as s3,
aws_resourcegroups as resource_groups,
)
from constants import Constants
from core_stack_properties import CoreStackProperties
class CoreStack(core.Stack):
@@ -60,6 +60,17 @@ class CoreStack(core.Stack):
type='TAG_FILTERS_1_0')
)
# Create an S3 bucket for Amazon S3 server access logging
# See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html
self._server_access_logs_bucket = s3.Bucket(
self,
f'{self._project_name}-{self._feature_name}-Access-Log-Bucket',
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED,
access_control=s3.BucketAccessControl.LOG_DELIVERY_WRITE
)
self._server_access_logs_bucket.grant_read(self._admin_group)
# Define exports
# Export resource group
self._resource_group_output = core.CfnOutput(
@@ -83,9 +94,10 @@ class CoreStack(core.Stack):
export_name=f"{self._project_name}:AdminGroup",
value=self._admin_group.group_arn)
@property
def properties(self) -> CoreStackProperties:
_props = CoreStackProperties()
_props.user_group = self._user_group
_props.admin_group = self._admin_group
return _props
# Export access log bucket name
self._server_access_logs_bucket_output = core.CfnOutput(
self,
id=f'ServerAccessLogsBucketOutput',
description='Name of the S3 bucket for storing server access logs generated by the sample CDK application(s)',
export_name=f"{self._project_name}:ServerAccessLogsBucket",
value=self._server_access_logs_bucket.bucket_name)
-25
View File
@@ -1,25 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
"""
from aws_cdk import (
core,
aws_iam as iam
)
class CoreStackProperties(core.StackProps):
"""
Support for cross stack references in the application.
Define any properties from the CoreStack other stacks in this application
may need to consume.
"""
# Common IAM group for users
user_group: iam.Group
# Common IAM group for Admin users
admin_group: iam.Group
@@ -8,13 +8,13 @@ import os
from aws_cdk import (
aws_lambda as lambda_,
aws_iam as iam,
aws_s3 as s3,
aws_s3_deployment as s3_deployment,
aws_dynamodb as dynamo,
core
)
from core_stack_properties import CoreStackProperties
from .auth import AuthPolicy
@@ -25,8 +25,7 @@ class ExampleResources(core.Stack):
* A python 'echo' lambda
* A small dynamodb table with the a primary 'id': str key
"""
def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str,
props_: CoreStackProperties, **kwargs) -> None:
def __init__(self, scope: core.Construct, id_: str, project_name: str, feature_name: str, **kwargs) -> None:
super().__init__(scope, id_, **kwargs,
description=f'Contains resources for the AWSCore examples as part of the '
f'{project_name} project')
@@ -42,17 +41,74 @@ class ExampleResources(core.Stack):
self.__create_outputs()
# Finally grant cross stack references
self.__grant_access(props=props_)
self.__grant_access()
def __grant_access(self, props: CoreStackProperties):
self._s3_bucket.grant_read(props.user_group)
self._s3_bucket.grant_read(props.admin_group)
def __grant_access(self):
user_group = iam.Group.from_group_arn(
self,
f'{self._project_name}-{self._feature_name}-ImportedUserGroup',
core.Fn.import_value(f'{self._project_name}:UserGroup')
)
admin_group = iam.Group.from_group_arn(
self,
f'{self._project_name}-{self._feature_name}-ImportedAdminGroup',
core.Fn.import_value(f'{self._project_name}:AdminGroup')
)
self._lambda.grant_invoke(props.user_group)
self._lambda.grant_invoke(props.admin_group)
# Provide the admin and user groups permissions to read the example S3 bucket.
# Cannot use the grant_read method defined by the Bucket structure since the method tries to add to
# the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are
# not valid principals in S3 bucket policies.
# Check https://aws.amazon.com/premiumsupport/knowledge-center/s3-invalid-principal-in-policy-error/
user_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*"
],
effect=iam.Effect.ALLOW,
resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*']
)
)
admin_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*"
],
effect=iam.Effect.ALLOW,
resources=[self._s3_bucket.bucket_arn, f'{self._s3_bucket.bucket_arn}/*']
)
)
self._table.grant_read_data(props.user_group)
self._table.grant_read_data(props.admin_group)
# Provide the admin and user groups permissions to invoke the example Lambda function.
# Cannot use the grant_invoke method defined by the Function structure since the method tries to add to
# the resource-based policy but the imported IAM groups (which are tokens from Fn.ImportValue) are
# not valid principals in Lambda function policies.
user_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"lambda:InvokeFunction"
],
effect=iam.Effect.ALLOW,
resources=[self._lambda.function_arn]
)
)
admin_group.add_to_principal_policy(
iam.PolicyStatement(
actions=[
"lambda:InvokeFunction"
],
effect=iam.Effect.ALLOW,
resources=[self._lambda.function_arn]
)
)
# Provide the admin and user groups permissions to read from the DynamoDB table.
self._table.grant_read_data(user_group)
self._table.grant_read_data(admin_group)
def __create_s3_bucket(self) -> s3.Bucket:
# Create a sample S3 bucket following S3 best practices
@@ -60,11 +116,21 @@ class ExampleResources(core.Stack):
# 1. Block all public access to the bucket
# 2. Use SSE-S3 encryption. Explore encryption at rest options via
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/serv-side-encryption.html
# 3. Enable Amazon S3 server access logging
# https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerLogs.html
server_access_logs_bucket = s3.Bucket.from_bucket_name(
self,
f'{self._project_name}-{self._feature_name}-ImportedAccessLogsBucket',
core.Fn.import_value(f"{self._project_name}:ServerAccessLogsBucket")
)
example_bucket = s3.Bucket(
self,
f'{self._project_name}-{self._feature_name}-Example-S3bucket',
block_public_access=s3.BlockPublicAccess.BLOCK_ALL,
encryption=s3.BucketEncryption.S3_MANAGED
encryption=s3.BucketEncryption.S3_MANAGED,
server_access_logs_bucket=server_access_logs_bucket,
server_access_logs_prefix=f'{self._project_name}-{self._feature_name}-{self.region}-AccessLogs'
)
s3_deployment.BucketDeployment(
@@ -28,14 +28,14 @@ class AWSMetrics(core.Construct):
# Check context variables to get enabled optional features
optional_features = {
'batch_processing': self.node.try_get_context("batch_processing") == 'true'
'batch_processing': self.node.try_get_context("batch_processing") == 'true',
'server_access_logs_bucket': self.node.try_get_context("server_access_logs_bucket")
}
# Deploy AWS Metrics Stack
self._feature_stack = AWSMetricsStack(
scope,
stack_name,
stack_name=stack_name,
application_name=application_name,
description=f'Contains resources for the AWS Metrics Gem Feature stack as part of the {project_name} project',
optional_features=optional_features,
@@ -43,9 +43,11 @@ class AWSMetricsStack(core.Stack):
)
batch_processing_enabled = optional_features.get('batch_processing', False)
server_access_logs_bucket = optional_features.get('server_access_logs_bucket')
self._data_lake_integration = DataLakeIntegration(
self,
application_name=application_name
application_name=application_name,
server_access_logs_bucket=server_access_logs_bucket
) if batch_processing_enabled else None
self._batch_processing = BatchProcessing(
@@ -19,9 +19,11 @@ class DataLakeIntegration:
"""
Create the AWS resources including the S3 bucket, Glue database, table and crawler for data lake integration
"""
def __init__(self, stack: core.Construct, application_name: str) -> None:
def __init__(self, stack: core.Construct, application_name: str,
server_access_logs_bucket: str = None) -> None:
self._stack = stack
self._application_name = application_name
self._server_access_logs_bucket = server_access_logs_bucket
self._create_analytics_bucket()
self._create_events_database()
@@ -34,6 +36,14 @@ class DataLakeIntegration:
The bucket uses server-side encryption with a CMK managed by S3:
https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingKMSEncryption.html
"""
# Enable server access logging if the server access logs bucket is provided following S3 best practices.
# See https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html
server_access_logs_bucket = s3.Bucket.from_bucket_name(
self._stack,
f'{self._stack.stack_name}-ImportedAccessLogsBucket',
self._server_access_logs_bucket,
) if self._server_access_logs_bucket else None
# Bucket name cannot contain uppercase characters
# Do not specify the bucket name here since bucket name is required to be unique globally. If we set
# a specific name here, only one customer can deploy the bucket successfully.
@@ -46,7 +56,9 @@ class DataLakeIntegration:
block_public_policy=True,
ignore_public_acls=True,
restrict_public_buckets=True
)
),
server_access_logs_bucket=server_access_logs_bucket,
server_access_logs_prefix=f'{self._stack.stack_name}-AccessLogs' if server_access_logs_bucket else None
)
# For Amazon S3 buckets, you must delete all objects in the bucket for deletion to succeed.