s3-credentials list-buckets --details option, closes #22

pull/34/head
Simon Willison 2021-11-08 16:01:32 -08:00
rodzic 6d0b5da65d
commit 8e207980a2
3 zmienionych plików z 221 dodań i 10 usunięć

Wyświetl plik

@ -143,9 +143,91 @@ Add `--array` to output a valid JSON array of objects instead.
Shows a list of all buckets in your AWS account.
s3-credentials list-buckets
% s3-credentials list-buckets
{
"Name": "aws-cloudtrail-logs-462092780466-f2c900d3",
"CreationDate": "2021-03-25 22:19:54+00:00"
}
{
"Name": "simonw-test-bucket-for-s3-credentials",
"CreationDate": "2021-11-03 21:46:12+00:00"
}
Accepts the same `--nl` and `--array` options as `list-users`.
With no extra arguments this will show all available buckets - you can also add one or more explicit bucket names to see just those buckets:
% s3-credentials list-buckets simonw-test-bucket-for-s3-credentials
{
"Name": "simonw-test-bucket-for-s3-credentials",
"CreationDate": "2021-11-03 21:46:12+00:00"
}
This accepts the same `--nl` and `--array` options as `list-users`.
Add `--details` to include details of the bucket ACL, website configuration and public access block settings. This is useful for running a security audit of your buckets.
Using `--details` adds three additional API calls for each bucket, so it is advisable to use it with one or more explicit bucket names.
```
% s3-credentials list-buckets simonw-test-public-website-bucket --details
{
"Name": "simonw-test-public-website-bucket",
"CreationDate": "2021-11-08 22:53:30+00:00",
"bucket_acl": {
"Owner": {
"DisplayName": "simon",
"ID": "abcdeabcdeabcdeabcdeabcdeabcde0001"
},
"Grants": [
{
"Grantee": {
"DisplayName": "simon",
"ID": "abcdeabcdeabcdeabcdeabcdeabcde0001",
"Type": "CanonicalUser"
},
"Permission": "FULL_CONTROL"
}
]
},
"public_access_block": null,
"bucket_website": {
"IndexDocument": {
"Suffix": "index.html"
},
"ErrorDocument": {
"Key": "error.html"
}
}
}
```
A bucket with `public_access_block` might look like this:
```json
{
"Name": "aws-cloudtrail-logs-462092780466-f2c900d3",
"CreationDate": "2021-03-25 22:19:54+00:00",
"bucket_acl": {
"Owner": {
"DisplayName": "simon",
"ID": "abcdeabcdeabcdeabcdeabcdeabcde0001"
},
"Grants": [
{
"Grantee": {
"DisplayName": "simon",
"ID": "abcdeabcdeabcdeabcdeabcdeabcde0001",
"Type": "CanonicalUser"
},
"Permission": "FULL_CONTROL"
}
]
},
"public_access_block": {
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": true,
"RestrictPublicBuckets": true
},
"bucket_website": null
}
```
### list-user-policies

Wyświetl plik

