Skip to content

feat(storage): sign Azure layer-file uploads with a SAS - #3634

Draft
michalsuba-e2b wants to merge 1 commit into
feat/layer-upload-cache-hit-tolerancefrom
feat/azure-signed-upload-headers
Draft

feat(storage): sign Azure layer-file uploads with a SAS#3634
michalsuba-e2b wants to merge 1 commit into
feat/layer-upload-cache-hit-tolerancefrom
feat/azure-signed-upload-headers

Conversation

@michalsuba-e2b

Copy link
Copy Markdown
Contributor

Stacked on #3633 (base is that branch; review only the second commit).

Azure's Put Blob requires the request header x-ms-blob-type: BlockBlob, which a SAS cannot carry — it only pins response headers. The provider therefore refused to issue an upload URL at all, so every template build with a COPY instruction failed on Azure at the get-signed-URL step.

UploadSignedURL now returns {URL, Headers} instead of a bare URL, and the header travels with the URL through InitLayerFileUploadResponse.uploadHeaders (proto field 3) and TemplateBuildFileUpload.headers (OpenAPI) for the upload client to apply. Azure mints a create+write blob SAS with whichever credential the provider already authenticated with: a user delegation key for a token credential (the managed-identity path), or the shared key when one is configured. A SAS-only connection string can sign neither and now says so at construction and at the call site. GCS, S3 and the filesystem provider return nil headers and byte-identical URLs.

Additive on both contract surfaces: url stays optional, headers/uploadHeaders are new, and a client ignoring them parses the response unchanged. Consumers: the JS and Python SDKs (getFileUploadLink/build_api.py), which apply the headers in a follow-up PR on e2b-dev/E2B; edge and belt pass the gRPC response through schema-blind, so they need no change.

Verification (all local, go test -count=1):

  • Azurite integration, real HTTP PUT to the minted SAS: without the header → 400, with the returned header → 201 and the blob reads back. Mutation-verified (drop the header from the response, the test fails).
  • User-delegation signing over a fake transport: asserts the comp=userdelegationkey fetch and skoid/sktid/sp=cw/spr=https on the SAS. The RBAC grant behind that fetch is the one thing not exercisable locally.
  • MinIO-backed S3 presign returns no headers and a plain PUT still succeeds; the filesystem provider returns no headers.
  • golangci-lint clean on the touched packages; orchestrator package tested with -race in a linux container.

Not verified here: a live Azure deploy and a real multi-COPY acceptance build.

Sponsor: @michalsuba-e2b

🤖 Generated with Claude Code

…load headers

Azure's Put Blob requires the request header "x-ms-blob-type: BlockBlob". A SAS can
only pin response headers, so no signed URL alone can satisfy it and the provider
refused to issue one at all — every template build with a COPY instruction failed on
Azure at the get-signed-URL step.

UploadSignedURL now returns {URL, Headers} instead of a bare URL, and the header
travels with the URL through the gRPC and public API responses for the upload client
to apply. Azure mints a create+write blob SAS with whichever credential the provider
already authenticated with: a user delegation key for a token credential (the managed
identity path), or the shared key when one is configured. A SAS-only connection string
can sign neither and now says so at construction and at the call. GCS, S3 and the
filesystem provider return nil headers and byte-identical URLs.

The proto and OpenAPI additions are additive — url stays optional, and a client that
ignores the new field parses the response unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@michalsuba-e2b

Copy link
Copy Markdown
Contributor Author

CI attribution, for the reviewer:

validate-openapi is red on main, not here. All three specs fail npx @redocly/cli lint at origin/main, and spec/openapi.yml produces the identical 1 error, 86 warnings before and after this branch's +5 lines. The error is e2b-consistency/operation-summary-no-get-prefix on /clusters/{clusterID}/rigs/{rigID}/errors. openapi-dashboard.yml (4 errors) and openapi-edge.yml (5 errors) are untouched here. The job is pull_request-gated on spec/**, so it only surfaces on a PR that edits a spec.

arm64-tests packages/orchestrator: TestCacheExportToDiffWithMetadata_ProceedsUnderReadLock in pkg/sandbox/block — a 10s-deadline timing test on a shared runner. That package never calls UploadSignedURL.

integration-tests: the shard runner retries, and TestTemplateBuildCOPY — the only integration test with a COPY step, i.e. the only one that reaches InitLayerFileUpload — passes on re-run 1 in 16s, with COPY . /app/ and the follow-up RUN cat /app/hello.txt returning the copied content. What stays red times out at ~300s at Provisioning sandbox template or on the build-status poll (TestTemplateBuildCache, TestTemplateBuildFromTemplate, TestEgressFirewall*, TestSmokeAllFCVersions); none has a COPY step, and InitLayerFileUpload appears nowhere under packages/orchestrator/cmd/ or tests/integration/.

@michalsuba-e2b

Copy link
Copy Markdown
Contributor Author

Live-environment validation on an Azure BYOC env (miso9), dev orchestrator-ee built from this branch stack: InitLayerFileUpload through edge returns the UD SAS + uploadHeaders={x-ms-blob-type: BlockBlob}; PUT without the header fails 400 MissingRequiredHeader, with it 201; repeat call returns present=true (#3633's tolerance). SDK-side (E2B#1870 uploadFile) exercised live against the minted URL: headers applied → success, headers stripped → the exact mandatory-header failure. Full evidence: https://linear.app/e2b/issue/BYOC-228#comment-259c9df2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant