kopia lustrzana https://github.com/simonw/s3-credentials
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 --tsvpull/69/head
rodzic
fc1e06ca3f
commit
7fb4db1978
117
README.md
117
README.md
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
|
|
Ładowanie…
Reference in New Issue