From 1628856634f66c58f4ec4d23a1cf176f6b51d0b0 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 4 Apr 2024 22:09:03 -0700 Subject: [PATCH] --public fixed tests, refs #88 --- docs/create.md | 2 +- s3_credentials/cli.py | 20 ++++++++++++++++++++ tests/test_dry_run.py | 2 ++ tests/test_s3_credentials.py | 20 +++++++------------- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/docs/create.md b/docs/create.md index 2ad5789..265fcc9 100644 --- a/docs/create.md +++ b/docs/create.md @@ -58,7 +58,7 @@ The `create` command has a number of options: - `--username TEXT`: The username to use for the user that is created by the command (or the username of an existing user if you do not want to create a new one). If ommitted a default such as `s3.read-write.static.niche-museums.com` will be used. - `-c, --create-bucket`: Create the buckets if they do not exist. Without this any missing buckets will be treated as an error. - `--prefix my-prefix/`: Credentials should only allow access to keys in the S3 bucket that start with this prefix. -- `--public`: When creating a bucket, set it so that any file uploaded to that bucket can be downloaded by anyone who knows its filename. This attaches the {ref}`public_bucket_policy`. +- `--public`: When creating a bucket, set it so that any file uploaded to that bucket can be downloaded by anyone who knows its filename. This attaches the {ref}`public_bucket_policy` and sets the `PublicAccessBlockConfiguration` to `false` for [every option](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PublicAccessBlockConfiguration.html). - `--website`: Sets the bucket to public and configures it to act as a website, with `index.html` treated as an index page and `error.html` used to display custom errors. The URL for the website will be `http://.s3-website..amazonaws.com/` - the region defaults to `us-east-1` unless you specify a `--bucket-region`. - `--read-only`: The user should only be allowed to read files from the bucket. - `--write-only`: The user should only be allowed to write files to the bucket, but not read them. This can be useful for logging and backups. diff --git a/s3_credentials/cli.py b/s3_credentials/cli.py index 391b020..649681c 100644 --- a/s3_credentials/cli.py +++ b/s3_credentials/cli.py @@ -16,6 +16,13 @@ import sys import textwrap from . import policies +PUBLIC_ACCESS_BLOCK_CONFIGURATION = { + "BlockPublicAcls": False, + "IgnorePublicAcls": False, + "BlockPublicPolicy": False, + "RestrictPublicBuckets": False, +} + def bucket_exists(s3, bucket): try: @@ -388,6 +395,12 @@ def create( ), ) ) + if public: + click.echo( + "... then add this public access block configuration:" + ) + click.echo(json.dumps(PUBLIC_ACCESS_BLOCK_CONFIGURATION)) + if bucket_policy: click.echo("... then attach the following bucket policy to it:") click.echo(json.dumps(bucket_policy, indent=4)) @@ -402,6 +415,13 @@ def create( info += " in region: {}".format(bucket_region) log(info) + if public: + s3.put_public_access_block( + Bucket=bucket, + PublicAccessBlockConfiguration=PUBLIC_ACCESS_BLOCK_CONFIGURATION, + ) + log("Set public access block configuration") + if bucket_policy: s3.put_bucket_policy( Bucket=bucket, Policy=json.dumps(bucket_policy) diff --git a/tests/test_dry_run.py b/tests/test_dry_run.py index 0bcc0ba..0f65763 100644 --- a/tests/test_dry_run.py +++ b/tests/test_dry_run.py @@ -55,6 +55,8 @@ Would assume role using following policy for 1200 seconds:*""" ["--public"], ( """Would create bucket: 'my-bucket' +... then add this public access block configuration: +{"BlockPublicAcls": false, "IgnorePublicAcls": false, "BlockPublicPolicy": false, "RestrictPublicBuckets": false} ... then attach the following bucket policy to it:* Would create user: 's3.read-write.my-bucket' with permissions boundary: 'arn:aws:iam::aws:policy/AmazonS3FullAccess' Would attach policy called 's3.read-write.my-bucket' to user 's3.read-write.my-bucket', details:* diff --git a/tests/test_s3_credentials.py b/tests/test_s3_credentials.py index f8d0316..ada1b05 100644 --- a/tests/test_s3_credentials.py +++ b/tests/test_s3_credentials.py @@ -476,6 +476,7 @@ def test_create_public(mocker): assert result.exit_code == 0 assert result.output == ( "Created bucket: pytest-bucket-simonw-1\n" + "Set public access block configuration\n" "Attached bucket policy allowing public access\n" "Attached policy s3.read-write.pytest-bucket-simonw-1 to user s3.read-write.pytest-bucket-simonw-1\n" "Created access key for user: s3.read-write.pytest-bucket-simonw-1\n" @@ -490,11 +491,8 @@ def test_create_public(mocker): "call('sts')", "call().head_bucket(Bucket='pytest-bucket-simonw-1')", "call().create_bucket(Bucket='pytest-bucket-simonw-1')", - "call().put_bucket_policy(Bucket='pytest-bucket-simonw-1', " - 'Policy=\'{"Version": "2012-10-17", "Statement": [{"Sid": ' - '"AllowAllGetObject", "Effect": "Allow", "Principal": "*", "Action": ' - '["s3:GetObject"], "Resource": ' - '["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}\')', + "call().put_public_access_block(Bucket='pytest-bucket-simonw-1', PublicAccessBlockConfiguration={'BlockPublicAcls': False, 'IgnorePublicAcls': False, 'BlockPublicPolicy': False, 'RestrictPublicBuckets': False})", + 'call().put_bucket_policy(Bucket=\'pytest-bucket-simonw-1\', Policy=\'{"Version": "2012-10-17", "Statement": [{"Sid": "AllowAllGetObject", "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}\')', "call().get_user(UserName='s3.read-write.pytest-bucket-simonw-1')", "call().put_user_policy(PolicyDocument='{}', PolicyName='s3.read-write.pytest-bucket-simonw-1', UserName='s3.read-write.pytest-bucket-simonw-1')".format( READ_WRITE_POLICY.replace("$!BUCKET_NAME!$", "pytest-bucket-simonw-1"), @@ -520,6 +518,7 @@ def test_create_website(mocker): assert result.exit_code == 0 assert result.output == ( "Created bucket: pytest-bucket-simonw-1\n" + "Set public access block configuration\n" "Attached bucket policy allowing public access\n" "Configured website: IndexDocument=index.html, ErrorDocument=error.html\n" "Attached policy s3.read-write.pytest-bucket-simonw-1 to user s3.read-write.pytest-bucket-simonw-1\n" @@ -535,14 +534,9 @@ def test_create_website(mocker): "call('sts')", "call().head_bucket(Bucket='pytest-bucket-simonw-1')", "call().create_bucket(Bucket='pytest-bucket-simonw-1')", - "call().put_bucket_policy(Bucket='pytest-bucket-simonw-1', " - 'Policy=\'{"Version": "2012-10-17", "Statement": [{"Sid": ' - '"AllowAllGetObject", "Effect": "Allow", "Principal": "*", "Action": ' - '["s3:GetObject"], "Resource": ' - '["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}\')', - "call().put_bucket_website(Bucket='pytest-bucket-simonw-1', " - "WebsiteConfiguration={'ErrorDocument': {'Key': 'error.html'}, " - "'IndexDocument': {'Suffix': 'index.html'}})", + "call().put_public_access_block(Bucket='pytest-bucket-simonw-1', PublicAccessBlockConfiguration={'BlockPublicAcls': False, 'IgnorePublicAcls': False, 'BlockPublicPolicy': False, 'RestrictPublicBuckets': False})", + 'call().put_bucket_policy(Bucket=\'pytest-bucket-simonw-1\', Policy=\'{"Version": "2012-10-17", "Statement": [{"Sid": "AllowAllGetObject", "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}\')', + "call().put_bucket_website(Bucket='pytest-bucket-simonw-1', WebsiteConfiguration={'ErrorDocument': {'Key': 'error.html'}, 'IndexDocument': {'Suffix': 'index.html'}})", "call().get_user(UserName='s3.read-write.pytest-bucket-simonw-1')", "call().put_user_policy(PolicyDocument='{}', PolicyName='s3.read-write.pytest-bucket-simonw-1', UserName='s3.read-write.pytest-bucket-simonw-1')".format( READ_WRITE_POLICY.replace("$!BUCKET_NAME!$", "pytest-bucket-simonw-1"),