Skip to content

Commit

Permalink
Use typed functions and explicit paging (#71)
Browse files Browse the repository at this point in the history
* Use typed functions and explicit paging

Replace undocumented function _build_full_result.

* Use ListChildren to avoid permission changes

* Tidy up minimum permissions

No permissions are required to perform `sts:GetCallerIdentity`.

https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html
  • Loading branch information
iainelder authored Apr 30, 2023
1 parent 07f3130 commit 2a34b5b
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 32 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@ Cove with appropriate [arguments](#arguments)

Cove will not execute a function call in the account it's called from.

Default IAM requirements are:
In the Botocove calling account the minimum IAM requirements are:

In the Botocove calling account:

- Base requirements `sts:assumerole` and `sts:get-caller-identity`
- To run against an entire AWS Organization and capture account metadata:
`organizations:list-accounts`
- To run against specific Organizational Units: `organizations:list-children`
- `sts:AssumeRole` on all of the roles in the target accounts.
- `organizations:ListAccounts` to run against an AWS organization and capture
account metadata.
- `organizations:ListChildren` to run against specific OUs.

In the organization member accounts:

Expand Down
47 changes: 22 additions & 25 deletions botocove/cove_host_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@
Set,
Tuple,
Union,
cast,
)

import boto3
from boto3.session import Session
from botocore.config import Config
from botocore.exceptions import ClientError
from mypy_boto3_organizations.client import OrganizationsClient
from mypy_boto3_organizations.type_defs import (
AccountTypeDef,
ListChildrenResponseTypeDef,
)
from mypy_boto3_organizations.type_defs import AccountTypeDef
from mypy_boto3_sts.client import STSClient
from mypy_boto3_sts.type_defs import PolicyDescriptorTypeTypeDef

Expand Down Expand Up @@ -261,11 +257,11 @@ def _get_all_accounts_by_organization_units(
def _get_all_child_ous(self, parent_ou: str, ou_list: List[str]) -> None:
"""Depth-first recursion mutates the current_ou_list present in the calling
function to establish all children of a parent OU"""

child_ous = self._get_child_ous(parent_ou)
child_ous_list = [ou["Id"] for ou in child_ous["Children"]]
ou_list.extend(child_ous_list)
ou_list.extend(child_ous)

for ou in child_ous_list:
for ou in child_ous:
self._get_all_child_ous(ou, ou_list)

def _get_accounts_by_organization_units(
Expand All @@ -276,15 +272,14 @@ def _get_accounts_by_organization_units(

for ou in organization_units:
ou_children = self._get_child_accounts(ou)
account_list.extend(acc["Id"] for acc in ou_children["Children"])
account_list.extend(ou_children)

return account_list

def _get_active_org_accounts(self) -> Set[str]:
"""
Captures all account metadata into self.account_data for future lookup
and returns a set of account IDs in the AWS organization.
"""
"""Captures all account metadata into self.account_data for future lookup and
returns a set of account IDs in the AWS organization."""

pages = self.org_client.get_paginator("list_accounts").paginate()
self.account_data: Dict[str, AccountTypeDef] = {
account["Id"]: account
Expand All @@ -296,14 +291,15 @@ def _get_active_org_accounts(self) -> Set[str]:
return set(self.account_data.keys())

@lru_cache()
def _get_child_ous(self, parent_ou: str) -> ListChildrenResponseTypeDef:
def _get_child_ous(self, parent_ou: str) -> List[str]:
"""List the child organizational units (OUs) of the parent OU. Just the ID
is needed to traverse the organization tree."""

try:
return cast(
ListChildrenResponseTypeDef,
self.org_client.get_paginator("list_children")
.paginate(ChildType="ORGANIZATIONAL_UNIT", ParentId=parent_ou)
.build_full_result(),
pages = self.org_client.get_paginator("list_children").paginate(
ParentId=parent_ou, ChildType="ORGANIZATIONAL_UNIT"
)
return [ou["Id"] for page in pages for ou in page["Children"]]
except ClientError:
logger.error(
"Cove can only look up target accounts by OU when running from the "
Expand All @@ -314,10 +310,11 @@ def _get_child_ous(self, parent_ou: str) -> ListChildrenResponseTypeDef:
raise

@lru_cache()
def _get_child_accounts(self, parent_ou: str) -> ListChildrenResponseTypeDef:
return cast(
ListChildrenResponseTypeDef,
self.org_client.get_paginator("list_children")
.paginate(ChildType="ACCOUNT", ParentId=parent_ou)
.build_full_result(),
def _get_child_accounts(self, parent_ou: str) -> List[str]:
"""List the child accounts of the parent organizational unit (OU). Just the ID is
needed to access the account. The metadata is enriched elsewhere."""

pages = self.org_client.get_paginator("list_children").paginate(
ParentId=parent_ou, ChildType="ACCOUNT"
)
return [account["Id"] for page in pages for account in page["Children"]]

0 comments on commit 2a34b5b

Please sign in to comment.