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

feat: add paths #787

Merged
merged 13 commits into from
Feb 10, 2025
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and [Keep a Ch
## Unreleased

### New
- adding support for IAM paths

### Changes

Expand Down
11 changes: 11 additions & 0 deletions docs/source/bootstrapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ Options:
--region TEXT AWS region to use
--qualifier TEXT A qualifier to append to toolchain role
(alpha-numeric char max length of 6)
--role-prefix TEXT An IAM path prefix to use with the
seedfarmer roles.
--policy-prefix TEXT An IAM path prefix to use with the
seedfarmer policies.
-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
Expand Down Expand Up @@ -65,6 +69,8 @@ Options:
--region TEXT AWS region to use
--qualifier TEXT A qualifier to append to target role (alpha-
numeric char max length of 6)
--role-prefix TEXT An IAM path prefix to use with the seedfarmer
roles.
-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
Expand All @@ -84,6 +90,11 @@ We have added support for the use of a qualifier for the toolchain role and the
The qualifier post-pends a 6 chars alpha-numeric string to the deployment role and toolchain role. The qualifier **MUST BE THE SAME ON THE TOOLCHAIN ROLE AND EACH TARGET ROLE.**


## IAM Paths Prefixes for Toolchain, Target Roles, and Policies
We have added support for the use of a IAM Paths for the toolchain role, target account deployment role(s), and policie(s). Using IAM Paths you can create groupings and design a logical separation to simplify permissions management. A common example in organizations is using Service Control Policies enforcing logical separation by team e.g. `/legal/` or `/sales/`, or project name.

A `--role-prefix` and `--policy-prefix` can be used if you want to provide IAM Paths to the roles and policies created by `seed-farmer`. IAM Paths must begin and end with a `/`. More information in [IAM identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html).

## Prepping the Account / Region
`seedfarmer` leverages the AWS CDKv2. This must be bootstrapped in each account/region combination to be used of each target account.

Expand Down
19 changes: 19 additions & 0 deletions docs/source/manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ targetAccountMappings:
npmMirrorSecret: /something/aws-addf-mirror-credentials
pypiMirror: https://pypi.python.org/simple
pypiMirrorSecret: /something/aws-addf-mirror-mirror-credentials
rolePrefix: /
policyPrefix: /
parametersGlobal:
dockerCredentialsSecret: nameofsecret
permissionsBoundaryName: policyname
Expand Down Expand Up @@ -108,6 +110,8 @@ targetAccountMappings:
- **npmMirrorSecret** - the AWS SecretManager to use when setting the mirror (see [Mirror Override](mirroroverride))
- **pypiMirror** - the Pypi mirror to use (see [Mirror Override](mirroroverride))
- **pypiMirrorSecret** - the AWS SecretManager to use when setting the mirror (see [Mirror Override](mirroroverride))
- **rolePrefix** - IAM path prefix to use with seedfarmer roles (see [IAM Path Prefixes](iamprefixes))
- **policyPrefix** - IAM path prefix to use with seedfarmer policies (see [IAM Path Prefixes](iamprefixes))
- **parametersGlobal** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
- **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
- **permissionsBoundaryName** - the name of the [permissions boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) policy to apply to all module-specific roles created
Expand All @@ -118,6 +122,8 @@ targetAccountMappings:
- **npmMirror** - the NPM registry mirror to use (see [Mirror Override](mirroroverride))
- **pypiMirror** - the Pypi mirror to use (see [Mirror Override](mirroroverride))
- **pypiMirrorSecret** - the AWS SecretManager to use when setting the mirror (see [Mirror Override](mirroroverride))
- **rolePrefix** - IAM path prefix to use with seedfarmer roles (see [IAM Path Prefixes](iamprefixes))
- **policyPrefix** - IAM path prefix to use with seedfarmer policies (see [IAM Path Prefixes](iamprefixes))
- **parametersRegional** - these are parameters that apply to all region mappings unless otherwise overridden at the region level
- **dockerCredentialsSecret** - please see [Docker Credentials Secret](dockerCredentialsSecret)
- This is a NAMED PARAMETER...in that `dockerCredentialsSecret` is recognized by `seed-farmer`
Expand Down Expand Up @@ -531,6 +537,19 @@ This would result in the creation of an `_auth` entry in npm config (`.npmrc`) w
npm config set //the-mirror-dns/npm/:_auth="mybase64encodedssltoken"
```

(iamprefixes)=
## IAM Path Prefixes
Using IAM Paths you can create groupings and design a logical separation to simplify permissions management. A common example in organizations is using Service Control Policies enforcing logical separation by team e.g. `/legal/` or `/sales/`, or project name.

It is possible to override the default paths of `/` for `seed-farmer` IAM roles and policies using the deployment manifest using `rolePrefix` and `policyPrefix` at the account/region level. IAM Paths must begin and end with a `/`. More information in [IAM identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html).

There is a level of logic that is followed:
1. if a prefix is defined at the region level --- USE IT... ELSE
2. if a prefix is defined at the account level --- USE IT... ELSE
4. use default `/` prefix

NOTE: the prefixes provided must match the prefixes provided during bootstrap, unless a custom bootstrap is used.

(archivesecret)=
### Archive Secret

Expand Down
18 changes: 18 additions & 0 deletions seedfarmer/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ def version() -> None:
Use only if bootstrapped with this qualifier""",
required=False,
)
@click.option(
"--role-prefix",
default="/",
help="""An IAM path prefix to use with the seedfarmer roles.
Use only if bootstrapped with this path""",
required=False,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes for apply, destroy, synth seem undocumented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On it

@click.option(
"--env-file",
"env_files",
Expand Down Expand Up @@ -129,6 +136,7 @@ def apply(
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
role_prefix: str,
env_files: List[str],
debug: bool,
dry_run: bool,
Expand All @@ -154,6 +162,7 @@ def apply(
profile=profile,
region_name=region,
qualifier=qualifier,
role_prefix=role_prefix,
dryrun=dry_run,
show_manifest=show_manifest,
enable_session_timeout=enable_session_timeout,
Expand Down Expand Up @@ -200,6 +209,13 @@ def apply(
Use only if bootstrapped with this qualifier""",
required=False,
)
@click.option(
"--role-prefix",
default="/",
help="""An IAM path prefix to use with the seedfarmer roles.
Use only if bootstrapped with this path""",
required=False,
)
@click.option(
"--env-file",
"env_files",
Expand Down Expand Up @@ -248,6 +264,7 @@ def destroy(
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
role_prefix: str,
env_files: List[str],
debug: bool,
enable_session_timeout: bool,
Expand All @@ -274,6 +291,7 @@ def destroy(
profile=profile,
region_name=region,
qualifier=qualifier,
role_prefix=role_prefix,
dryrun=dry_run,
show_manifest=show_manifest,
enable_session_timeout=enable_session_timeout,
Expand Down
24 changes: 24 additions & 0 deletions seedfarmer/cli_groups/_bootstrap_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,18 @@ def bootstrap() -> None:
If used, it MUST be used on every seedfarmer command.""",
required=False,
)
@click.option(
"--role-prefix",
default="/",
help="An IAM path prefix to use with the seedfarmer roles.",
required=False,
)
@click.option(
"--policy-prefix",
default="/",
help="An IAM path prefix to use with the seedfarmer policies.",
required=False,
)
@click.option(
"--policy-arn",
"-pa",
Expand All @@ -121,6 +133,8 @@ def bootstrap_toolchain(
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
role_prefix: str,
policy_prefix: str,
as_target: bool,
synth: bool,
debug: bool,
Expand All @@ -140,6 +154,8 @@ def bootstrap_toolchain(
policy_arns=policy_arn,
profile=profile,
qualifier=qualifier,
role_prefix=role_prefix,
policy_prefix=policy_prefix,
region_name=region,
synthesize=synth,
as_target=as_target,
Expand Down Expand Up @@ -197,6 +213,12 @@ def bootstrap_toolchain(
If used on the toolchain account, it should be used here!""",
required=False,
)
@click.option(
"--role-prefix",
default="/",
help="An IAM path prefix to use with the seedfarmer roles.",
required=False,
)
@click.option(
"--policy-arn",
"-pa",
Expand All @@ -215,6 +237,7 @@ def bootstrap_target(
profile: Optional[str],
region: Optional[str],
qualifier: Optional[str],
role_prefix: str,
synth: bool,
debug: bool,
) -> None:
Expand All @@ -229,6 +252,7 @@ def bootstrap_target(
profile=profile,
region_name=region,
qualifier=qualifier,
role_prefix=role_prefix,
permissions_boundary_arn=permissions_boundary,
policy_arns=policy_arn,
synthesize=synth,
Expand Down
9 changes: 8 additions & 1 deletion seedfarmer/cli_groups/_project_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,18 @@ def projectpolicy() -> None:
name="synth",
help="Synth a Project Policy from seed-farmer.",
)
@click.option(
"--policy-prefix",
default="/",
help="An IAM path prefix to use with the policy.",
required=False,
)
@click.option("--debug/--no-debug", default=False, help="Enable detail logging", show_default=True)
def policy_synth(
policy_prefix: str,
debug: bool,
) -> None:
if debug:
enable_debug(format=DEBUG_LOGGING_FORMAT)

get_default_project_policy()
get_default_project_policy(policy_prefix=policy_prefix)
24 changes: 23 additions & 1 deletion seedfarmer/commands/_bootstrap_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def get_toolchain_template(
principal_arn: List[str],
role_name: str,
permissions_boundary_arn: Optional[str] = None,
role_prefix: str = "/",
) -> Dict[Any, Any]:
with open((os.path.join(CLI_ROOT, "resources/toolchain_role.template")), "r") as f:
role = yaml.safe_load(f)
Expand All @@ -49,7 +50,14 @@ def get_toolchain_template(
if permissions_boundary_arn:
role["Resources"]["ToolchainRole"]["Properties"]["PermissionsBoundary"] = permissions_boundary_arn
template = Template(json.dumps(role))
t = template.render({"project_name": project_name, "role_name": role_name, "seedfarmer_version": __version__})
t = template.render(
{
"project_name": project_name,
"role_name": role_name,
"role_prefix": role_prefix,
"seedfarmer_version": __version__,
}
)
return dict(json.loads(t))


Expand All @@ -59,6 +67,8 @@ def get_deployment_template(
role_name: str,
policy_arns: Optional[List[str]],
permissions_boundary_arn: Optional[str] = None,
role_prefix: str = "/",
policy_prefix: str = "/",
) -> Dict[Any, Any]:
with open((os.path.join(CLI_ROOT, "resources/deployment_role.template")), "r") as f:
role = yaml.safe_load(f)
Expand All @@ -72,6 +82,8 @@ def get_deployment_template(
"toolchain_role_arn": toolchain_role_arn,
"project_name": project_name,
"role_name": role_name,
"role_prefix": role_prefix,
"policy_prefix": policy_prefix,
"seedfarmer_version": __version__,
}
)
Expand All @@ -89,6 +101,8 @@ def bootstrap_toolchain_account(
permissions_boundary_arn: Optional[str] = None,
policy_arns: Optional[List[str]] = None,
qualifier: Optional[str] = None,
role_prefix: str = "/",
policy_prefix: str = "/",
profile: Optional[str] = None,
region_name: Optional[str] = None,
synthesize: bool = False,
Expand All @@ -107,6 +121,7 @@ def bootstrap_toolchain_account(
role_name=role_stack_name,
principal_arn=principal_arns,
permissions_boundary_arn=permissions_boundary_arn,
role_prefix=role_prefix,
)
_logger.debug((json.dumps(template, indent=4)))
if not synthesize:
Expand All @@ -124,6 +139,8 @@ def bootstrap_toolchain_account(
toolchain_account_id=session_account_id,
project_name=project_name,
qualifier=cast(str, qualifier),
role_prefix=role_prefix,
policy_prefix=policy_prefix,
permissions_boundary_arn=permissions_boundary_arn,
profile=profile,
region_name=region_name,
Expand Down Expand Up @@ -152,6 +169,8 @@ def bootstrap_target_account(
project_name: str,
permissions_boundary_arn: Optional[str] = None,
qualifier: Optional[str] = None,
role_prefix: str = "/",
policy_prefix: str = "/",
profile: Optional[str] = None,
region_name: Optional[str] = None,
session: Optional[Session] = None,
Expand All @@ -171,6 +190,7 @@ def bootstrap_target_account(
toolchain_account_id=toolchain_account_id,
project_name=project_name,
qualifier=cast(str, qualifier),
role_prefix=role_prefix,
)

template = get_deployment_template(
Expand All @@ -179,6 +199,8 @@ def bootstrap_target_account(
role_name=role_stack_name,
policy_arns=policy_arns if policy_arns else None,
permissions_boundary_arn=permissions_boundary_arn,
role_prefix=role_prefix,
policy_prefix=policy_prefix,
)
_logger.debug((json.dumps(template, indent=4)))
if not synthesize:
Expand Down
Loading