list-roles command

* list-roles command, closes #61
* Refactor list-roles to use paginate() from #63
* Tests for list-roles - a whole lot of mocks
* Documentation for list-roles
* Fixed JSON output in --csv and --tsv
pull/69/head
Simon Willison 2022-01-19 11:44:30 -08:00 zatwierdzone przez GitHub
rodzic fc1e06ca3f
commit 7fb4db1978
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 302 dodań i 1 usunięć

117
README.md
Wyświetl plik

@ -372,6 +372,123 @@ You can pass any number of usernames here. If you don't specify a username the t
s3-credentials list-user-policies
### list-roles
The `list-roles` command lists all of the roles available for the authenticated account.
Add `--details` to fetch the inline and attached managed policies for each row as well - this is slower as it needs to make several additional API calls for each role.
You can optionally add one or more role names to the command to display and fetch details about just those specific roles.
Example usage:
```
% s3-credentials list-roles AWSServiceRoleForLightsail --details
[
{
"Path": "/aws-service-role/lightsail.amazonaws.com/",
"RoleName": "AWSServiceRoleForLightsail",
"RoleId": "AROAWXFXAIOZG5ACQ5NZ5",
"Arn": "arn:aws:iam::462092780466:role/aws-service-role/lightsail.amazonaws.com/AWSServiceRoleForLightsail",
"CreateDate": "2021-01-15 21:41:48+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lightsail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"MaxSessionDuration": 3600,
"inline_policies": [
{
"RoleName": "AWSServiceRoleForLightsail",
"PolicyName": "LightsailExportAccess",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
"kms:CreateGrant"
],
"Resource": "arn:aws:kms:*:451833091580:key/*"
},
{
"Effect": "Allow",
"Action": [
"cloudformation:DescribeStacks"
],
"Resource": "arn:aws:cloudformation:*:*:stack/*/*"
}
]
}
}
],
"attached_policies": [
{
"PolicyName": "LightsailExportAccess",
"PolicyId": "ANPAJ4LZGPQLZWMVR4WMQ",
"Arn": "arn:aws:iam::aws:policy/aws-service-role/LightsailExportAccess",
"Path": "/aws-service-role/",
"DefaultVersionId": "v2",
"AttachmentCount": 1,
"PermissionsBoundaryUsageCount": 0,
"IsAttachable": true,
"Description": "AWS Lightsail service linked role policy which grants permissions to export resources",
"CreateDate": "2018-09-28 16:35:54+00:00",
"UpdateDate": "2022-01-15 01:45:33+00:00",
"Tags": [],
"PolicyVersion": {
"Document": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:DeleteServiceLinkedRole",
"iam:GetServiceLinkedRoleDeletionStatus"
],
"Resource": "arn:aws:iam::*:role/aws-service-role/lightsail.amazonaws.com/AWSServiceRoleForLightsail*"
},
{
"Effect": "Allow",
"Action": [
"ec2:CopySnapshot",
"ec2:DescribeSnapshots",
"ec2:CopyImage",
"ec2:DescribeImages"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetAccountPublicAccessBlock"
],
"Resource": "*"
}
]
},
"VersionId": "v2",
"IsDefaultVersion": true,
"CreateDate": "2022-01-15 01:45:33+00:00"
}
}
]
}
]
```
Add `--nl` to collapse these to single lines as valid newline-delimited JSON.
Add `--csv` or `--tsv` to get back CSV or TSV data.
### delete-user
In trying out this tool it's possible you will create several different user accounts that you later decide to clean up.

Wyświetl plik

