Skip to content

Instantly share code, notes, and snippets.

@micahmelling
Created June 12, 2025 23:39
Show Gist options
  • Select an option

  • Save micahmelling/5f414ff327bc04fe54284611df0f1e0f to your computer and use it in GitHub Desktop.

Select an option

Save micahmelling/5f414ff327bc04fe54284611df0f1e0f to your computer and use it in GitHub Desktop.
import mimetypes
import pulumi_aws as aws
import pulumi
from pulumi import FileAsset
from pulumi_aws import s3
def main(bucket_name: str, index_html_path: str, package_dist_path: str, aliases: list, certificate_arn: str,
domain_name: str, hosted_zone_id: str, ip_addresses: list):
"""
Creates a static, single-page website accessible via a Route 53 DNS and protected with SSL.
Example:
main(
bucket_name='my-s3-bucket',
index_html_path='index.html',
aliases=['python-package.mydomain.com'],
certificate_arn='arn:aws:acm:us-east-1:000000000000:certificate/oooooooooo', # found in certificate manager
domain_name='python-package.mydomain.com',
hosted_zone_id='ZZZZZZZZZZZZZZZZZZZZZZZ', # found in route 53
ip_address='0.0.0.0/32' # desired IP address to have access
)
:param bucket_name: name of the S3 bucket hosting the content.
:param index_html_path: path to index.html
:param package_dist_path: path to package distribution
:param aliases: CloudFront aliases, which must include domain_name
:param certificate_arn: ARN of the SSL cert, which must be in us-east-1
:param domain_name: domain name
:param hosted_zone_id: Route53 hosted zone ID
:param ip_addresses: IP address we want to have access
"""
web_bucket = s3.Bucket(bucket_name,
bucket=bucket_name,
website=s3.BucketWebsiteArgs(
index_document="index.html"
))
html_mime_type, _ = mimetypes.guess_type(index_html_path)
html_obj = s3.BucketObject(
index_html_path,
bucket=web_bucket.id,
source=FileAsset(index_html_path),
content_type=html_mime_type
)
dist_mime_type, _ = mimetypes.guess_type(package_dist_path)
dist_obj = s3.BucketObject(
package_dist_path,
bucket=web_bucket.id,
source=FileAsset(package_dist_path),
content_type=dist_mime_type
)
ip_set = aws.wafv2.IpSet("package-ipset",
scope="CLOUDFRONT",
ip_address_version="IPV4",
addresses=ip_addresses,
)
rule_group = aws.wafv2.RuleGroup("exampleRuleGroup",
name="example-rule-group",
scope="CLOUDFRONT",
capacity=1,
visibility_config=aws.wafv2.RuleGroupVisibilityConfigArgs(
cloudwatch_metrics_enabled=True,
metric_name="exampleRuleGroup",
sampled_requests_enabled=True,
),
rules=[
aws.wafv2.RuleGroupRuleArgs(
name="BlockNonAllowedIPs",
priority=1,
action=aws.wafv2.RuleGroupRuleActionArgs(
block={}
),
statement=aws.wafv2.RuleGroupRuleStatementArgs(
not_statement=aws.wafv2.RuleGroupRuleStatementNotStatementArgs(
statements=[
aws.wafv2.RuleGroupRuleStatementArgs(
ip_set_reference_statement=aws.wafv2.RuleGroupRuleStatementIpSetReferenceStatementArgs(
arn=ip_set.arn
)
)
]
)
),
visibility_config=aws.wafv2.RuleGroupRuleVisibilityConfigArgs(
cloudwatch_metrics_enabled=True,
metric_name="blockNonAllowedIPs",
sampled_requests_enabled=True,
),
)
],
description="Block all except specified IPs"
)
web_acl = aws.wafv2.WebAcl("exampleWebAcl",
name="example-web-acl",
scope="CLOUDFRONT",
default_action=aws.wafv2.WebAclDefaultActionArgs(
allow={}
),
visibility_config=aws.wafv2.WebAclVisibilityConfigArgs(
cloudwatch_metrics_enabled=True,
metric_name="exampleWebAcl",
sampled_requests_enabled=True,
),
rules=[
aws.wafv2.WebAclRuleArgs(
name="IPRuleGroup",
priority=1,
override_action=aws.wafv2.WebAclRuleOverrideActionArgs(
none={}
),
statement=aws.wafv2.WebAclRuleStatementArgs(
rule_group_reference_statement=aws.wafv2.WebAclRuleStatementRuleGroupReferenceStatementArgs(
arn=rule_group.arn
)
),
visibility_config=aws.wafv2.WebAclRuleVisibilityConfigArgs(
cloudwatch_metrics_enabled=True,
metric_name="ruleGroup",
sampled_requests_enabled=True,
),
)
]
)
oai = aws.cloudfront.OriginAccessIdentity(f"{bucket_name}_oai")
s3_distribution = aws.cloudfront.Distribution(
f'{bucket_name}_distribution',
origins=[aws.cloudfront.DistributionOriginArgs(
domain_name=web_bucket.bucket_regional_domain_name,
origin_id=f's3{bucket_name}_origin',
s3_origin_config=aws.cloudfront.DistributionOriginS3OriginConfigArgs(
origin_access_identity=oai.cloudfront_access_identity_path,
),
)],
enabled=True,
is_ipv6_enabled=False,
default_root_object=index_html_path,
aliases=aliases,
web_acl_id=web_acl.arn,
default_cache_behavior=aws.cloudfront.DistributionDefaultCacheBehaviorArgs(
allowed_methods=[
"DELETE",
"GET",
"HEAD",
"OPTIONS",
"PATCH",
"POST",
"PUT",
],
cached_methods=[
"GET",
"HEAD",
],
target_origin_id=f's3{bucket_name}_origin',
forwarded_values=aws.cloudfront.DistributionOrderedCacheBehaviorForwardedValuesArgs(
query_string=False,
headers=["Origin"],
cookies=aws.cloudfront.DistributionOrderedCacheBehaviorForwardedValuesCookiesArgs(
forward="none",
),
),
viewer_protocol_policy="redirect-to-https",
),
restrictions = aws.cloudfront.DistributionRestrictionsArgs(
geo_restriction=aws.cloudfront.DistributionRestrictionsGeoRestrictionArgs(
restriction_type="none",
),
),
viewer_certificate = aws.cloudfront.DistributionViewerCertificateArgs(
acm_certificate_arn=certificate_arn,
ssl_support_method='sni-only'
))
source = aws.iam.get_policy_document(statements=[
aws.iam.GetPolicyDocumentStatementArgs(
actions=["s3:GetObject"],
resources=[f"arn:aws:s3:::{bucket_name}/*"],
principals=[
aws.iam.GetPolicyDocumentStatementPrincipalArgs(
type='AWS',
identifiers=[oai.iam_arn]
),
]
),
aws.iam.GetPolicyDocumentStatementArgs(
actions=["s3:GetObject"],
resources=[f"arn:aws:s3:::{bucket_name}/*"],
principals=[
aws.iam.GetPolicyDocumentStatementPrincipalArgs(
type='*',
identifiers=['*']
),
],
conditions=[
aws.iam.GetPolicyDocumentStatementConditionArgs(
test='IpAddress',
variable="aws:SourceIp",
values=ip_addresses
)
]
)
],
)
web_bucket_name = web_bucket.id
bucket_policy = s3.BucketPolicy(f"{bucket_name}_bucket-policy",
bucket=web_bucket_name,
policy=source.json)
route_53_record = aws.route53.Record(domain_name,
zone_id=hosted_zone_id,
name=domain_name,
type="A",
aliases=[aws.route53.RecordAliasArgs(
name=s3_distribution.domain_name,
zone_id=s3_distribution.hosted_zone_id,
evaluate_target_health=False,
)]
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment