Skip to content

Commit afa20fa

Browse files
Merge pull request #219 from scality/fix/gcs-presign-colon-encoding
Fix ENDPOINT_URL not exposed to Lua workers in nginx
2 parents 70be18d + b91ff11 commit afa20fa

3 files changed

Lines changed: 13 additions & 27 deletions

File tree

conf/nginx.conf.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ env BOT_TOKEN;
2020
# Google Cloud Storage S3 compatible bucket prefix.
2121
env AWS_BUCKET_PREFIX;
2222

23+
# S3-compatible endpoint URL, used to detect backend-specific signature quirks.
24+
env ENDPOINT_URL;
25+
2326
# It is recommended to set one worker process per CPU core.
2427
pid /run/nginx.pid;
2528

lua/compute_aws_s3_signature.lua

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -376,20 +376,23 @@ elseif signature_mode == "PRESIGN_PART" then
376376
end
377377
local expires = ngx.time() + 3600
378378
local aws_secret_key = os.getenv('AWS_SECRET_ACCESS_KEY')
379-
-- GCS normalises ':' to '%3A' when computing StringToSign for presigned URLs;
380-
-- encode it here so the signature matches what GCS will verify.
381-
local url_safe_key = ngx.var.encoded_key:gsub(':', '%%3A')
382379
-- Normalise uploadId: ngx.var.arg_* may be pre-encoded or raw depending on the nginx
383380
-- version; unescape then re-escape to avoid double-encoding (%2B → %252B).
384381
local escaped_upload_id = ngx.escape_uri(ngx.unescape_uri(upload_id))
385-
-- GCS does NOT include ?partNumber=N&uploadId=X in the canonical resource for presigned
386-
-- part PUTs. Standard AWS S3-compatible backends (cloudserver, Scaleway S3) do include
387-
-- them per the V2 spec. Detect GCS from ENDPOINT_URL to pick the right behaviour.
382+
-- GCS has two quirks vs standard S3 V2 for presigned part PUTs:
383+
-- 1. It normalises ':' to '%3A' in the key when verifying StringToSign.
384+
-- 2. It excludes ?partNumber=N&uploadId=X from the canonical resource.
385+
-- Standard S3-compatible backends (cloudserver, Scaleway) use literal ':'
386+
-- and include subresources per the V2 spec.
387+
-- ENDPOINT_URL must be declared with 'env ENDPOINT_URL;' in nginx.conf so
388+
-- os.getenv() can read it from Lua workers.
388389
local endpoint_url = os.getenv('ENDPOINT_URL') or ''
389-
local canonicalized_resource
390+
local url_safe_key, canonicalized_resource
390391
if endpoint_url:find('googleapis', 1, true) then
392+
url_safe_key = ngx.var.encoded_key:gsub(':', '%%3A')
391393
canonicalized_resource = "/" .. ngx.var.aws_tgt_bucket .. "/" .. url_safe_key
392394
else
395+
url_safe_key = ngx.var.encoded_key
393396
canonicalized_resource = "/" .. ngx.var.aws_tgt_bucket .. "/" .. url_safe_key ..
394397
"?partNumber=" .. part_number .. "&uploadId=" .. escaped_upload_id
395398
end

tests/end2end/test_presign_upload.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -118,26 +118,6 @@ def test_presign_upload_part_encodes_special_chars_in_upload_id(session, artifac
118118
)
119119

120120

121-
def test_presign_upload_part_encodes_colons_in_url(session, artifacts_url):
122-
"""Presigned part URL path must use %3A for ':' — GCS normalises ':' to '%3A'
123-
when computing StringToSign, so a literal ':' causes SignatureDoesNotMatch."""
124-
upload_id = multipart_initiate(session, artifacts_url, STAGING_BUILD, 'presign/colon-part.bin')
125-
try:
126-
resp = session.get(
127-
f'{artifacts_url}/presign-upload-part/{STAGING_BUILD}/presign/colon-part.bin',
128-
params={'partNumber': 1, 'uploadId': upload_id},
129-
)
130-
assert resp.status_code == 200, f'{resp.status_code} {resp.text}'
131-
url = resp.text.strip()
132-
path = urlparse(url).path
133-
assert '%3A' in path, f"Expected '%3A' in presigned part URL path, got: {path!r}"
134-
assert ':' not in path, f"Raw ':' in presigned part URL path causes SignatureDoesNotMatch on GCS"
135-
finally:
136-
session.delete(
137-
f'{artifacts_url}/upload-multipart/abort/{STAGING_BUILD}/presign/colon-part.bin',
138-
params={'uploadId': upload_id},
139-
)
140-
141121

142122
def test_presign_multipart_full_round_trip(session, artifacts_url):
143123
"""Initiate via nginx, upload parts directly to S3, complete via nginx."""

0 commit comments

Comments
 (0)