Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ The deployer verifies account/role, live service configuration, source label, EC

ECR may omit its optional severity-count map for a clean image. That response is accepted only after the same completion/freshness checks, with an exact registry/repository/digest match, explicitly empty findings lists, and no remaining page. An absent summary without that evidence, nonempty findings without a summary, or malformed counts fails closed. This requires no additional IAM permissions or changes to registry scanning. See the [ECR response schema](https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_ImageScanFindings.html).

Enhanced scanning also requires `inspector2:ListFindings` and `inspector2:ListCoverage` in the deployment role, its permissions boundary, and the STS session policy. The generated role policy limits these read-only actions to the deployment region. AWS does not support repository-level resource restrictions for these Inspector list actions, so they can read account-wide scan metadata in that region. No Inspector configuration/write actions or parameter/secret access are granted. Existing roles and boundaries must be updated through the private infrastructure repository before adopting this change. See [AWS enhanced-scanning permissions](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning-enhanced-iam.html).

Use a unique CircleCI build number when retrying publication to immutable tags. A failed rollout remains a failure even if ECS rolls back. The script never blindly reverts over another deployment.

## Tests
Expand Down
9 changes: 7 additions & 2 deletions tcdeploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def role_policy(m):
'Resource': arn(m, 'ecs', 'task-definition/' + m['taskFamily'] + ':*')},
{'Effect': 'Allow', 'Action': ['ecs:ListTasks', 'ecs:DescribeTasks'], 'Resource': '*',
'Condition': {'ArnEquals': {'ecs:cluster': arn(m, 'ecs', 'cluster/' + m['cluster'])}}},
# ECR enhanced scan reads depend on these Inspector APIs. They do not
# support resource-level permissions; limit them to the deployment region.
{'Effect': 'Allow', 'Action': ['inspector2:ListFindings', 'inspector2:ListCoverage'], 'Resource': '*',
'Condition': {'StringEquals': {'aws:RequestedRegion': m['region']}}},
{'Effect': 'Allow', 'Action': 'iam:PassRole', 'Resource': [r for r in (m['taskRoleArn'], m['executionRoleArn']) if r],
'Condition': {'StringEquals': {'iam:PassedToService': 'ecs-tasks.amazonaws.com'}}}
]}
Expand Down Expand Up @@ -122,7 +126,8 @@ def trust_policy(m):
'ecr:BatchCheckLayerAvailability', 'ecr:InitiateLayerUpload', 'ecr:UploadLayerPart',
'ecr:CompleteLayerUpload', 'ecr:PutImage', 'ecr:DescribeImageScanFindings',
'ecs:DescribeServices', 'ecs:DescribeTaskDefinition', 'ecs:RegisterTaskDefinition',
'ecs:TagResource', 'ecs:ListTagsForResource', 'ecs:UpdateService', 'ecs:ListTasks', 'ecs:DescribeTasks', 'iam:PassRole'], 'Resource': '*'}]}
'ecs:TagResource', 'ecs:ListTagsForResource', 'ecs:UpdateService', 'ecs:ListTasks', 'ecs:DescribeTasks', 'iam:PassRole',
'inspector2:ListFindings', 'inspector2:ListCoverage'], 'Resource': '*'}]}

# Reviewed service adapters may retain migration/health/CloudFormation ownership
# gates. These capabilities are not exposed as deployment CLI flags. Their IAM
Expand All @@ -139,7 +144,7 @@ def trust_policy(m):
def session_policy(additional_actions=()):
require(set(additional_actions) <= ADAPTER_ACTIONS, 'Unsupported custom deployment capability')
result = deepcopy(SESSION_POLICY)
result['Statement'][1]['Action'] += sorted(set(additional_actions))
result['Statement'][1]['Action'] += sorted(set(additional_actions) - set(result['Statement'][1]['Action']))
require(len(compact(result)) <= 2048, 'Session policy exceeds the STS limit')
return result

Expand Down
14 changes: 14 additions & 0 deletions tests/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,20 @@ def test_policies_bind_identity_and_forbid_parameters(self):
passrole = d.role_policy(m)['Statement'][-1]
self.assertEqual(passrole['Resource'], [m['taskRoleArn'], m['executionRoleArn']])

def test_enhanced_scan_permissions_are_read_only_and_region_limited(self):
m = manifest()
inspector = [s for s in d.role_policy(m)['Statement']
if any(a.startswith('inspector2:') for a in s['Action'])]
self.assertEqual(inspector, [{'Effect': 'Allow',
'Action': ['inspector2:ListFindings', 'inspector2:ListCoverage'], 'Resource': '*',
'Condition': {'StringEquals': {'aws:RequestedRegion': m['region']}}}])
for policy in (d.session_policy(), d.session_policy(d.ADAPTER_ACTIONS)):
actions = policy['Statement'][1]['Action']
self.assertEqual({a for a in actions if a.startswith('inspector2:')},
{'inspector2:ListFindings', 'inspector2:ListCoverage'})
self.assertEqual(len(actions), len(set(actions)))
self.assertLessEqual(len(d.compact(policy)), 2048)


class CredentialTests(unittest.TestCase):
def test_ecs_service_parameter_reaches_the_cli_without_name_collision(self):
Expand Down
Loading