Skip to content

Commit

Permalink
Add External Id Conditions to Deployment Roles (#1521)
Browse files Browse the repository at this point in the history
### Feature or Bugfix
<!-- please choose -->
- Enhancement


### Detail 
- Add external ID condition to roles created in deployment account and
used by tooling account (frontend config role, stacks updater role, and
S3 Deployment role)

### Relates
N/A

### Security
Please answer the questions below briefly where applicable, or write
`N/A`. Based on
[OWASP 10](https://owasp.org/Top10/en/).

- Does this PR introduce or modify any input fields or queries - this
includes
fetching data from storage outside the application (e.g. a database, an
S3 bucket)?
  - Is the input sanitized?
- What precautions are you taking before deserializing the data you
consume?
  - Is injection prevented by parametrizing queries?
  - Have you ensured no `eval` or similar functions are used?
- Does this PR introduce any functionality or component that requires
authorization?
- How have you ensured it respects the existing AuthN/AuthZ mechanisms?
  - Are you logging failed auth attempts?
- Are you using or adding any cryptographic features?
  - Do you use a standard proven implementations?
  - Are the used keys controlled by the customer? Where are they stored?
- Are you introducing any new policies/roles/users?
  - Have you used the least-privilege principle? How?


By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
noah-paige committed Sep 10, 2024
1 parent 1804aab commit cf3b457
Show file tree
Hide file tree
Showing 7 changed files with 28 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ deploy-image:
docker push ${account}.dkr.ecr.${region}.amazonaws.com/${repo}:${image-tag}

assume-role:
aws sts assume-role --role-arn "arn:aws:iam::${REMOTE_ACCOUNT_ID}:role/${REMOTE_ROLE}" --role-session-name "session1" >.assume_role_json
aws sts assume-role --role-arn "arn:aws:iam::${REMOTE_ACCOUNT_ID}:role/${REMOTE_ROLE}" --external-id ${EXTERNAL_ID} --role-session-name "session1" >.assume_role_json
echo "export AWS_ACCESS_KEY_ID=$$(cat .assume_role_json | jq '.Credentials.AccessKeyId' -r)" >.env.assumed_role
echo "export AWS_SECRET_ACCESS_KEY=$$(cat .assume_role_json | jq '.Credentials.SecretAccessKey' -r)" >>.env.assumed_role
echo "export AWS_SESSION_TOKEN=$$(cat .assume_role_json | jq '.Credentials.SessionToken' -r)" >>.env.assumed_role
Expand Down
2 changes: 2 additions & 0 deletions deploy/stacks/backend_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .sqs import SqsStack
from .trigger_function_stack import TriggerFunctionStack
from .vpc import VpcStack
from .iam_utils import get_tooling_account_external_id


class BackendStack(Stack):
Expand Down Expand Up @@ -138,6 +139,7 @@ def __init__(
f'{resource_prefix}-{envname}-frontend-config-role',
role_name=f'{resource_prefix}-{envname}-frontend-config-role',
assumed_by=iam.AccountPrincipal(tooling_account_id),
external_ids=[get_tooling_account_external_id(self.account)],
)
cross_account_frontend_config_role.add_to_policy(
iam.PolicyStatement(
Expand Down
2 changes: 2 additions & 0 deletions deploy/stacks/cloudfront.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .pyNestedStack import pyNestedClass
from .solution_bundling import SolutionBundling
from .waf_rules import get_waf_rules
from .iam_utils import get_tooling_account_external_id


class CloudfrontDistro(pyNestedClass):
Expand Down Expand Up @@ -227,6 +228,7 @@ def __init__(
f'S3DeploymentRole{envname}',
role_name=f'{resource_prefix}-{envname}-S3DeploymentRole',
assumed_by=iam.AccountPrincipal(tooling_account_id),
external_ids=[get_tooling_account_external_id(self.account)],
)
resources_for_cross_account = []
resources_for_cross_account.append(f'{cloudfront_bucket.bucket_arn}/*')
Expand Down
2 changes: 2 additions & 0 deletions deploy/stacks/cognito.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .pyNestedStack import pyNestedClass
from .solution_bundling import SolutionBundling
from .waf_rules import get_waf_rules
from .iam_utils import get_tooling_account_external_id


class IdpStack(pyNestedClass):
Expand Down Expand Up @@ -198,6 +199,7 @@ def __init__(
f'{resource_prefix}-{envname}-cognito-config-role',
role_name=f'{resource_prefix}-{envname}-cognito-config-role',
assumed_by=iam.AccountPrincipal(tooling_account_id),
external_ids=[get_tooling_account_external_id(self.account)],
)
cross_account_frontend_config_role.add_to_policy(
iam.PolicyStatement(
Expand Down
14 changes: 10 additions & 4 deletions deploy/stacks/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .pyNestedStack import pyNestedClass
from .run_if import run_if
from .deploy_config import deploy_config
from .iam_utils import get_tooling_account_external_id


class ContainerStack(pyNestedClass):
Expand Down Expand Up @@ -562,10 +563,15 @@ def create_cicd_stacks_updater_role(self, envname, resource_prefix, tooling_acco
self,
id=f'StackUpdaterCBRole{envname}',
role_name=f'{resource_prefix}-{envname}-cb-stackupdater-role',
assumed_by=iam.CompositePrincipal(
iam.ServicePrincipal('codebuild.amazonaws.com'),
iam.AccountPrincipal(tooling_account_id),
),
assumed_by=iam.AccountPrincipal(tooling_account_id),
external_ids=[get_tooling_account_external_id(self.account)],
)
cicd_stacks_updater_role.assume_role_policy.add_statements(
iam.PolicyStatement(
effect=iam.Effect.ALLOW,
principals=[iam.ServicePrincipal('codebuild.amazonaws.com')],
actions=['sts:AssumeRole'],
)
)
cicd_stacks_updater_role.add_to_policy(
iam.PolicyStatement(
Expand Down
5 changes: 5 additions & 0 deletions deploy/stacks/iam_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import hashlib


def get_tooling_account_external_id(account: str):
return hashlib.sha256(str(account).encode('UTF-8')).hexdigest()[:12]
7 changes: 6 additions & 1 deletion deploy/stacks/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .codeartifact import CodeArtifactStack
from .ecr_stage import ECRStage
from .vpc import VpcStack
from .iam_utils import get_tooling_account_external_id


class PipelineStack(Stack):
Expand Down Expand Up @@ -683,6 +684,7 @@ def set_approval_tests_stage(
'echo "[profile buildprofile]" > ~/.aws/config',
f'echo "role_arn = {frontend_deployment_role_arn}" >> ~/.aws/config',
'echo "credential_source = EcsContainer" >> ~/.aws/config',
f'echo "external_id = {get_tooling_account_external_id(target_env["account"])}" >> ~/.aws/config',
'aws sts get-caller-identity --profile buildprofile',
f'export COGNITO_CLIENT=$(aws ssm get-parameter --name /dataall/{target_env["envname"]}/cognito/appclient --profile buildprofile --output text --query "Parameter.Value")',
f'export API_ENDPOINT=$(aws ssm get-parameter --name /dataall/{target_env["envname"]}/apiGateway/backendUrl --profile buildprofile --output text --query "Parameter.Value")',
Expand Down Expand Up @@ -728,6 +730,7 @@ def set_stacks_updater_stage(
'echo "[profile buildprofile]" > ~/.aws/config',
f'echo "role_arn = arn:aws:iam::{target_env["account"]}:role/{self.resource_prefix}-{target_env["envname"]}-cb-stackupdater-role" >> ~/.aws/config',
'echo "credential_source = EcsContainer" >> ~/.aws/config',
f'echo "external_id = {get_tooling_account_external_id(target_env["account"])}" >> ~/.aws/config',
'aws sts get-caller-identity --profile buildprofile',
f"export cluster_name=$(aws ssm get-parameter --name /dataall/{target_env['envname']}/ecs/cluster/name --profile buildprofile --output text --query 'Parameter.Value')",
f"export private_subnets=$(aws ssm get-parameter --name /dataall/{target_env['envname']}/ecs/private_subnets --profile buildprofile --output text --query 'Parameter.Value')",
Expand Down Expand Up @@ -788,6 +791,7 @@ def set_cloudfront_stage(self, target_env):
'echo "[profile buildprofile]" > ~/.aws/config',
f'echo "role_arn = arn:aws:iam::{target_env["account"]}:role/{self.resource_prefix}-{target_env["envname"]}-S3DeploymentRole" >> ~/.aws/config',
'echo "credential_source = EcsContainer" >> ~/.aws/config',
f'echo "external_id = {get_tooling_account_external_id(target_env["account"])}" >> ~/.aws/config',
'aws sts get-caller-identity --profile buildprofile',
'export AWS_PROFILE=buildprofile',
'pip install boto3==1.34.35',
Expand Down Expand Up @@ -826,7 +830,7 @@ def set_cloudfront_stage(self, target_env):
),
commands=[
f'aws codeartifact login --tool pip --repository {self.codeartifact.codeartifact_pip_repo_name} --domain {self.codeartifact.codeartifact_domain_name} --domain-owner {self.codeartifact.domain.attr_owner}',
f"make assume-role REMOTE_ACCOUNT_ID={target_env['account']} REMOTE_ROLE={self.resource_prefix}-{target_env['envname']}-S3DeploymentRole",
f"make assume-role REMOTE_ACCOUNT_ID={target_env['account']} REMOTE_ROLE={self.resource_prefix}-{target_env['envname']}-S3DeploymentRole EXTERNAL_ID={get_tooling_account_external_id(target_env['account'])}",
'. ./.env.assumed_role',
'aws sts get-caller-identity',
'export AWS_DEFAULT_REGION=us-east-1',
Expand Down Expand Up @@ -927,6 +931,7 @@ def set_albfront_stage(self, target_env, repository_name):
'echo "[profile buildprofile]" > ~/.aws/config',
f'echo "role_arn = {frontend_deployment_role_arn}" >> ~/.aws/config',
'echo "credential_source = EcsContainer" >> ~/.aws/config',
f'echo "external_id = {get_tooling_account_external_id(target_env["account"])}" >> ~/.aws/config',
'aws sts get-caller-identity --profile buildprofile',
'export AWS_PROFILE=buildprofile',
'pip install boto3==1.34.35',
Expand Down

0 comments on commit cf3b457

Please sign in to comment.