Skip to content

feat: provider cache proxy for parallel terraform init (PROVIDER_CACHE) - #6840

Open
sakkiii wants to merge 11 commits into
runatlantis:mainfrom
sakkiii:feat/provider-cache-proxy
Open

feat: provider cache proxy for parallel terraform init (PROVIDER_CACHE)#6840
sakkiii wants to merge 11 commits into
runatlantis:mainfrom
sakkiii:feat/provider-cache-proxy

Conversation

@sakkiii

@sakkiii sakkiii commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

what

  • Add an opt-in provider caching proxy for Terraform, modeled on Terragrunt's provider cache server, enabled with --provider-cache.
  • Atlantis starts a small HTTP server on localhost that speaks the Terraform Provider Registry Protocol, and points Terraform at it via a host block in a generated ~/.terraformrc CLI config file (merged with the existing TFE credentials block).
  • The proxy forwards provider metadata upstream and rewrites the archive / SHA256SUMS / signature download URLs back through itself, so it downloads each artifact from the origin once, de-duplicates concurrent requests (singleflight), caches on disk, and serves the cached copy to every subsequent request.
  • New flags/env vars: --provider-cache, --provider-cache-dir, --provider-cache-port, --provider-cache-registry-hosts.
  • Archive bytes are served verbatim, so Terraform's checksum and GPG-signature verification is unaffected.

why

  • When Atlantis runs many terraform init commands in parallel (across workspaces, projects and PRs), each process independently downloads the same providers from the upstream registry — wasting bandwidth, hitting registry rate limits, and racing on the shared plugin-cache dir (text file busy / partial-download failures).
  • A single shared, deduplicated cache removes the redundant downloads and the concurrency races, while leaving provider verification untouched.
  • Complements --use-tf-plugin-cache: the plugin cache lets one process reuse an installed provider; this proxy deduplicates the downloads across many parallel processes.
  • Uses the host service-discovery override (plain HTTP to loopback) rather than network_mirror (which requires HTTPS/certs) — the same approach Terragrunt uses.
  • Rewritten artifact URLs are HMAC-signed with a per-process key so the artifact endpoint only fetches URLs the proxy itself produced (no open-proxy/SSRF).

tests

  • Unit tests for the proxy against a fake upstream registry: versions pass-through, download-URL rewriting, artifact caching, 15-way concurrent de-duplication (asserts a single upstream download), and signature enforcement.
  • Tests for CLI-config generation (credentials + host blocks, either/both).
  • Updated flag coverage / defaults / documentation enforcement tests.
  • go build, go vet, gofmt, and the affected package tests pass on Go 1.26.

references

architecture decision record

ADR:

Signed-off-by: sakkiii <s@sakkiii.in>
Signed-off-by: sakkiii <s@sakkiii.in>
Add an opt-in local caching proxy for Terraform providers, modeled on
Terragrunt's provider cache server. When enabled with --provider-cache,
Atlantis starts a small HTTP server on localhost that speaks the Terraform
Provider Registry Protocol and points terraform at it via a host block in a
generated ~/.terraformrc CLI config file.

The many parallel `terraform init` runs Atlantis executes across workspaces
and pull requests then fetch providers through the proxy, which downloads
each provider archive from the upstream registry exactly once (concurrent
requests for the same artifact are de-duplicated with singleflight), caches
it on disk, and serves the cached copy to every subsequent request. Archive
bytes are served verbatim so terraform's checksum and GPG-signature
verification is unaffected.

New flags: --provider-cache, --provider-cache-dir, --provider-cache-port,
--provider-cache-registry-hosts.

Signed-off-by: sakkiii <s@sakkiii.in>
Signed-off-by: sakkiii <s@sakkiii.in>
@sakkiii sakkiii changed the title Feat/provider cache proxy feat: provider cache proxy for parallel terraform init (PROVIDER_CACHE) Sep 3, 2026
@sakkiii
sakkiii marked this pull request as ready for review September 3, 2026 15:36
@github-actions github-actions Bot added docs Documentation go Pull requests that update Go code labels Sep 3, 2026
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
Comment thread server/core/terraform/providercache/providercache.go Fixed
- Restrict provider metadata requests to the configured registry allowlist:
  handleVersions/handleDownload now reject any {host} not in --provider-cache-
  registry-hosts and use the trusted, configured spelling of the host, so the
  proxy cannot be used to reach arbitrary hosts. Path segments are already
  url.PathEscape'd against a trusted base, so the destination host can't be
  altered.
- Make cached artifact filenames the bare hex SHA-256 of the URL (fixed charset,
  no separators/dots), wrapped in filepath.Base, removing any user-derived
  component from on-disk paths.
- Add a test asserting unconfigured registry hosts are refused.

The remaining artifact-download request is bound by the per-process HMAC
signature (only proxy-generated URLs are fetched) plus an https/loopback scheme
check; fetching the registry-provided download URL is the endpoint's purpose.

Signed-off-by: sakkiii <s@sakkiii.in>
- Modernize: use strings.SplitSeq, range-over-int, and WaitGroup.Go.
- Suppress the three gosec taint findings (G703 path traversal on the
  hash-named cache file served via http.ServeFile; G704 SSRF on the artifact
  download) with #nosec directives that document why each is safe: the cache
  file name is a bare hex SHA-256 that cannot escape the cache dir, and the
  downloaded URL is HMAC-signed by this process and scheme-restricted before
  it reaches download().

Signed-off-by: sakkiii <s@sakkiii.in>
Comment thread server/core/terraform/providercache/providercache.go Fixed
… SSRF

Rework the artifact endpoint so it carries no request-supplied URL: Terraform
now receives coordinate-addressed artifact URLs
(/artifact/{host}/{ns}/{type}/{version}/{os}/{arch}/{kind}) and the proxy
re-resolves the real download location from the trusted registry's own
download-metadata response. Combined with a strict per-segment character
allowlist (segmentPattern) and the existing registry-host allowlist, no
attacker-controlled value from the incoming request reaches an outbound request
target or an on-disk path.

Validated with CodeQL (go-security-extended): 0 request-forgery and 0
path-injection alerts in the package (previously 3 critical request-forgery +
path-injection). The HMAC-signed-URL machinery is removed as it is no longer
needed. gosec's coarser taint pass is quieted with justified #nosec directives.

Also fix an MD049 markdownlint error in the provider-cache docs.

Signed-off-by: sakkiii <s@sakkiii.in>
Alpine 3.23's repository replaced curl-8.20.0-r0 with curl-8.22.0-r0 and
dropped the old version, so `apk add curl=8.20.0-r0` no longer resolves and the
image build fails. Bump the pin to the currently available version. Verified
with `apk add --simulate` against alpine:3.23.5 that the full package set
resolves; the other pins are unchanged and still current.

Signed-off-by: sakkiii <s@sakkiii.in>
@github-actions github-actions Bot added the build Relating to how we build Atlantis label Sep 6, 2026
@chenrui333 chenrui333 added the feature New functionality/enhancement label Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Relating to how we build Atlantis docs Documentation feature New functionality/enhancement go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition on provider installation with parallel_plan/apply enabled (Text file busy)

3 participants