Skip to content

Reach sources that block public network access via private endpoints - #9

Open
ryanwjackson wants to merge 1 commit into
mainfrom
source-private-endpoints
Open

Reach sources that block public network access via private endpoints#9
ryanwjackson wants to merge 1 commit into
mainfrom
source-private-endpoints

Conversation

@ryanwjackson

Copy link
Copy Markdown
Contributor

Why

grant-source adds the copy subnet to a source account's firewall allow list, but an account with public network access disabled ignores that list entirely — every request to its public endpoint fails with 403 AuthorizationFailure. Such accounts previously could not be sources at all (README said as much).

What

A new per-job flag, configured in the job JSON:

"source": { "privateEndpoint": true }

When any job sets it, the template additionally deploys — all inside the copy service's own resource group and vnet:

  • a private-endpoints subnet (always created, empty and free when unused; the Container Apps subnet is delegated and cannot hold endpoints),
  • a privatelink.file.core.windows.net / privatelink.blob.core.windows.net private DNS zone, linked to the copy vnet,
  • one private endpoint per distinct storage account + service, with a DNS zone group registering the account's A record. Deterministic names make full redeploys, single-job deploy, and jobs sharing an account converge on the same resources.

The connection uses manualPrivateLinkServiceConnections, so deploying needs no permission on the storage account: it arrives Pending, and the account's owner approves it once — portal, or the new copyctl.py approve-source <job>. Until then runs keep failing with the same 403, which copyctl.py status now calls out by showing the connection state.

copyctl

  • approve-source <job> — approves this deployment's pending connection (matched by the endpoint's deterministic name, so it can never approve someone else's request).
  • status — shows the endpoint's approval state for jobs with the flag.
  • deploy --replace — redeploys one existing job in place so the flag can be backfilled without a fleet-wide redeploy; that job's schedule returns to parked and its secret to the placeholder, which the command spells out. Other jobs stay untouched.
  • deploy reads the deployed private-endpoints subnet prefix back from Azure (like the other shared values); params/validate/deploy gain --pe-subnet for custom addressing, validated for fit and non-overlap.
  • SOURCE_PRIVATE_ENDPOINT is carried on the container env (not read by it) so pull round-trips the flag.

The portal form always emits "privateEndpoint": false — portal deployments are unchanged; the flag is CLI/job-file only.

Notes for review

  • tests/template_test.py forbids conditional top-level resources, so everything is zero-length loops derived from the jobs array; with no flagged jobs the template's output is unchanged except the extra subnet.
  • infra/main.json regenerated with Bicep CLI 0.46.1. If CI's az bicep version differs enough to change codegen, regenerate with az bicep build --file infra/main.bicep --outfile infra/main.json.
  • Full local suite passes (copyctl.py validate, config/uidefinition/template/transfer tests, shellcheck). Not verified against live Azure: the Pending→Approved flow, and that re-PUTting an approved manual connection on redeploy keeps its approved state (expected ARM behavior, but worth watching on the first backfill).
  • Docs: README section on blocked-public-access sources, SECURITY trust-boundary note (deploying alone never grants access; approval is the account owner's half), CHECKLIST step.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WddsTwUa11DHabXuJ4pTHd

A storage account with public network access disabled ignores its firewall
allow list entirely, so grant-source cannot reach it. Setting the new
source.privateEndpoint flag on a job now makes the deployment create, in its
own network: a private-endpoints subnet, a privatelink DNS zone for the
storage service with a link to the copy vnet, and one private endpoint per
distinct source account with its DNS record.

The connection is requested rather than auto-approved, so deploying needs no
permission on the storage account; it arrives Pending and the account's owner
approves it once, in the portal or with the new approve-source command.
status reports the connection's state, and deploy gains --replace so the flag
can be backfilled onto an already-deployed job without redeploying the fleet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WddsTwUa11DHabXuJ4pTHd
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