@ -289,14 +289,47 @@ def list_user_policies(usernames, access_key, secret_key, session_token, endpoin
@cli.command()
@click.argument("buckets", nargs=-1)
@click.option("--details", help="Include extra bucket details (slower)", is_flag=True)
@click.option("--array", help="Output a valid JSON array", is_flag=True)
@click.option("--nl", help="Output newline-delimited JSON", is_flag=True)
@common_boto3_options
def list_buckets(array, nl, access_key, secret_key, session_token, endpoint_url):
"List all buckets"
def list_buckets(
buckets, details, array, nl, access_key, secret_key, session_token, endpoint_url
):
"List buckets - defaults to all, or pass one or more bucket names"
s3 = make_client("s3", access_key, secret_key, session_token, endpoint_url)
gathered = []
for bucket in s3.list_buckets()["Buckets"]:
if buckets and (bucket["Name"] not in buckets):
continue
if details:
bucket_acl = dict(
(key, value)
for key, value in s3.get_bucket_acl(
Bucket=bucket["Name"],
).items()
if key != "ResponseMetadata"
)
try:
pab = s3.get_public_access_block(
Bucket=bucket["Name"],
)["PublicAccessBlockConfiguration"]
except s3.exceptions.ClientError:
pab = None
try:
bucket_website = dict(
(key, value)
for key, value in s3.get_bucket_website(
Bucket=bucket["Name"],
).items()
if key != "ResponseMetadata"
)
except s3.exceptions.ClientError:
bucket_website = None
bucket["bucket_acl"] = bucket_acl
bucket["public_access_block"] = pab
bucket["bucket_website"] = bucket_website
if array:
gathered.append(bucket)
else:

Wyświetl plik

@ -130,10 +130,10 @@ def test_list_users(mocker, option, expected, stub_iam):
@pytest.mark.parametrize(
"option,expected",
"options,expected",
(
(
"",
[],
"{\n"
' "Name": "bucket-one",\n'
' "CreationDate": "2020-01-01 00:00:00+00:00"\n'
@ -144,7 +144,7 @@ def test_list_users(mocker, option, expected, stub_iam):
"}\n",
),
(
"--array",
["--array"],
"[\n"
" {\n"
' "Name": "bucket-one",\n'
@ -157,13 +157,17 @@ def test_list_users(mocker, option, expected, stub_iam):
"\n]\n",
),
(
"--nl",
["--nl"],
'{"Name": "bucket-one", "CreationDate": "2020-01-01 00:00:00+00:00"}\n'
'{"Name": "bucket-two", "CreationDate": "2020-02-01 00:00:00+00:00"}\n',
),
(
["--nl", "bucket-one"],
'{"Name": "bucket-one", "CreationDate": "2020-01-01 00:00:00+00:00"}\n',
),
),
)
def test_list_buckets(stub_s3, option, expected):
def test_list_buckets(stub_s3, options, expected):
stub_s3.add_response(
"list_buckets",
{
@ -181,11 +185,103 @@ def test_list_buckets(stub_s3, option, expected):
)
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli, ["list-buckets"] + ([option] if option else []))
result = runner.invoke(cli, ["list-buckets"] + options)
assert result.exit_code == 0
assert result.output == expected
def test_list_buckets_details(stub_s3):
stub_s3.add_response(
"list_buckets",
{
"Buckets": [
{
"Name": "bucket-one",
"CreationDate": "2020-01-01 00:00:00+00:00",
}
]
},
)
stub_s3.add_response(
"get_bucket_acl",
{
"Owner": {
"DisplayName": "swillison",
"ID": "36b2eeee501c5952a8ac119f9e5212277a4c01eccfa8d6a9d670bba1e2d5f441",
},
"Grants": [
{
"Grantee": {
"DisplayName": "swillison",
"ID": "36b2eeee501c5952a8ac119f9e5212277a4c01eccfa8d6a9d670bba1e2d5f441",
"Type": "CanonicalUser",
},
"Permission": "FULL_CONTROL",
}
],
"ResponseMetadata": {},
},
)
stub_s3.add_response(
"get_public_access_block",
{
"PublicAccessBlockConfiguration": {
"BlockPublicAcls": True,
"IgnorePublicAcls": True,
"BlockPublicPolicy": True,
"RestrictPublicBuckets": True,
},
},
)
stub_s3.add_response(
"get_bucket_website",
{
"IndexDocument": {"Suffix": "index.html"},
"ErrorDocument": {"Key": "error.html"},
},
)
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli, ["list-buckets", "--details"])
assert result.exit_code == 0
assert result.output == (
"{\n"
' "Name": "bucket-one",\n'
' "CreationDate": "2020-01-01 00:00:00+00:00",\n'
' "bucket_acl": {\n'
' "Owner": {\n'
' "DisplayName": "swillison",\n'
' "ID": "36b2eeee501c5952a8ac119f9e5212277a4c01eccfa8d6a9d670bba1e2d5f441"\n'
" },\n"
' "Grants": [\n'
" {\n"
' "Grantee": {\n'
' "DisplayName": "swillison",\n'
' "ID": "36b2eeee501c5952a8ac119f9e5212277a4c01eccfa8d6a9d670bba1e2d5f441",\n'
' "Type": "CanonicalUser"\n'
" },\n"
' "Permission": "FULL_CONTROL"\n'
" }\n"
" ]\n"
" },\n"
' "public_access_block": {\n'
' "BlockPublicAcls": true,\n'
' "IgnorePublicAcls": true,\n'
' "BlockPublicPolicy": true,\n'
' "RestrictPublicBuckets": true\n'
" },\n"
' "bucket_website": {\n'
' "IndexDocument": {\n'
' "Suffix": "index.html"\n'
" },\n"
' "ErrorDocument": {\n'
' "Key": "error.html"\n'
" }\n"
" }\n"
"}\n"
)
CUSTOM_POLICY = '{"custom": "policy", "bucket": "$!BUCKET_NAME!$"}'
READ_WRITE_POLICY = '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1"]}, {"Effect": "Allow", "Action": "s3:*Object", "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}'
READ_ONLY_POLICY = '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1"]}, {"Effect": "Allow", "Action": "s3:GetObject*", "Resource": ["arn:aws:s3:::pytest-bucket-simonw-1/*"]}]}'