Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AUTOMATED] Changes from Sandbox #287

Merged
merged 2 commits into from
Jun 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 93 additions & 64 deletions tools/c7n_org/scripts/orgaccounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,54 @@
from c7n.resources.aws import ApiStats
from c7n.credentials import assumed_session, SessionFactory
from c7n.utils import yaml_dump
import re

ROLE_TEMPLATE = "arn:aws:iam::{Id}:role/OrganizationAccountAccessRole"
NAME_TEMPLATE = "{name}"

log = logging.getLogger('orgaccounts')
log = logging.getLogger("orgaccounts")


@click.command()
@click.option(
'--role',
"--role",
default=ROLE_TEMPLATE,
help="Role template for accounts in the config, defaults to %s" % ROLE_TEMPLATE)
help="Role template for accounts in the config, defaults to %s" % ROLE_TEMPLATE,
)
@click.option(
'--name',
"--name",
default=NAME_TEMPLATE,
help="Name template for accounts in the config, defaults to %s" % NAME_TEMPLATE)
@click.option('--ou', multiple=True, default=["/"],
help="Only export the given subtrees of an organization")
@click.option('-r', '--regions', multiple=True,
help="If specified, set regions per account in config")
@click.option('--assume', help="Role to assume for Credentials")
@click.option('--profile', help="AWS CLI Profile to use for Credentials")
help="Name template for accounts in the config, defaults to %s" % NAME_TEMPLATE,
)
@click.option(
'-f', '--output', type=click.File('w'),
help="File to store the generated config (default stdout)")
@click.option('-a', '--active', is_flag=True, default=False, help="Get only active accounts")
@click.option('-i', '--ignore', multiple=True,
help="list of accounts that won't be added to the config file")
"--ou",
multiple=True,
default=["/"],
help="Only export the given subtrees of an organization",
)
@click.option(
"-r",
"--regions",
multiple=True,
help="If specified, set regions per account in config",
)
@click.option("--assume", help="Role to assume for Credentials")
@click.option("--profile", help="AWS CLI Profile to use for Credentials")
@click.option(
"-f",
"--output",
type=click.File("w"),
help="File to store the generated config (default stdout)",
)
@click.option(
"-a", "--active", is_flag=True, default=False, help="Get only active accounts"
)
@click.option(
"-i",
"--ignore",
multiple=True,
help="list of accounts that won't be added to the config file",
)
def main(role, name, ou, assume, profile, output, regions, active, ignore):
"""Generate a c7n-org accounts config file using AWS Organizations

Expand All @@ -44,8 +64,8 @@ def main(role, name, ou, assume, profile, output, regions, active, ignore):
"""
logging.basicConfig(level=logging.INFO)

stats, session = get_session(assume, 'c7n-org', profile)
client = session.client('organizations')
stats, session = get_session(assume, "c7n-org", profile)
client = session.client("organizations")
accounts = []
for path in ou:
ou = get_ou_from_path(client, path)
Expand All @@ -55,40 +75,41 @@ def main(role, name, ou, assume, profile, output, regions, active, ignore):
for a in accounts:
tags = []

path_parts = a['Path'].strip('/').split('/')
path_parts = a["Path"].strip("/").split("/")
for idx, _ in enumerate(path_parts):
tags.append("path:/%s" % "/".join(path_parts[:idx + 1]))
tags.append("path:/%s" % "/".join(path_parts[: idx + 1]))

for k, v in a.get('Tags', {}).items():
for k, v in a.get("Tags", {}).items():
tags.append("{}:{}".format(k, v))

a['OrgId'] = a['Arn'].split('/')[1]
if not role.startswith('arn'):
arn_role = "arn:aws:iam::{}:role/{}".format(a['Id'], role)
a["OrgId"] = a["Arn"].split("/")[1]
if not role.startswith("arn"):
arn_role = "arn:aws:iam::{}:role/{}".format(a["Id"], role)
else:
arn_role = role.format(**a)
ainfo = {
'account_id': a['Id'],
'email': a['Email'],
'display_name': a['Name'],
'name': a['Name'],
'org_id': a['OrgId'],
'tags': tags,
'role': arn_role}
ainfo['name'] = name.format(**ainfo)
"account_id": a["Id"],
"email": a["Email"],
"display_name": a["Name"],
"name": a["Name"],
"org_id": a["OrgId"],
"tags": tags,
"role": arn_role,
}
ainfo["name"] = name.format(**ainfo)
if regions:
ainfo['regions'] = list(regions)
if 'Tags' in a and a['Tags']:
ainfo['vars'] = a['Tags']
ainfo["regions"] = list(regions)
if "Tags" in a and a["Tags"]:
ainfo["vars"] = a["Tags"]

