Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Allow multi-part policies on aditional s3 buckets #275

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.rst
Expand Up @@ -586,6 +586,8 @@ For example, the sample custom policy defined in this `json file <https://github

We can also supply a list of buckets to create a range of s3 buckets, these require a name.
These entries can also specify their own policies or use the default, vpc limited one.
Policies for these additional buckets can be provided as an individual policy document the same as
for the static bucket or a list as in this `example multi-part policy <https://github.com/ministryofjustice/bootstrap-cfn/blob/master/tests/sample-s3-additional-bucket-multi-part-custom-policy.json>`_

.. code:: yaml

Expand All @@ -602,6 +604,7 @@ These entries can also specify their own policies or use the default, vpc limite
lifecycles:
/:
expirationdays: 5
policy: tests/sample-s3-additional-bucket-multi-part-custom-policy.json

The outputs of these buckets will be the bucket name postfixed by 'BucketName', ie, mybucketidBucketName. Additionally, and as shown above, one can define a list of Lifecycle rules on a per prefix basis. If a root rule is defined, the rest of the rules are ignored.

Expand Down
5 changes: 4 additions & 1 deletion bootstrap_cfn/config.py
Expand Up @@ -546,10 +546,13 @@ def create_s3_bucket(self, bucket_config, template):
}
}
}

policy = policy if isinstance(policy, list) else [policy]

bucket_policy = BucketPolicy(
"{}Policy".format(bucket_name),
Bucket=Ref(bucket),
PolicyDocument={"Statement": [policy]},
PolicyDocument={"Statement": policy},
)
# Add the bucket name to the list of cloudformation
# outputs
Expand Down
6 changes: 6 additions & 0 deletions tests/sample-s3-additional-bucket-custom-policy.json
@@ -0,0 +1,6 @@
{
"Action": ["s3:Get*", "s3:Put*", "s3:List*", "s3:Delete*"],
"Resource": "arn:aws:s3:::testbucket/*",
"Effect": "Allow",
"Principal": {"AWS": "*"}
}
22 changes: 22 additions & 0 deletions tests/sample-s3-additional-bucket-multi-part-custom-policy.json
@@ -0,0 +1,22 @@
[
{
"Action": ["s3:Get*", "s3:Put*", "s3:List*", "s3:Delete*"],
"Resource": "arn:aws:s3:::testbucket/*",
"Effect": "Allow",
"Condition": {
"StringEquals": {
"aws:sourceVpc": {"Ref": "VPC"}
}
}
},
{
"Action": ["s3:Put*"],
"Resource": "arn:aws:s3:::testbucket/*",
"Effect": "Deny",
"Condition": {
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
]
82 changes: 81 additions & 1 deletion tests/tests.py
Expand Up @@ -249,7 +249,7 @@ def test_s3_no_subkeys(self):
actual_outputs = self._resources_to_dict(template.outputs.values())
compare(expected_outputs, actual_outputs)

def test_custom_s3_policy(self):
def test_s3_custom_policy(self):
resource_value = 'arn:aws:s3:::moj-test-dev-static/*'
expected_s3 = [
{
Expand Down Expand Up @@ -458,6 +458,86 @@ def _policy(x):
self._resources_to_dict([static_bp, bucket, bucket1, bucket_policy1,
bucket2, bucket_policy2, bucket3, bucket_policy3]))

def test_s3_additional_bucket_with_custom_policy(self):
conf_under_test = {
'buckets': [
{
'name': 'testbucket',
'policy': 'tests/sample-s3-additional-bucket-custom-policy.json'
}
]
}

# Resources to compare
expected_bucket_policy = [
{
'Action': ['s3:Get*', 's3:Put*', 's3:List*', 's3:Delete*'],
'Resource': 'arn:aws:s3:::testbucket/*',
'Effect': 'Allow',
'Principal': {'AWS': '*'}
}
]

project_config = ProjectConfig('tests/sample-project.yaml', 'dev')
project_config.config['s3'] = conf_under_test
config = ConfigParser(project_config.config, 'my-stack-name')

template = Template()
config.s3(template)
resources = template.resources.values()

s3_cfg = self._resources_to_dict(resources)
s3_bucket_policy = s3_cfg['testbucketPolicy']['Properties']['PolicyDocument']['Statement']

compare(expected_bucket_policy, s3_bucket_policy)

def test_s3_additional_bucket_with_multi_part_custom_policy(self):
conf_under_test = {
'buckets': [
{
'name': 'testbucket',
'policy': 'tests/sample-s3-additional-bucket-multi-part-custom-policy.json'
}
]
}

# Resources to compare
expected_bucket_policy = [
{
'Action': ['s3:Get*', 's3:Put*', 's3:List*', 's3:Delete*'],
'Resource': 'arn:aws:s3:::testbucket/*',
'Effect': 'Allow',
'Condition': {
'StringEquals': {
'aws:sourceVpc': {'Ref': 'VPC'}
}
}
},
{
'Action': ['s3:Put*'],
'Resource': 'arn:aws:s3:::testbucket/*',
'Effect': 'Deny',
'Condition': {
'StringNotEquals': {
's3:x-amz-server-side-encryption': 'AES256'
}
}
}
]

project_config = ProjectConfig('tests/sample-project.yaml', 'dev')
project_config.config['s3'] = conf_under_test
config = ConfigParser(project_config.config, 'my-stack-name')

template = Template()
config.s3(template)
resources = template.resources.values()

s3_cfg = self._resources_to_dict(resources)
s3_bucket_policy = s3_cfg['testbucketPolicy']['Properties']['PolicyDocument']['Statement']

compare(expected_bucket_policy, s3_bucket_policy)

def test_rds(self):

template = Template()
Expand Down