Repository navigation
infra: remote locked state, confirmed destroy, resolving SSRF guard - #273
Merged
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Three things in the pipeline that creates and destroys real infrastructure. State lived in the Jenkins workspace. A workspace wipe or an agent change orphaned a VPC, NAT gateway, ECS services and a Cloud Map namespace that kept billing and that the next run could not destroy, because it no longer had the state describing them. versions.tf declares an S3 backend now, deliberately empty: the bucket, lock table and region come from -backend-config at init, so the file carries no account-specific names and a missing one fails the init rather than quietly writing state to disk again. terraform destroy -auto-approve ran unattended. Against state the run did not create that destroys whatever the state happens to describe, and there is no undo, so it waits on an input step first. The TARGET_URL guard matched regexes against the whole URL string. Every one of these got past it: http://user@10.0.0.1/ (the pattern saw "http://user"), http://0.0.0.0/, http://[::1]/, http://2130706433/, and any DNS name pointing at 169.254.169.254. The check now lives in infra/jenkins/validate_target_url.py: it parses the URL, refuses userinfo, resolves the host, and requires every address it resolves to -- not just the first -- to be global. ipaddress.is_global covers the whole special-purpose registry rather than the handful of ranges anyone remembers to write out. Python rather than Groovy for two reasons: it has unit tests there and the pipeline has none here, and resolving a hostname from Groovy needs script approval on a sandboxed controller, which is a thing people grant once and forget. Seven tests on the validator with DNS stubbed, covering every bypass above, plus three asserting the pipeline and backend themselves. All fail on the old files. Note: the issue also asks for disableConcurrentBuilds(); it is already there, at Jenkinsfile:96. Nothing here was applied. This is a PR, as the issue asks. Signed-off-by: kurok <22548029+kurok@users.noreply.github.com>
kurok
force-pushed
the
infra/remote-state-and-ssrf
branch
from
September 19, 2026 15:09
f4ac5fe to
5009d2e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #256. Nothing here was applied — this is a PR, as the issue asks.
State lived in the Jenkins workspace
A workspace wipe or an agent change orphaned a VPC, NAT gateway, ECS services and a Cloud Map namespace that kept billing and that the next run could not destroy, because it no longer had the state describing them.
versions.tfdeclares an S3 backend now, deliberately empty: bucket, lock table and region come from-backend-configat init. The file carries no account-specific names, and a missing one fails the init rather than quietly writing state to disk again.destroy -auto-approveran unattendedAgainst state the run did not create, that destroys whatever the state happens to describe, and there is no undo. It waits on an
inputstep first.The SSRF guard matched regexes against the URL string
Every one of these got past it:
http://user@10.0.0.1/http://userhttp://0.0.0.0/·http://[::1]/·http://2130706433/169.254.169.254The check now lives in
infra/jenkins/validate_target_url.py: it parses the URL, refuses userinfo, resolves the host, and requires every address it resolves to — not just the first — to be global.ipaddress.is_globalcovers the whole special-purpose registry rather than the handful of ranges anyone remembers to write out.Python rather than Groovy, for two reasons: it has unit tests there and the pipeline has none here; and resolving a hostname from Groovy needs script approval on a sandboxed controller, which is a thing people grant once and forget.
Tests
Seven on the validator with DNS stubbed — covering every bypass above, including a name answering with one public and one private address, which is the standard way past a check that looks at a single answer. Plus three asserting the backend block, the destroy gate and the validator wiring. All fail on the old files.
One correction to the issue
It lists "no
disableConcurrentBuilds()". It is already there, atJenkinsfile:96, so nothing was needed.Acceptance
ruff format/checkclean,mypyclean ✓uv run pytest— 1889 passed, coverage 95.67% ✓Worth your judgement before merging
TF_STATE_BUCKET/TF_LOCK_TABLEdefault topywrkr-terraform-state/pywrkr-terraform-locks. Those must exist, and the firstinitagainst the new backend will ask to migrate the existing local state. That is a one-off manual step on the controller, not something this PR can do.python3on the agent. That is already assumed —generate_report.pyruns there too.