Skip to content

feat(portal): manage KMS keys and EC2 networking only when tagged dda-portal:managed - #112

Merged
rvanderwerf merged 2 commits into
mainfrom
feat/exec-policy-tag-conditions
Oct 30, 2026
Merged

rvanderwerf merged 2 commits into
mainfrom
feat/exec-policy-tag-conditions

Conversation

@rvanderwerf

@rvanderwerf rvanderwerf commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review item 4 of the least-privilege install (#103): tag-condition the KMS and EC2 statements, with the tag the portal already uses, dda-portal:managed=true.

Problem

The Portal CloudFormation execution policies allowed:

  • kms:PutKeyPolicy, kms:ScheduleKeyDeletion, kms:DisableKey and the other key management actions on every KMS key in the account and Region (key/*);
  • create, change and delete on any VPC, subnet, route table, internet gateway, security group or VPC endpoint in the Region (Resource: "*").

Change

  • portal-cfn-execution-app-policy.json:
    • key management (DdaKeys) and the DynamoDB key grants (EncryptDdaTablesWithDdaKeys) need aws:ResourceTag/dda-portal:managed=true on the key;
    • alias actions stay on alias/dda-portal/*;
    • kms:CreateKey and kms:TagResource need the tag in the request.
  • New portal-cfn-execution-network-policy.json (dda-cfn-exec-portal-network, a fourth Portal execution policy) replaces the platform policy's Networking statement. The restructured statement no longer fit in the platform policy's 6,144 characters.
    • Creates need the tag in the request.
    • ec2:CreateTags is allowed only while creating (ec2:CreateAction) or on a resource that already has the tag.
    • Every other change needs the tag on the resource.
    • Security group rules go through their tagged group.
    • Describe* is unchanged.
  • lib/managed-tag.ts, applied to the whole app in bin/app.ts: every KMS key, VPC, subnet, route table, internet gateway, security group and VPC endpoint the portal stacks create carries the tag. Route table associations, routes, gateway attachments, rules and aliases have no tags; they are authorized through their tagged parent.
  • iam/setup-account.sh handles the fourth policy. Before it writes the policies, it tags the KMS keys and network resources that the EdgeCVPortal* stacks (nested ones included) already own. This is the rollout for existing installs: run the setup once and the next deploy works. A first install has nothing to tag. A resource it cannot tag (for example a key pending deletion) is reported and the setup continues.

KMS limit. To tag a key at creation, KMS requires kms:TagResource on every key in the account and Region. So the execution role can still add dda-portal:managed=true to another key and then manage it. That is a deliberate, logged two-step action; before this change any key could be changed in one step. EC2 has no such gap. The README documents it.

Live test (account 642746895665, us-east-1, the least-privilege test portal)

  • iam/setup-account.sh portal as an administrator:
    • tagged the 36 KMS keys and network resources of the existing stacks;
    • created dda-cfn-exec-portal-network and updated the app and platform policies;
    • re-bootstrapped with the four execution policies.
  • deploy-portal.sh single-account of this branch as DDAInstaller: all four steps succeeded. The CDK tags updated on the compute, node designer, test runner and build fleet stacks under the conditioned policies. The portal API answered afterwards.
  • Create and delete paths: a throwaway stack (EdgeCVPortalTagConditionTest) with every governed type was deployed and destroyed through the same execution role. It held a VPC, public and isolated subnets, route tables, routes, an internet gateway, an S3 gateway endpoint, a Logs interface endpoint, security groups with rules, and a KMS key with an alias. Both operations succeeded.
  • IAM policy simulator on the filled policies:
    • 14 actions on untagged resources are denied: DeleteVpc, ModifyVpcAttribute, CreateRoute, AuthorizeSecurityGroupIngress, DeleteVpcEndpoints, CreateTags (with and without a request tag), CreateSubnet in an untagged VPC, an untagged CreateVpc, and PutKeyPolicy, ScheduleKeyDeletion, DisableKey, CreateAlias and Decrypt through DynamoDB on an untagged key.
    • The 15th, kms:TagResource with the request tag, is allowed. That is the KMS limit above.
  • CloudTrail of the deploy and throwaway-stack window (21:44–22:10Z, 4,285 events): no denial for the CloudFormation execution role. DDAInstaller was refused two lookups at 21:47:29Z (sts:AssumeRole on the CDK lookup role, ec2:DescribeAvailabilityZones), which nothing needed: both deploys succeeded.
  • Keeping the tag (8f967b1): both policies deny ec2:DeleteTags and kms:UntagResource for the dda-portal:managed key, so a template without the tag (an older portal version) cannot strip it and lock the next change out. Other tags can still be removed. Simulator: removing the managed tag is an explicit deny, removing another tag is allowed. The policies with the deny have been on the test account since 2026-10-30 00:11Z, and the full deploy in feat(portal): a DDA permissions boundary on the execution role and every portal role #113's live test (00:27–00:47Z) succeeded under them.

Tests

  • infrastructure/test/exec-policy-tag-conditions.test.ts (8):
    • the helper tags each governed type and nothing else;
    • every governed resource of the node designer, test runner and build fleet stacks carries the tag, and every other KMS or EC2 type there goes through a tagged parent;
    • bin/app.ts applies it;
    • the policies, setup-account.sh and the type list agree;
    • neither policy lets a deploy remove the managed tag.
  • edge-cv-portal/tests/setup_account.bats (29): the fourth policy and the tagging step. The test's fake aws now handles the stack and tag calls.
  • Results:
    • infrastructure jest: 515 passed;
    • edge-cv-portal/tests/*.bats: 220 passed;
    • IAM CDK synth gate: 40 passed;
    • security preservation: 198 passed, 13 skipped;
    • Access Analyzer: no findings on the platform and network policies. The app policy keeps its two known apigateway:TagResource/UntagResource "unknown action" findings, which the README already explains.
  • Baseline: the ComputeStack fixed baseline's AccountTablesKey gains the tag. test_refreshed_resources_match_synth pins that resource whole, so the baseline was edited by hand, not regenerated.

Upgrading an existing install

The conditions apply once an administrator runs this version's iam/setup-account.sh for the Portal Account. It tags what the earlier deploy created, then writes the conditioned policies. Until then the old policies stay in place, and a deploy of this version still works: CloudFormation adds the tags with the old permissions.

…-portal:managed

Review item 4 of the least-privilege install (#103). The Portal execution
policies let CloudFormation run kms:PutKeyPolicy, kms:ScheduleKeyDeletion and
the other key management actions on every key in the account and Region, and
create, change and delete any VPC, subnet, route table, internet gateway,
security group or VPC endpoint in the Region.

- portal-cfn-execution-app-policy.json: key management (DdaKeys) and the
  DynamoDB grants (EncryptDdaTablesWithDdaKeys) need aws:ResourceTag
  dda-portal:managed=true on the key; alias actions stay limited to
  alias/dda-portal/*; kms:CreateKey and kms:TagResource need the tag in the
  request.
- New portal-cfn-execution-network-policy.json (dda-cfn-exec-portal-network)
  replaces the platform policy's Networking statement, which did not fit in
  6,144 characters once split: creates need the tag in the request,
  ec2:CreateTags only at creation (ec2:CreateAction) or on a tagged resource,
  every other change needs the tag on the resource, security group rules go
  through their tagged group, Describe* is unchanged.
- lib/managed-tag.ts, applied to the whole app in bin/app.ts: every KMS key
  and every VPC, subnet, route table, internet gateway, security group and
  VPC endpoint the portal stacks create carries dda-portal:managed=true.
- iam/setup-account.sh: the fourth Portal execution policy, and before it
  writes the policies it tags the KMS keys and network resources the
  EdgeCVPortal* stacks (nested ones included) already own, so an install made
  before these conditions keeps deploying. A resource it cannot tag (a key
  pending deletion) is reported and the setup goes on.

KMS limit: to tag a key at creation, KMS needs kms:TagResource on every key in
the account and Region, so the execution role can still add
dda-portal:managed=true to another key and then manage it. EC2 has no such
gap. The README says so.

Tests: infrastructure/test/exec-policy-tag-conditions.test.ts (7): the helper
tags each governed type and nothing else; every governed resource of the node
designer, test runner and build fleet stacks carries the tag and every other
KMS or EC2 type there goes through a tagged parent; bin/app.ts applies it;
the policies, setup-account.sh and the type list agree. setup_account.bats
(29): the fourth policy, the tagging step, and the fake aws's stack and tag
calls. Baseline: in the ComputeStack fixed baseline, AccountTablesKey gains
the tag (test_refreshed_resources_match_synth pins that resource whole),
edited by hand, not regenerated.
A template without the managed tag (an older portal version) would make
CloudFormation delete it from the keys and network resources, and the next
change to them would then be refused. Both policies now deny ec2:DeleteTags
and kms:UntagResource for that tag key; other tags can still be removed.
Simulator: removing the managed tag is an explicit deny, removing another tag
is allowed. Test: exec-policy-tag-conditions.test.ts (8).
@rvanderwerf
rvanderwerf merged commit 2d15760 into main Oct 30, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant