Skip to content

Harden YOLO: fix RDS ingress, remove dead EC2 SG, lock down S3 buckets#38

Merged
stevethomas merged 5 commits into
mainfrom
security-hardening
May 23, 2026
Merged

Harden YOLO: fix RDS ingress, remove dead EC2 SG, lock down S3 buckets#38
stevethomas merged 5 commits into
mainfrom
security-hardening

Conversation

@stevethomas
Copy link
Copy Markdown
Member

@stevethomas stevethomas commented May 23, 2026

Hey, I made a thing! 🥳

A focused security pass over the Fargate deploy path.

What problems are you solving?

  • Vestigial EC2 security group. An EC2/ASG-era leftover that opened SSH/22 (to an ipify-derived /32) and HTTP/80 but held no workloads — Fargate tasks run in the task SG. Removed the step, its enum cases, and the orphaned launchTemplate()/keyPair()/ec2SecurityGroup() helpers in UsesEc2.
  • Latent RDS access bug. The RDS ingress rule authorised 3306 from the (empty) EC2 SG, so Fargate tasks were never actually granted DB access. Repointed to the task SG, applied additively — the rule is matched by content and never revoked. The RDS SG step moves to sync:compute (after the task SG it now references).
  • Secret S3 buckets had no Block Public Access. The artefact bucket stores .env files but, unlike the asset bucket, had no BPA. Added all four BPA flags + versioning, reconciled on every sync. New app buckets get BPA on create only.

Is there anything the reviewer needs to know to deploy this?

  • The RDS change is purely additive — on existing environments the old EC2 SG and its RDS ingress rule are left untouched (the EC2 SG becomes an inert orphan; remove by hand or via a future yolo destroy if wanted). New environments never get an EC2 SG.
  • Artefact-bucket BPA + versioning reconcile onto existing buckets on the next sync (safe — never public). App-bucket BPA is create-only, so apps that legitimately serve public objects aren't broken.
  • The public/ CDN scoping (dotfiles + source maps excluded) already landed on main as 169d94e, so it isn't in this diff.
  • No AWS commands were run — recommend yolo sync:network --dry-run then sync:compute --dry-run against a real account to confirm the RDS rule lands additively before a real sync.
  • 213 tests pass (11 new), PHPStan + Pint clean.

Follow-ups (not in this PR, tracked in LPX-623): move secrets to runtime injection (SSM param versions, release-pinned in the task definition) so .env stops being baked into the image; ECR tag immutability; extract the shared Block-Public-Access config; more dead EC2 code in UsesIam/EnsureIamRolesExistStep.

🤖 Generated with Claude Code

stevethomas and others added 2 commits May 23, 2026 09:42
The EC2 security group was an EC2/ASG-era leftover: Fargate tasks run in the
task SG, so it held no workloads while still opening SSH/22 (to an ipify-derived
IP) and HTTP/80. Its only live use was the RDS ingress rule, which authorised
3306 from the EC2 SG — so Fargate tasks were never actually granted DB access.

RDS ingress now targets the task SG, applied additively: a tagged rule is
ensured and nothing is ever revoked, so any out-of-band access is preserved.
The RDS SG step moves to sync:compute (after the task SG it references). Also
removes the orphaned launch-template/keyPair helpers in UsesEc2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The artefact bucket stores .env files but, unlike the asset bucket, had no
Block Public Access. Enable all four BPA flags plus versioning (recovery and
tamper-evidence for .env), reconciled on every sync. New app buckets get BPA
on create only, so existing buckets that legitimately serve public objects
aren't broken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/Commands/SyncComputeCommand.php Outdated
Comment thread src/Commands/SyncNetworkCommand.php Outdated
stevethomas and others added 3 commits May 23, 2026 10:54
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The name-based arn() lookup already throws ResourceDoesNotExistException if the
task SG is missing — the defensive exists() guard was both redundant
(sync:compute provisions it first) and wrong, silently swallowing a real
failure instead of surfacing it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The tag only served to pre-check whether the rule already existed — a guarantee
AWS already provides (it rejects duplicate permissions). That made it the same
redundant pre-check as the exists() guard, and less robust: an untagged, manually
added identical rule slips past the tag filter and then fails on the duplicate.
Idempotency is now by content (protocol + port + source SG), which also spots
out-of-band duplicates. The tag earns its place only for drift reconciliation
(SyncLoadBalancerSecurityGroupStep), which this step doesn't do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@stevethomas stevethomas merged commit a381c30 into main May 23, 2026
5 checks passed
@stevethomas stevethomas deleted the security-hardening branch May 23, 2026 01:36
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