|
8 | 8 | If,
|
9 | 9 | Join,
|
10 | 10 | Not,
|
| 11 | + NoValue, |
11 | 12 | Output,
|
12 | 13 | Ref,
|
13 | 14 | Split,
|
|
42 | 43 | use_cmk_arn
|
43 | 44 | )
|
44 | 45 | from .domain import domain_name, domain_name_alternates, no_alt_domains
|
| 46 | +from .sftp import use_sftp_condition, use_sftp_with_kms_condition |
45 | 47 | from .template import template
|
46 | 48 | from .utils import ParameterWithDefaults as Parameter
|
47 | 49 |
|
|
102 | 104 | Bucket(
|
103 | 105 | "AssetsBucket",
|
104 | 106 | AccessControl=Ref(assets_bucket_access_control),
|
105 |
| - BucketEncryption=BucketEncryption( |
106 |
| - ServerSideEncryptionConfiguration=If( |
107 |
| - use_aes256_encryption_cond, |
108 |
| - [ |
| 107 | + BucketEncryption=If( |
| 108 | + use_aes256_encryption_cond, |
| 109 | + BucketEncryption( |
| 110 | + ServerSideEncryptionConfiguration=[ |
109 | 111 | ServerSideEncryptionRule(
|
110 | 112 | ServerSideEncryptionByDefault=ServerSideEncryptionByDefault(
|
111 | 113 | SSEAlgorithm='AES256'
|
112 | 114 | )
|
113 | 115 | )
|
114 |
| - ], |
115 |
| - [ |
116 |
| - ServerSideEncryptionRule() |
117 | 116 | ]
|
118 |
| - ) |
| 117 | + ), |
| 118 | + NoValue |
119 | 119 | ),
|
120 | 120 | **common_bucket_conf,
|
121 | 121 | )
|
122 | 122 | )
|
123 | 123 |
|
124 | 124 |
|
125 | 125 | # Output S3 asset bucket name
|
126 |
| -template.add_output(Output( |
127 |
| - "AssetsBucketDomainName", |
128 |
| - Description="Assets bucket domain name", |
129 |
| - Value=GetAtt(assets_bucket, "DomainName") |
130 |
| -)) |
| 126 | +template.add_output( |
| 127 | + Output( |
| 128 | + "AssetsBucketDomainName", |
| 129 | + Description="Assets bucket domain name", |
| 130 | + Value=GetAtt(assets_bucket, "DomainName"), |
| 131 | + ) |
| 132 | +) |
131 | 133 |
|
132 | 134 |
|
133 | 135 | # Create an S3 bucket that holds user uploads or other non-public files
|
|
141 | 143 | IgnorePublicAcls=True,
|
142 | 144 | RestrictPublicBuckets=True,
|
143 | 145 | ),
|
144 |
| - BucketEncryption=BucketEncryption( |
145 |
| - ServerSideEncryptionConfiguration=If( |
146 |
| - use_aes256_encryption_cond, |
147 |
| - [ |
| 146 | + BucketEncryption=If( |
| 147 | + use_aes256_encryption_cond, |
| 148 | + BucketEncryption( |
| 149 | + ServerSideEncryptionConfiguration=[ |
148 | 150 | ServerSideEncryptionRule(
|
149 | 151 | ServerSideEncryptionByDefault=ServerSideEncryptionByDefault(
|
150 | 152 | SSEAlgorithm=If(use_cmk_arn, 'aws:kms', 'AES256'),
|
151 | 153 | KMSMasterKeyID=If(use_cmk_arn, Ref(cmk_arn), Ref("AWS::NoValue")),
|
152 | 154 | )
|
153 | 155 | )
|
154 |
| - ], |
155 |
| - [ |
156 |
| - ServerSideEncryptionRule() |
157 | 156 | ]
|
158 |
| - ) |
| 157 | + ), |
| 158 | + NoValue |
159 | 159 | ),
|
160 | 160 | **common_bucket_conf,
|
161 | 161 | )
|
162 | 162 | )
|
163 | 163 |
|
| 164 | +# Output S3 private assets bucket name |
| 165 | +template.add_output( |
| 166 | + Output( |
| 167 | + "PrivateAssetsBucketDomainName", |
| 168 | + Description="Private assets bucket domain name", |
| 169 | + Value=GetAtt(private_assets_bucket, "DomainName"), |
| 170 | + ) |
| 171 | +) |
164 | 172 |
|
165 |
| -# Output S3 asset bucket name |
166 |
| -template.add_output(Output( |
167 |
| - "PrivateAssetsBucketDomainName", |
168 |
| - Description="Private assets bucket domain name", |
169 |
| - Value=GetAtt(private_assets_bucket, "DomainName") |
170 |
| -)) |
| 173 | +# Bucket for SFTP service |
| 174 | +sftp_assets_bucket = Bucket( |
| 175 | + "SFTPAssetsBucket", |
| 176 | + Condition=use_sftp_condition, |
| 177 | + AccessControl=Private, |
| 178 | + PublicAccessBlockConfiguration=PublicAccessBlockConfiguration( |
| 179 | + BlockPublicAcls=True, |
| 180 | + BlockPublicPolicy=True, |
| 181 | + IgnorePublicAcls=True, |
| 182 | + RestrictPublicBuckets=True, |
| 183 | + ), |
| 184 | + BucketEncryption=If( |
| 185 | + use_aes256_encryption_cond, |
| 186 | + BucketEncryption( |
| 187 | + ServerSideEncryptionConfiguration=[ |
| 188 | + ServerSideEncryptionRule( |
| 189 | + ServerSideEncryptionByDefault=ServerSideEncryptionByDefault( |
| 190 | + SSEAlgorithm=If(use_cmk_arn, "aws:kms", "AES256"), |
| 191 | + KMSMasterKeyID=If( |
| 192 | + use_cmk_arn, Ref(cmk_arn), Ref("AWS::NoValue") |
| 193 | + ), |
| 194 | + ) |
| 195 | + ) |
| 196 | + ] |
| 197 | + ), |
| 198 | + NoValue, |
| 199 | + ), |
| 200 | + **common_bucket_conf, |
| 201 | +) |
| 202 | +template.add_resource(sftp_assets_bucket) |
| 203 | + |
| 204 | +# Output SFTP asset bucket name |
| 205 | +template.add_output( |
| 206 | + Output( |
| 207 | + "SFTPBucketDomainName", |
| 208 | + Condition=use_sftp_condition, |
| 209 | + Description="SFTP bucket domain name", |
| 210 | + Value=GetAtt(sftp_assets_bucket, "DomainName"), |
| 211 | + ) |
| 212 | +) |
171 | 213 |
|
| 214 | +assets_management_policy_statements = [ |
| 215 | + dict( |
| 216 | + Effect="Allow", |
| 217 | + Action=["s3:ListBucket"], |
| 218 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(assets_bucket)]), |
| 219 | + ), |
| 220 | + dict( |
| 221 | + Effect="Allow", |
| 222 | + Action=["s3:*"], |
| 223 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(assets_bucket), "/*"]), |
| 224 | + ), |
| 225 | + dict( |
| 226 | + Effect="Allow", |
| 227 | + Action=["s3:ListBucket"], |
| 228 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(private_assets_bucket)]), |
| 229 | + ), |
| 230 | + dict( |
| 231 | + Effect="Allow", |
| 232 | + Action=["s3:*"], |
| 233 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(private_assets_bucket), "/*"]), |
| 234 | + ), |
| 235 | +] |
| 236 | + |
| 237 | +assets_management_policy_statements_including_sftp_bucket = ( |
| 238 | + assets_management_policy_statements |
| 239 | + + [ |
| 240 | + dict( |
| 241 | + Effect="Allow", |
| 242 | + Action=["s3:ListBucket"], |
| 243 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(sftp_assets_bucket)]), |
| 244 | + ), |
| 245 | + dict( |
| 246 | + Effect="Allow", |
| 247 | + Action=["s3:*"], |
| 248 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(sftp_assets_bucket), "/*"]), |
| 249 | + ), |
| 250 | + ] |
| 251 | +) |
172 | 252 |
|
173 | 253 | # central asset management policy for use in instance roles
|
174 | 254 | assets_management_policy = iam.Policy(
|
175 | 255 | PolicyName="AssetsManagementPolicy",
|
176 | 256 | PolicyDocument=dict(
|
177 |
| - Statement=[ |
178 |
| - dict( |
179 |
| - Effect="Allow", |
180 |
| - Action=["s3:ListBucket"], |
181 |
| - Resource=Join("", [arn_prefix, ":s3:::", Ref(assets_bucket)]), |
182 |
| - ), |
183 |
| - dict( |
184 |
| - Effect="Allow", |
185 |
| - Action=["s3:*"], |
186 |
| - Resource=Join("", [arn_prefix, ":s3:::", Ref(assets_bucket), "/*"]), |
187 |
| - ), |
188 |
| - dict( |
189 |
| - Effect="Allow", |
190 |
| - Action=["s3:ListBucket"], |
191 |
| - Resource=Join("", [arn_prefix, ":s3:::", Ref(private_assets_bucket)]), |
192 |
| - ), |
193 |
| - dict( |
194 |
| - Effect="Allow", |
195 |
| - Action=["s3:*"], |
196 |
| - Resource=Join("", [arn_prefix, ":s3:::", Ref(private_assets_bucket), "/*"]), |
197 |
| - ), |
198 |
| - ], |
| 257 | + Statement=If( |
| 258 | + use_sftp_condition, |
| 259 | + assets_management_policy_statements_including_sftp_bucket, |
| 260 | + assets_management_policy_statements, |
| 261 | + ) |
199 | 262 | ),
|
200 | 263 | )
|
201 | 264 |
|
|
321 | 384 | )
|
322 | 385 |
|
323 | 386 | # Output CloudFront url
|
324 |
| - template.add_output(Output( |
325 |
| - "AssetsDistributionDomainName", |
326 |
| - Description="The assets CDN domain name", |
327 |
| - Value=GetAtt(distribution, "DomainName"), |
328 |
| - Condition=assets_use_cloudfront_condition, |
329 |
| - )) |
| 387 | + template.add_output( |
| 388 | + Output( |
| 389 | + "AssetsDistributionDomainName", |
| 390 | + Description="The assets CDN domain name", |
| 391 | + Value=GetAtt(distribution, "DomainName"), |
| 392 | + Condition=assets_use_cloudfront_condition, |
| 393 | + ) |
| 394 | + ) |
330 | 395 | else:
|
331 | 396 | distribution = None
|
| 397 | + |
| 398 | +# The scopedown policy is used to restrict a user's access to the parts of the bucket |
| 399 | +# we don't want them to access. |
| 400 | +common_sftp_scopedown_policy_statements = [ |
| 401 | + { |
| 402 | + "Sid": "AllowListingOfSFTPUserFolder", |
| 403 | + "Action": ["s3:ListBucket"], |
| 404 | + "Effect": "Allow", |
| 405 | + "Resource": ["arn:aws:s3:::${transfer:HomeBucket}"], |
| 406 | + "Condition": { |
| 407 | + "StringLike": { |
| 408 | + "s3:prefix": ["${transfer:UserName}/*", "${transfer:UserName}"] |
| 409 | + } |
| 410 | + }, |
| 411 | + }, |
| 412 | + { |
| 413 | + "Sid": "HomeDirObjectAccess", |
| 414 | + "Effect": "Allow", |
| 415 | + "Action": [ |
| 416 | + "s3:PutObject", |
| 417 | + "s3:GetObject", |
| 418 | + "s3:DeleteObjectVersion", |
| 419 | + "s3:DeleteObject", |
| 420 | + "s3:GetObjectVersion", |
| 421 | + ], |
| 422 | + "Resource": [ |
| 423 | + Join("/", [GetAtt(sftp_assets_bucket, "Arn"), "${transfer:UserName}"]), |
| 424 | + Join("/", [GetAtt(sftp_assets_bucket, "Arn"), "${transfer:UserName}/*"]), |
| 425 | + ], |
| 426 | + }, |
| 427 | +] |
| 428 | + |
| 429 | +sftp_kms_policy_statement = dict( |
| 430 | + Effect="Allow", |
| 431 | + Action=["kms:DescribeKey", "kms:GenerateDataKey", "kms:Encrypt", "kms:Decrypt"], |
| 432 | + Resource=Ref(cmk_arn), |
| 433 | +) |
| 434 | + |
| 435 | +sftp_scopedown_policy = iam.ManagedPolicy( |
| 436 | + # This is for applying when adding users to the transfer server. It's not used directly in the stack creation, |
| 437 | + # other than adding it to IAM for later use. |
| 438 | + "SFTPUserScopeDownPolicy", |
| 439 | + PolicyDocument=dict( |
| 440 | + Version="2012-10-17", |
| 441 | + Statement=If( |
| 442 | + use_sftp_with_kms_condition, |
| 443 | + common_sftp_scopedown_policy_statements + [sftp_kms_policy_statement], |
| 444 | + common_sftp_scopedown_policy_statements, |
| 445 | + ), |
| 446 | + ), |
| 447 | +) |
| 448 | +template.add_resource(sftp_scopedown_policy) |
| 449 | + |
| 450 | +# The ROLE is applied to users to let them access the bucket in general, |
| 451 | +# without regart to who they are. |
| 452 | +common_sftp_user_role_statements = [ |
| 453 | + dict( |
| 454 | + Effect="Allow", |
| 455 | + Action=["s3:ListBucket", "s3:GetBucketLocation"], |
| 456 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(sftp_assets_bucket)]), |
| 457 | + ), |
| 458 | + dict( |
| 459 | + Effect="Allow", |
| 460 | + Action=[ |
| 461 | + "s3:PutObject", |
| 462 | + "s3:GetObject", |
| 463 | + "s3:DeleteObject", |
| 464 | + "s3:DeleteObjectVersion", |
| 465 | + "s3:GetObjectVersion", |
| 466 | + "s3:GetObjectACL", |
| 467 | + "s3:PutObjectACL", |
| 468 | + ], |
| 469 | + Resource=Join("", [arn_prefix, ":s3:::", Ref(sftp_assets_bucket), "/*"]), |
| 470 | + ), |
| 471 | +] |
| 472 | + |
| 473 | +sftp_user_role = iam.Role( |
| 474 | + # This also is not used directly during the stack setup, but is put into IAM |
| 475 | + # to be used later when adding users to the transfer server. |
| 476 | + "SFTPUserRole", |
| 477 | + template=template, |
| 478 | + AssumeRolePolicyDocument=dict( |
| 479 | + Statement=[ |
| 480 | + dict( |
| 481 | + Effect="Allow", |
| 482 | + Principal=dict(Service=["transfer.amazonaws.com"]), |
| 483 | + Action=["sts:AssumeRole"], |
| 484 | + ) |
| 485 | + ] |
| 486 | + ), |
| 487 | + Policies=[ |
| 488 | + iam.Policy( |
| 489 | + "SFTPSUserRolePolicy", |
| 490 | + PolicyName="SFTPSUserRolePolicy", |
| 491 | + PolicyDocument=dict( |
| 492 | + Version="2012-10-17", |
| 493 | + Statement=If( |
| 494 | + use_sftp_with_kms_condition, |
| 495 | + common_sftp_user_role_statements + [sftp_kms_policy_statement], |
| 496 | + common_sftp_user_role_statements, |
| 497 | + ), |
| 498 | + ), |
| 499 | + ) |
| 500 | + ], |
| 501 | + RoleName=Join("-", [Ref("AWS::StackName"), "SFTPUserRole"]), |
| 502 | +) |
0 commit comments