Skip to content

Commit 2258bbd

Browse files
authored
Merge pull request #140 from pierretr/master
Add BucketWithRoles construct
2 parents aca1392 + 1d852c3 commit 2258bbd

File tree

3 files changed

+316
-0
lines changed

3 files changed

+316
-0
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""Provide s3 high level constructs."""
2+
from __future__ import annotations
3+
4+
from typing import TYPE_CHECKING
5+
6+
from e3.aws.troposphere import Construct
7+
from e3.aws.troposphere.iam.managed_policy import ManagedPolicy
8+
from e3.aws.troposphere.iam.policy_statement import Allow, Trust
9+
from e3.aws.troposphere.iam.role import Role
10+
from e3.aws.troposphere.s3.bucket import Bucket
11+
12+
if TYPE_CHECKING:
13+
from typing import Any, Union
14+
from troposphere import AWSObject, Stack
15+
16+
17+
class BucketWithRoles(Construct):
18+
"""Provide resources for a s3 bucket with its access roles."""
19+
20+
def __init__(
21+
self,
22+
name: str,
23+
iam_names_prefix: str,
24+
iam_path: str,
25+
trusted_accounts: list[str],
26+
iam_read_root_name: str = "Read",
27+
iam_write_root_name: str = "Write",
28+
**bucket_kwargs: Any,
29+
) -> None:
30+
"""Initialize BucketWithRoles instance.
31+
32+
:param name: name of the bucket
33+
:param iam_names_prefix: prefix for policies and roles names
34+
:param iam_path: path for iam resources
35+
:param trusted_accounts: accounts to be trusted by access roles
36+
:param iam_read_root_name: root name for read access roles and policy
37+
:param iam_write_root_name: root name for write access roles and policy
38+
:param bucket_kwargs: keyword arguments to pass to the bucket constructor
39+
"""
40+
self.name = name
41+
self.iam_names_prefix = iam_names_prefix
42+
self.trusted_accounts = trusted_accounts
43+
44+
self.bucket = Bucket(name=self.name, **bucket_kwargs)
45+
self.read_policy = ManagedPolicy(
46+
name=f"{self.iam_names_prefix}{iam_read_root_name}Policy",
47+
description=f"Grants read access permissions to {self.name} bucket",
48+
statements=[
49+
Allow(action=["s3:GetObject"], resource=self.bucket.all_objects_arn),
50+
Allow(action=["s3:ListBucket"], resource=self.bucket.arn),
51+
],
52+
path=iam_path,
53+
)
54+
self.read_role = Role(
55+
name=f"{self.iam_names_prefix}{iam_read_root_name}Role",
56+
description=f"Role with read access to {self.name} bucket.",
57+
trust=Trust(accounts=self.trusted_accounts),
58+
managed_policy_arns=[self.read_policy.arn],
59+
path=iam_path,
60+
)
61+
self.push_policy = ManagedPolicy(
62+
name=f"{self.iam_names_prefix}{iam_write_root_name}Policy",
63+
description=f"Grants write access permissions to {self.name} bucket",
64+
statements=[
65+
Allow(
66+
action=["s3:PutObject", "s3:DeleteObject"],
67+
resource=self.bucket.all_objects_arn,
68+
)
69+
],
70+
path=iam_path,
71+
)
72+
self.push_role = Role(
73+
name=f"{self.iam_names_prefix}{iam_write_root_name}Role",
74+
description=f"Role with read and write access to {self.name} bucket.",
75+
trust=Trust(accounts=self.trusted_accounts),
76+
managed_policy_arns=[self.push_policy.arn, self.read_policy.arn],
77+
path=iam_path,
78+
)
79+
80+
@property
81+
def ref(self):
82+
"""Return bucket ref."""
83+
return self.bucket.ref
84+
85+
@property
86+
def arn(self):
87+
"""Return bucket arn."""
88+
return self.bucket.arn
89+
90+
@property
91+
def all_objects_arn(self):
92+
return self.bucket.all_objects_arn
93+
94+
def resources(self, stack: Stack) -> list[Union[AWSObject, Construct]]:
95+
"""Return resources associated with the construct."""
96+
return [
97+
self.bucket,
98+
self.read_policy,
99+
self.read_role,
100+
self.push_policy,
101+
self.push_role,
102+
]
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
{
2+
"TestBucketWithRoles": {
3+
"Properties": {
4+
"BucketName": "test-bucket-with-roles",
5+
"BucketEncryption": {
6+
"ServerSideEncryptionConfiguration": [
7+
{
8+
"ServerSideEncryptionByDefault": {
9+
"SSEAlgorithm": "AES256"
10+
}
11+
}
12+
]
13+
},
14+
"PublicAccessBlockConfiguration": {
15+
"BlockPublicAcls": true,
16+
"BlockPublicPolicy": true,
17+
"IgnorePublicAcls": true,
18+
"RestrictPublicBuckets": true
19+
},
20+
"VersioningConfiguration": {
21+
"Status": "Enabled"
22+
}
23+
},
24+
"Type": "AWS::S3::Bucket"
25+
},
26+
"TestBucketWithRolesPolicy": {
27+
"Properties": {
28+
"Bucket": {
29+
"Ref": "TestBucketWithRoles"
30+
},
31+
"PolicyDocument": {
32+
"Version": "2012-10-17",
33+
"Statement": [
34+
{
35+
"Effect": "Deny",
36+
"Principal": {
37+
"AWS": "*"
38+
},
39+
"Action": "s3:*",
40+
"Resource": "arn:aws:s3:::test-bucket-with-roles/*",
41+
"Condition": {
42+
"Bool": {
43+
"aws:SecureTransport": "false"
44+
}
45+
}
46+
},
47+
{
48+
"Effect": "Deny",
49+
"Principal": {
50+
"AWS": "*"
51+
},
52+
"Action": "s3:PutObject",
53+
"Resource": "arn:aws:s3:::test-bucket-with-roles/*",
54+
"Condition": {
55+
"StringNotEquals": {
56+
"s3:x-amz-server-side-encryption": "AES256"
57+
}
58+
}
59+
},
60+
{
61+
"Effect": "Deny",
62+
"Principal": {
63+
"AWS": "*"
64+
},
65+
"Action": "s3:PutObject",
66+
"Resource": "arn:aws:s3:::test-bucket-with-roles/*",
67+
"Condition": {
68+
"Null": {
69+
"s3:x-amz-server-side-encryption": "true"
70+
}
71+
}
72+
}
73+
]
74+
}
75+
},
76+
"Type": "AWS::S3::BucketPolicy"
77+
},
78+
"TestBucketRestorePolicy": {
79+
"Properties": {
80+
"Description": "Grants read access permissions to test-bucket-with-roles bucket",
81+
"ManagedPolicyName": "TestBucketRestorePolicy",
82+
"PolicyDocument": {
83+
"Version": "2012-10-17",
84+
"Statement": [
85+
{
86+
"Effect": "Allow",
87+
"Action": [
88+
"s3:GetObject"
89+
],
90+
"Resource": "arn:aws:s3:::test-bucket-with-roles/*"
91+
},
92+
{
93+
"Effect": "Allow",
94+
"Action": [
95+
"s3:ListBucket"
96+
],
97+
"Resource": "arn:aws:s3:::test-bucket-with-roles"
98+
}
99+
]
100+
},
101+
"Path": "/test/"
102+
},
103+
"Type": "AWS::IAM::ManagedPolicy"
104+
},
105+
"TestBucketRestoreRole": {
106+
"Properties": {
107+
"RoleName": "TestBucketRestoreRole",
108+
"Description": "Role with read access to test-bucket-with-roles bucket.",
109+
"ManagedPolicyArns": [
110+
{
111+
"Ref": "TestBucketRestorePolicy"
112+
}
113+
],
114+
"AssumeRolePolicyDocument": {
115+
"Version": "2012-10-17",
116+
"Statement": [
117+
{
118+
"Effect": "Allow",
119+
"Action": "sts:AssumeRole",
120+
"Principal": {
121+
"AWS": [
122+
"arn:aws:iam::123456789:root"
123+
]
124+
}
125+
}
126+
]
127+
},
128+
"Tags": [
129+
{
130+
"Key": "Name",
131+
"Value": "TestBucketRestoreRole"
132+
}
133+
],
134+
"Path": "/test/"
135+
},
136+
"Type": "AWS::IAM::Role"
137+
},
138+
"TestBucketPushPolicy": {
139+
"Properties": {
140+
"Description": "Grants write access permissions to test-bucket-with-roles bucket",
141+
"ManagedPolicyName": "TestBucketPushPolicy",
142+
"PolicyDocument": {
143+
"Version": "2012-10-17",
144+
"Statement": [
145+
{
146+
"Effect": "Allow",
147+
"Action": [
148+
"s3:PutObject",
149+
"s3:DeleteObject"
150+
],
151+
"Resource": "arn:aws:s3:::test-bucket-with-roles/*"
152+
}
153+
]
154+
},
155+
"Path": "/test/"
156+
},
157+
"Type": "AWS::IAM::ManagedPolicy"
158+
},
159+
"TestBucketPushRole": {
160+
"Properties": {
161+
"RoleName": "TestBucketPushRole",
162+
"Description": "Role with read and write access to test-bucket-with-roles bucket.",
163+
"ManagedPolicyArns": [
164+
{
165+
"Ref": "TestBucketPushPolicy"
166+
},
167+
{
168+
"Ref": "TestBucketRestorePolicy"
169+
}
170+
],
171+
"AssumeRolePolicyDocument": {
172+
"Version": "2012-10-17",
173+
"Statement": [
174+
{
175+
"Effect": "Allow",
176+
"Action": "sts:AssumeRole",
177+
"Principal": {
178+
"AWS": [
179+
"arn:aws:iam::123456789:root"
180+
]
181+
}
182+
}
183+
]
184+
},
185+
"Tags": [
186+
{
187+
"Key": "Name",
188+
"Value": "TestBucketPushRole"
189+
}
190+
],
191+
"Path": "/test/"
192+
},
193+
"Type": "AWS::IAM::Role"
194+
}
195+
}

tests/tests_e3_aws/troposphere/s3/s3_test.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44

55
from e3.aws.troposphere.s3.bucket import Bucket, EncryptionAlgorithm
6+
from e3.aws.troposphere.s3 import BucketWithRoles
67
from e3.aws.troposphere import Stack
78
from e3.aws.troposphere.awslambda import Py38Function
89
from e3.aws.troposphere.sns import Topic
@@ -49,6 +50,24 @@ def test_bucket(stack: Stack) -> None:
4950
assert stack.export()["Resources"] == expected_template
5051

5152

53+
def test_bucket_with_roles(stack: Stack) -> None:
54+
"""Test BucketWithRoles."""
55+
bucket = BucketWithRoles(
56+
name="test-bucket-with-roles",
57+
iam_names_prefix="TestBucket",
58+
iam_read_root_name="Restore",
59+
iam_write_root_name="Push",
60+
iam_path="/test/",
61+
trusted_accounts=["123456789"],
62+
)
63+
stack.add(bucket)
64+
65+
with open(os.path.join(TEST_DIR, "bucket-with-roles.json")) as fd:
66+
expected_template = json.load(fd)
67+
68+
assert stack.export()["Resources"] == expected_template
69+
70+
5271
def test_bucket_multi_encryption(stack: Stack) -> None:
5372
"""Test bucket accepting multiple types of encryptions and without default."""
5473
bucket = Bucket(

0 commit comments

Comments
 (0)