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
|
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
|
### delete-user
|
||||||
|
|
||||||
In trying out this tool it's possible you will create several different user accounts that you later decide to clean up.
|
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()
|
@cli.command()
|
||||||
@click.argument("usernames", nargs=-1)
|
@click.argument("usernames", nargs=-1)
|
||||||
@common_boto3_options
|
@common_boto3_options
|
||||||
|
@ -782,7 +853,7 @@ def output(iterator, headers, nl, csv, tsv):
|
||||||
sys.stdout, headers, dialect="excel-tab" if tsv else "excel"
|
sys.stdout, headers, dialect="excel-tab" if tsv else "excel"
|
||||||
)
|
)
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
writer.writerows(iterator)
|
writer.writerows(fix_json(row) for row in iterator)
|
||||||
else:
|
else:
|
||||||
for line in stream_indented_json(iterator):
|
for line in stream_indented_json(iterator):
|
||||||
click.echo(line)
|
click.echo(line)
|
||||||
|
@ -817,3 +888,18 @@ def paginate(service, method, list_key, **kwargs):
|
||||||
paginator = service.get_paginator(method)
|
paginator = service.get_paginator(method)
|
||||||
for response in paginator.paginate(**kwargs):
|
for response in paginator.paginate(**kwargs):
|
||||||
yield from response[list_key]
|
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)
|
result = runner.invoke(cli, ["list-bucket", "test-bucket"] + options)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.output == expected
|
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