results.append(ainfo)

# log.info('api calls {}'.format(stats.get_metadata()))
print(yaml_dump({'accounts': results}), file=output)
print(yaml_dump({"accounts": results}), file=output)


def get_session(role, session_name, profile):
region = os.environ.get('AWS_DEFAULT_REGION', 'eu-west-1')
region = os.environ.get("AWS_DEFAULT_REGION", "eu-west-1")
stats = ApiStats(Bag(), Config.empty())
if role:
s = assumed_session(role, session_name, region=region)
Expand All @@ -99,38 +120,41 @@ def get_session(role, session_name, profile):


def get_ou_from_path(client, path):
ou = client.list_roots()['Roots'][0]
ou = client.list_roots()["Roots"][0]

if path == "/":
ou['Path'] = path
ou["Path"] = path
return ou

ou_pager = client.get_paginator('list_organizational_units_for_parent')
for part in path.strip('/').split('/'):
ou_pager = client.get_paginator("list_organizational_units_for_parent")
for part in path.strip("/").split("/"):
found = False
for page in ou_pager.paginate(ParentId=ou['Id']):
for child in page.get('OrganizationalUnits'):
if child['Name'] == part:
for page in ou_pager.paginate(ParentId=ou["Id"]):
for child in page.get("OrganizationalUnits"):
if child["Name"] == part:
found = True
ou = child
break
if found:
break
if found is False:
raise ValueError(
"No OU named:%r found in path: %s" % (
path, path))
ou['Path'] = path
raise ValueError("No OU named:%r found in path: %s" % (path, path))
ou["Path"] = path
return ou


def get_sub_ous(client, ou):
results = [ou]
ou_pager = client.get_paginator('list_organizational_units_for_parent')
for sub_ou in ou_pager.paginate(
ParentId=ou['Id']).build_full_result().get(
'OrganizationalUnits'):
sub_ou['Path'] = "/%s/%s" % (ou['Path'].strip('/'), sub_ou['Name'])
ignore_regex = r".*Closed Accounts.*"
ou_pager = client.get_paginator("list_organizational_units_for_parent")
for sub_ou in (
ou_pager.paginate(ParentId=ou["Id"])
.build_full_result()
.get("OrganizationalUnits")
):
sub_ou["Path"] = "/%s/%s" % (ou["Path"].strip("/"), sub_ou["Name"])
if re.search(ignore_regex, sub_ou["Path"]):
continue
results.extend(get_sub_ous(client, sub_ou))
return results

Expand All @@ -141,25 +165,30 @@ def get_accounts_for_ou(client, ou, active, recursive=True, ignoredAccounts=()):
if recursive:
ous = get_sub_ous(client, ou)

account_pager = client.get_paginator('list_accounts_for_parent')
account_pager = client.get_paginator("list_accounts_for_parent")
for ou in ous:
for a in account_pager.paginate(
ParentId=ou['Id']).build_full_result().get(
'Accounts', []):
a['Path'] = ou['Path']
a['Tags'] = {
t['Key']: t['Value'] for t in
client.list_tags_for_resource(ResourceId=a['Id']).get('Tags', ())}
if a['Id'] in ignoredAccounts:
for a in (
account_pager.paginate(ParentId=ou["Id"])
.build_full_result()
.get("Accounts", [])
):
a["Path"] = ou["Path"]
a["Tags"] = {
t["Key"]: t["Value"]
for t in client.list_tags_for_resource(ResourceId=a["Id"]).get(
"Tags", ()
)
}
if a["Id"] in ignoredAccounts:
continue

if active:
if a['Status'] == 'ACTIVE':
if a["Status"] == "ACTIVE":
results.append(a)
else:
results.append(a)
return results


if __name__ == '__main__':
if __name__ == "__main__":
main()
Loading