Skip to content

Commit

Permalink
Merge pull request #357 from dgraeber/feature/deployment-policy
Browse files Browse the repository at this point in the history
adding support for policy attach to bootstrap target role
  • Loading branch information
dgraeber authored Jun 16, 2023
2 parents 9779f5d + 2e5805b commit fc17d09
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch

### New
- adding qualifier support for bootstrap roles
- adding support to attach policies to target role when bootstrapping

### Changes
- raise error if a metadata parameter or value_from parameter is not available
Expand Down
20 changes: 17 additions & 3 deletions docs/source/bootstrapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Usage: seedfarmer bootstrap toolchain [OPTIONS]
Options:
-p, --project TEXT Project identifier
-t, --trusted-principal TEXT ARN of Principals trusted to assume the
Toolchain Role
Toolchain Role. This can be used multiple
times to create a list.
-b, --permissions-boundary TEXT
ARN of a Managed Policy to set as the
Permission Boundary on the Toolchain Role
Expand All @@ -23,7 +24,15 @@ Options:
deploy [default: no-synth]
--profile TEXT The AWS profile to initiate a session
--region TEXT AWS region to use
--qualifier TEXT A qualifier to append to toolchain role (alpha-numeric char max length of 6)
--qualifier TEXT A qualifier to append to toolchain role
(alpha-numeric char max length of 6)
-pa, --policy-arn TEXT ARN of existing Policy to attach to Target
Role (Deploymenmt Role) This can be use
multiple times, but EACH policy MUST be
valid in the Target Account. The `--as-
target` flag must be used if passing in
policy arns as they are applied to the
Deployment Role only.
--debug / --no-debug Enable detail logging [default: no-debug]
--help Show this message and exit.
```
Expand Down Expand Up @@ -54,7 +63,12 @@ Options:
deploy [default: no-synth]
--profile TEXT The AWS profile to initiate a session
--region TEXT AWS region to use
--qualifier TEXT A qualifier to append to target role (alpha-numeric char max length of 6)
--qualifier TEXT A qualifier to append to target role (alpha-
numeric char max length of 6)
-pa, --policy-arn TEXT ARN of existing Policy to attach to Target
Role (Deploymenmt Role) This can be use
multiple times to create a list, but EACH
policy MUST be valid in the Target Account
--debug / --no-debug Enable detail logging [default: no-debug]
--help Show this message and exit.
```
Expand Down
30 changes: 29 additions & 1 deletion seedfarmer/cli_groups/_bootstrap_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def bootstrap() -> None:
@click.option(
"--trusted-principal",
"-t",
help="ARN of Principals trusted to assume the Toolchain Role",
help="""ARN of Principals trusted to assume the Toolchain Role.
This can be used multiple times to create a list.""",
multiple=True,
required=False,
default=[],
Expand Down Expand Up @@ -100,11 +101,23 @@ def bootstrap() -> None:
help="A qualifier to append to toolchain role (alpha-numeric char max length of 6)",
required=False,
)
@click.option(
"--policy-arn",
"-pa",
help="""ARN of existing Policy to attach to Target Role (Deploymenmt Role)
This can be use multiple times, but EACH policy MUST be valid in the Target Account.
The `--as-target` flag must be used if passing in policy arns as they are applied to
the Deployment Role only.""",
multiple=True,
required=False,
default=[],
)
@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
def bootstrap_toolchain(
project: Optional[str],
trusted_principal: List[str],
permissions_boundary: Optional[str],
policy_arn: Optional[List[str]],
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
Expand All @@ -117,10 +130,14 @@ def bootstrap_toolchain(
if project is None:
project = _load_project()
_logger.debug("Bootstrapping a Toolchain account for Project %s", project)
if len(policy_arn) > 0 and not as_target: # type: ignore
raise click.ClickException("Cannot set PolicyARNS when the `-as-target` flag is not set.")

bootstrap_toolchain_account(
project_name=project,
principal_arns=trusted_principal,
permissions_boundary_arn=permissions_boundary,
policy_arns=policy_arn,
profile=profile,
qualifier=qualifier,
region_name=region,
Expand Down Expand Up @@ -179,11 +196,21 @@ def bootstrap_toolchain(
help="A qualifier to append to target role (alpha-numeric char max length of 6)",
required=False,
)
@click.option(
"--policy-arn",
"-pa",
help="""ARN of existing Policy to attach to Target Role (Deploymenmt Role)
This can be use multiple times to create a list, but EACH policy MUST be valid in the Target Account""",
multiple=True,
required=False,
default=[],
)
@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
def bootstrap_target(
project: Optional[str],
toolchain_account: str,
permissions_boundary: Optional[str],
policy_arn: Optional[List[str]],
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
Expand All @@ -202,5 +229,6 @@ def bootstrap_target(
region_name=region,
qualifier=qualifier,
permissions_boundary_arn=permissions_boundary,
policy_arns=policy_arn,
synthesize=synth,
)
14 changes: 12 additions & 2 deletions seedfarmer/commands/_bootstrap_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,18 @@ def get_toolchain_template(


def get_deployment_template(
toolchain_role_arn: str, project_name: str, role_name: str, permissions_boundary_arn: Optional[str] = None
toolchain_role_arn: str,
project_name: str,
role_name: str,
policy_arns: Optional[List[str]],
permissions_boundary_arn: Optional[str] = None,
) -> Dict[Any, Any]:
with open((os.path.join(CLI_ROOT, "resources/deployment_role.template")), "r") as f:
role = yaml.safe_load(f)
if permissions_boundary_arn:
role["Resources"]["DeploymentRole"]["Properties"]["PermissionsBoundary"] = permissions_boundary_arn

if policy_arns:
role["Resources"]["DeploymentRole"]["Properties"]["ManagedPolicyArns"] = policy_arns
template = Template(json.dumps(role))
t = template.render(
{"toolchain_role_arn": toolchain_role_arn, "project_name": project_name, "role_name": role_name}
Expand All @@ -76,6 +81,7 @@ def bootstrap_toolchain_account(
project_name: str,
principal_arns: List[str],
permissions_boundary_arn: Optional[str] = None,
policy_arns: Optional[List[str]] = None,
qualifier: Optional[str] = None,
profile: Optional[str] = None,
region_name: Optional[str] = None,
Expand Down Expand Up @@ -105,6 +111,7 @@ def bootstrap_toolchain_account(
permissions_boundary_arn=permissions_boundary_arn,
profile=profile,
region_name=region_name,
policy_arns=policy_arns,
session=session,
)
else:
Expand All @@ -115,6 +122,7 @@ def bootstrap_toolchain_account(
project_name=project_name,
qualifier=cast(str, qualifier),
permissions_boundary_arn=permissions_boundary_arn,
policy_arns=policy_arns,
profile=profile,
region_name=region_name,
session=None,
Expand All @@ -131,6 +139,7 @@ def bootstrap_target_account(
profile: Optional[str] = None,
region_name: Optional[str] = None,
session: Optional[Session] = None,
policy_arns: Optional[List[str]] = None,
synthesize: bool = False,
) -> Optional[Dict[Any, Any]]:

Expand All @@ -146,6 +155,7 @@ def bootstrap_target_account(
toolchain_role_arn=toolchain_role_arn,
project_name=project_name,
role_name=role_stack_name,
policy_arns=policy_arns if policy_arns else None,
permissions_boundary_arn=permissions_boundary_arn,
)
_logger.debug((json.dumps(template, indent=4)))
Expand Down
53 changes: 53 additions & 0 deletions test/unit-test/test_cli_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,46 @@ def test_bootstrap_toolchain_only(mocker):
)


@pytest.mark.bootstrap
def test_bootstrap_toolchain_only_with_qualifier(mocker):
# Bootstrap an Account As Target
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_target_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_toolchain_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.apply_deploy_logic", return_value=None)
_test_command(
sub_command=bootstrap,
options=[
"toolchain",
"--trusted-principal",
"arn:aws:iam::123456789012:role/AdminRole",
"--qualifier",
"testit",
"--debug",
],
exit_code=0,
)


@pytest.mark.bootstrap
def test_bootstrap_toolchain_only_with_policies_fail(mocker):
# Bootstrap an Account As Target
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_target_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_toolchain_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.apply_deploy_logic", return_value=None)
_test_command(
sub_command=bootstrap,
options=[
"toolchain",
"--trusted-principal",
"arn:aws:iam::123456789012:role/AdminRole",
"-pa",
"arn:aws:iam::aws:policy/AdministratorAccess",
"--debug",
],
exit_code=1,
)


@pytest.mark.bootstrap
def test_bootstrap_target_account(mocker):
# Bootstrap an Account As Target
Expand All @@ -199,6 +239,19 @@ def test_bootstrap_target_account(mocker):
)


@pytest.mark.bootstrap
def test_bootstrap_target_account_with_qualifier(mocker):
# Bootstrap an Account As Target
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_target_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_toolchain_account", return_value=None)
mocker.patch("seedfarmer.commands._bootstrap_commands.apply_deploy_logic", return_value=None)
_test_command(
sub_command=bootstrap,
options=["target", "--toolchain-account", "123456789012", "--qualifier", "testit", "--debug"],
exit_code=0,
)


@pytest.mark.apply
def test_apply_missing_deployment():
deployment_manifest = f"{_TEST_ROOT}/manifests/test-missing-deployment-manifest/deployment.yaml"
Expand Down
34 changes: 34 additions & 0 deletions test/unit-test/test_commands_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ def test_bootstrap_toolchain_account_synth_with_qualifier_fail(mocker, session):
)


@pytest.mark.commands
@pytest.mark.commands_bootstrap
@pytest.mark.parametrize("session", [boto3.Session()])
def test_bootstrap_toolchain_account_with_policies(mocker, session):

mocker.patch("seedfarmer.commands._bootstrap_commands.apply_deploy_logic", return_value="")
mocker.patch("seedfarmer.commands._bootstrap_commands.bootstrap_target_account", return_value="")
bc.bootstrap_toolchain_account(
project_name="testing",
principal_arns=["arn:aws:iam::123456789012:role/AdminRole"],
policy_arns=["arn:aws:iam::aws:policy/AdministratorAccess", "arn:aws:iam::aws:policy/ReadOnlyAccess"],
permissions_boundary_arn=None,
region_name="us-east-1",
synthesize=False,
as_target=False,
)


@pytest.mark.commands
@pytest.mark.commands_bootstrap
@pytest.mark.parametrize("session", [boto3.Session()])
Expand Down Expand Up @@ -235,3 +253,19 @@ def test_bootstrap_target_account_with_qualifier_fail(mocker, session):
synthesize=False,
session=session,
)


@pytest.mark.commands
@pytest.mark.commands_bootstrap
@pytest.mark.parametrize("session", [boto3.Session()])
def test_bootstrap_target_account_with_policies(mocker, session):
mocker.patch("seedfarmer.commands._bootstrap_commands.apply_deploy_logic", return_value="")
bc.bootstrap_target_account(
toolchain_account_id="123456789012",
project_name="testing",
policy_arns=["arn:aws:iam::aws:policy/AdministratorAccess", "arn:aws:iam::aws:policy/ReadOnlyAccess"],
permissions_boundary_arn=None,
region_name="us-east-1",
synthesize=False,
session=session,
)

0 comments on commit fc17d09

Please sign in to comment.