@ -503,6 +503,77 @@ def list_users(nl, csv, tsv, **boto_options):
)
@cli.command()
@click.argument("role_names", nargs=-1)
@click.option("--details", help="Include attached policies (slower)", is_flag=True)
@common_output_options
@common_boto3_options
def list_roles(role_names, details, nl, csv, tsv, **boto_options):
"List all roles"
iam = make_client("iam", **boto_options)
headers = (
"Path",
"RoleName",
"RoleId",
"Arn",
"CreateDate",
"AssumeRolePolicyDocument",
"Description",
"MaxSessionDuration",
"PermissionsBoundary",
"Tags",
"RoleLastUsed",
)
if details:
headers += ("inline_policies", "attached_policies")
def iterate():
for role in paginate(iam, "list_roles", "Roles"):
if role_names and role["RoleName"] not in role_names:
continue
if details:
role_name = role["RoleName"]
role["inline_policies"] = []
# Get inline policy names, then policy for each one
for policy_name in paginate(
iam, "list_role_policies", "PolicyNames", RoleName=role_name
):
role_policy_response = iam.get_role_policy(
RoleName=role_name,
PolicyName=policy_name,
)
role_policy_response.pop("ResponseMetadata", None)
role["inline_policies"].append(role_policy_response)
# Get attached managed policies
role["attached_policies"] = []
for attached in paginate(
iam,
"list_attached_role_policies",
"AttachedPolicies",
RoleName=role_name,
):
policy_arn = attached["PolicyArn"]
attached_policy_response = iam.get_policy(
PolicyArn=policy_arn,
)
policy_details = attached_policy_response["Policy"]
# Also need to fetch the policy JSON
version_id = policy_details["DefaultVersionId"]
policy_version_response = iam.get_policy_version(
PolicyArn=policy_arn,
VersionId=version_id,
)
policy_details["PolicyVersion"] = policy_version_response[
"PolicyVersion"
]
role["attached_policies"].append(policy_details)
yield role
output(iterate(), headers, nl, csv, tsv)
@cli.command()
@click.argument("usernames", nargs=-1)
@common_boto3_options
@ -782,7 +853,7 @@ def output(iterator, headers, nl, csv, tsv):
sys.stdout, headers, dialect="excel-tab" if tsv else "excel"
)
writer.writeheader()
writer.writerows(iterator)
writer.writerows(fix_json(row) for row in iterator)
else:
for line in stream_indented_json(iterator):
click.echo(line)
@ -817,3 +888,18 @@ def paginate(service, method, list_key, **kwargs):
paginator = service.get_paginator(method)
for response in paginator.paginate(**kwargs):
yield from response[list_key]
def fix_json(row):
# If a key value is list or dict, json encode it
return dict(
[
(
key,
json.dumps(value, indent=2, default=str)
if isinstance(value, (dict, list, tuple))
else value,
)
for key, value in row.items()
]
)

Wyświetl plik

@ -805,3 +805,101 @@ def test_list_bucket(stub_s3, options, expected):
result = runner.invoke(cli, ["list-bucket", "test-bucket"] + options)
assert result.exit_code == 0
assert result.output == expected
@pytest.fixture
def stub_iam_for_list_roles(stub_iam):
stub_iam.add_response(
"list_roles",
{
"Roles": [
{
"RoleName": "role-one",
"Path": "/",
"Arn": "arn:aws:iam::462092780466:role/role-one",
"RoleId": "36b2eeee501c5952a8ac119f9e521",
"CreateDate": "2020-01-01 00:00:00+00:00",
}
]
},
)
stub_iam.add_response(
"list_role_policies",
{"PolicyNames": ["policy-one"]},
)
stub_iam.add_response(
"get_role_policy",
{
"RoleName": "role-one",
"PolicyName": "policy-one",
"PolicyDocument": '{"foo": "bar}',
},
)
stub_iam.add_response(
"list_attached_role_policies",
{"AttachedPolicies": [{"PolicyArn": "arn:123:must-be-at-least-tweny-chars"}]},
)
stub_iam.add_response(
"get_policy",
{"Policy": {"DefaultVersionId": "v1"}},
)
stub_iam.add_response(
"get_policy_version",
{"PolicyVersion": {"CreateDate": "2020-01-01 00:00:00+00:00"}},
)
@pytest.mark.parametrize("details", (False, True))
def test_list_roles_details(stub_iam_for_list_roles, details):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli, ["list-roles"] + (["--details"] if details else []))
assert result.exit_code == 0
expected = {
"RoleName": "role-one",
"Path": "/",
"Arn": "arn:aws:iam::462092780466:role/role-one",
"RoleId": "36b2eeee501c5952a8ac119f9e521",
"CreateDate": "2020-01-01 00:00:00+00:00",
"inline_policies": [
{
"RoleName": "role-one",
"PolicyName": "policy-one",
"PolicyDocument": '{"foo": "bar}',
}
],
"attached_policies": [
{
"DefaultVersionId": "v1",
"PolicyVersion": {"CreateDate": "2020-01-01 00:00:00+00:00"},
}
],
}
if not details:
expected.pop("inline_policies")
expected.pop("attached_policies")
assert json.loads(result.output) == [expected]
def test_list_roles_csv(stub_iam_for_list_roles):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli, ["list-roles", "--csv", "--details"])
assert result.exit_code == 0
assert result.output == (
"Path,RoleName,RoleId,Arn,CreateDate,AssumeRolePolicyDocument,Description,MaxSessionDuration,PermissionsBoundary,Tags,RoleLastUsed,inline_policies,attached_policies\n"
'/,role-one,36b2eeee501c5952a8ac119f9e521,arn:aws:iam::462092780466:role/role-one,2020-01-01 00:00:00+00:00,,,,,,,"[\n'
" {\n"
' ""RoleName"": ""role-one"",\n'
' ""PolicyName"": ""policy-one"",\n'
' ""PolicyDocument"": ""{\\""foo\\"": \\""bar}""\n'
" }\n"
']","[\n'
" {\n"
' ""DefaultVersionId"": ""v1"",\n'
' ""PolicyVersion"": {\n'
' ""CreateDate"": ""2020-01-01 00:00:00+00:00""\n'
" }\n"
" }\n"
']"\n'
)