Skip to content

Commit 7272f70

Browse files
authored
Fix query parameter signing in Azure (#334)
* fix azure issue * Add unit test for query string formatting in the builder
1 parent 3e12e1a commit 7272f70

File tree

2 files changed

+69
-26
lines changed

2 files changed

+69
-26
lines changed

CONTRIBUTING.md

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ To test the S3 integration against [localstack](https://localstack.cloud/)
3838

3939
First start up a container running localstack
4040

41-
```
42-
$ LOCALSTACK_VERSION=sha256:a0b79cb2430f1818de2c66ce89d41bba40f5a1823410f5a7eaf3494b692eed97
43-
$ podman run -d -p 4566:4566 localstack/localstack@$LOCALSTACK_VERSION
44-
$ podman run -d -p 1338:1338 amazon/amazon-ec2-metadata-mock:v1.9.2 --imdsv2
41+
```shell
42+
LOCALSTACK_VERSION=sha256:a0b79cb2430f1818de2c66ce89d41bba40f5a1823410f5a7eaf3494b692eed97
43+
podman run -d -p 4566:4566 localstack/localstack@$LOCALSTACK_VERSION
44+
podman run -d -p 1338:1338 amazon/amazon-ec2-metadata-mock:v1.9.2 --imdsv2
4545
```
4646

4747
Setup environment
4848

49-
```
49+
```shell
5050
export TEST_INTEGRATION=1
5151
export AWS_DEFAULT_REGION=us-east-1
5252
export AWS_ACCESS_KEY_ID=test
@@ -58,44 +58,44 @@ export AWS_BUCKET_NAME=test-bucket
5858

5959
Create a bucket using the AWS CLI
6060

61-
```
61+
```shell
6262
podman run --net=host --env-host amazon/aws-cli --endpoint-url=http://localhost:4566 s3 mb s3://test-bucket
6363
```
6464

6565
Or directly with:
6666

67-
```
67+
```shell
6868
aws s3 mb s3://test-bucket --endpoint-url=http://localhost:4566
6969
aws --endpoint-url=http://localhost:4566 dynamodb create-table --table-name test-table --key-schema AttributeName=path,KeyType=HASH AttributeName=etag,KeyType=RANGE --attribute-definitions AttributeName=path,AttributeType=S AttributeName=etag,AttributeType=S --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5
7070
```
7171

7272
Run tests
7373

74-
```
75-
$ cargo test --features aws
74+
```shell
75+
cargo test --features aws
7676
```
7777

7878
#### Encryption tests
7979

8080
To create an encryption key for the tests, you can run the following command:
8181

82-
```
82+
```shell
8383
export AWS_SSE_KMS_KEY_ID=$(aws --endpoint-url=http://localhost:4566 \
8484
kms create-key --description "test key" |
8585
jq -r '.KeyMetadata.KeyId')
8686
```
8787

8888
To run integration tests with encryption, you can set the following environment variables:
8989

90-
```
90+
```shell
9191
export AWS_SERVER_SIDE_ENCRYPTION=aws:kms
9292
export AWS_SSE_BUCKET_KEY=false
9393
cargo test --features aws
9494
```
9595

9696
As well as:
9797

98-
```
98+
```shell
9999
unset AWS_SSE_BUCKET_KEY
100100
export AWS_SERVER_SIDE_ENCRYPTION=aws:kms:dsse
101101
cargo test --features aws
@@ -147,33 +147,33 @@ export TEST_S3_SSEC_ENCRYPTION=1
147147
cargo test --features aws --package object_store --lib aws::tests::test_s3_ssec_encryption_with_minio -- --exact --nocapture
148148
```
149149

150-
151-
152150
### Azure
153151

154152
To test the Azure integration
155153
against [azurite](https://docs.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio)
156154

157155
Startup azurite
158156

159-
```
160-
$ podman run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
157+
```shell
158+
podman run -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
161159
```
162160

163161
Create a bucket
164162

165-
```
166-
$ podman run --net=host mcr.microsoft.com/azure-cli az storage container create -n test-bucket --connection-string 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;'
163+
```shell
164+
podman run --net=host mcr.microsoft.com/azure-cli az storage container create -n test-bucket --connection-string 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;'
167165
```
168166

169167
Run tests
170168

171169
```shell
172170
AZURE_USE_EMULATOR=1 \
173171
TEST_INTEGRATION=1 \
174-
OBJECT_STORE_BUCKET=test-bucket \
175-
AZURE_STORAGE_ACCOUNT=devstoreaccount1 \
172+
AZURE_CONTAINER_NAME=test-bucket \
173+
AZURE_STORAGE_ACCOUNT_NAME=devstoreaccount1 \
176174
AZURE_STORAGE_ACCESS_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== \
175+
AZURE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \
176+
AZURE_ALLOW_HTTP=true \
177177
cargo test --features azure
178178
```
179179

@@ -201,7 +201,6 @@ GOOGLE_SERVICE_ACCOUNT=/tmp/gcs.json \
201201
cargo test -p object_store --features=gcp
202202
```
203203

204-
205204
# Deprecation Guidelines
206205

207206
Minor releases may deprecate, but not remove APIs. Deprecating APIs allows

src/client/builder.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,13 @@ impl HttpRequestBuilder {
178178
self
179179
}
180180

181-
#[cfg(any(feature = "aws", feature = "gcp", feature = "azure"))]
181+
#[cfg(any(test, feature = "aws", feature = "gcp", feature = "azure"))]
182182
pub(crate) fn query<T: serde::Serialize + ?Sized>(mut self, query: &T) -> Self {
183183
let mut error = None;
184184
if let Ok(ref mut req) = self.request {
185185
let mut out = format!("{}?", req.uri().path());
186-
let mut encoder = form_urlencoded::Serializer::new(&mut out);
186+
let start_position = out.len();
187+
let mut encoder = form_urlencoded::Serializer::for_suffix(&mut out, start_position);
187188
let serializer = serde_urlencoded::Serializer::new(&mut encoder);
188189

189190
if let Err(err) = query.serialize(serializer) {
@@ -251,12 +252,18 @@ where
251252

252253
let mut out = match parts.path_and_query {
253254
Some(p) => match p.query() {
254-
Some(x) => format!("{}?{}", p.path(), x),
255+
Some(query) => format!("{}?{}", p.path(), query),
255256
None => format!("{}?", p.path()),
256257
},
257258
None => "/?".to_string(),
258259
};
259-
let mut serializer = form_urlencoded::Serializer::new(&mut out);
260+
let mut serializer = if out.ends_with('?') {
261+
let start_position = out.len();
262+
form_urlencoded::Serializer::for_suffix(&mut out, start_position)
263+
} else {
264+
form_urlencoded::Serializer::new(&mut out)
265+
};
266+
260267
serializer.extend_pairs(query_pairs);
261268

262269
parts.path_and_query = Some(out.try_into().unwrap());
@@ -269,7 +276,10 @@ mod tests {
269276

270277
#[test]
271278
fn test_add_query_pairs() {
272-
let mut uri = Uri::from_static("https://[email protected]/bananas?foo=1");
279+
let mut uri = Uri::from_static("https://[email protected]/bananas");
280+
281+
add_query_pairs(&mut uri, [("foo", "1")]);
282+
assert_eq!(uri.to_string(), "https://[email protected]/bananas?foo=1");
273283

274284
add_query_pairs(&mut uri, [("bingo", "foo"), ("auth", "test")]);
275285
assert_eq!(
@@ -283,4 +293,38 @@ mod tests {
283293
"https://[email protected]/bananas?foo=1&bingo=foo&auth=test&t1=funky+shenanigans&a=%F0%9F%98%80"
284294
);
285295
}
296+
297+
#[test]
298+
fn test_add_query_pairs_no_path() {
299+
let mut uri = Uri::from_static("https://[email protected]");
300+
add_query_pairs(&mut uri, [("foo", "1")]);
301+
assert_eq!(uri.to_string(), "https://[email protected]/?foo=1");
302+
}
303+
304+
#[test]
305+
fn test_request_builder_query() {
306+
let client = HttpClient::new(reqwest::Client::new());
307+
assert_request_uri(
308+
HttpRequestBuilder::new(client.clone()).uri("http://example.com/bananas"),
309+
"http://example.com/bananas",
310+
);
311+
312+
assert_request_uri(
313+
HttpRequestBuilder::new(client.clone())
314+
.uri("http://example.com/bananas")
315+
.query(&[("foo", "1")]),
316+
"http://example.com/bananas?foo=1",
317+
);
318+
319+
assert_request_uri(
320+
HttpRequestBuilder::new(client.clone())
321+
.uri("http://example.com")
322+
.query(&[("foo", "1")]),
323+
"http://example.com/?foo=1",
324+
);
325+
}
326+
327+
fn assert_request_uri(builder: HttpRequestBuilder, expected: &str) {
328+
assert_eq!(builder.into_parts().1.unwrap().uri().to_string(), expected)
329+
}
286330
}

0 commit comments

Comments
 (0)