Skip to content

Commit 792b0a0

Browse files
committed
fixups post review
1 parent 2682cc5 commit 792b0a0

File tree

3 files changed

+102
-30
lines changed

3 files changed

+102
-30
lines changed

.github/pykmip/Dockerfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ RUN apk add --no-cache \
1616
git clone https://github.com/openkmip/pykmip.git && \
1717
cd pykmip && \
1818
pip3 install . && \
19-
cd / && \
2019
apk del .build-deps && \
2120
rm -rf /var/cache/apk/* /pykmip && \
2221
mkdir /pykmip

lib/routes/routeVeeam.js

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,19 @@ const apiToAction = {
3232
const allowedSdkQueryKeys = new Set([
3333
'x-id',
3434
'x-amz-user-agent',
35-
'x-amz-sdk-invocation-id',
36-
'x-amz-sdk-request',
35+
]);
36+
37+
// Allowed query parameters for SigV4 presigned URLs (lower-cased).
38+
const allowedPresignQueryKeys = new Set([
39+
'x-amz-algorithm',
40+
'x-amz-credential',
41+
'x-amz-date',
42+
'x-amz-expires',
43+
'x-amz-signedheaders',
44+
'x-amz-signature',
45+
'x-amz-security-token',
46+
// Used by Veeam UI for delete operations.
47+
'tagging',
3748
]);
3849

3950
const routeMap = {
@@ -71,38 +82,27 @@ function checkBucketAndKey(bucketName, objectKey, requestQueryParams, method, lo
7182
}
7283
if (method !== 'LIST') {
7384
// Reject any unsupported request, but allow downloads and deletes from UI
74-
// Download relies on GETs calls with auth in query parameters, that can be
75-
// checked if 'X-Amz-Credential' is included.
76-
// Deletion requires that the tags of the object are returned.
85+
// Download relies on GETs calls with auth in query parameters, and delete
86+
// requires that the tags of the object are returned.
7787
const originalQuery = requestQueryParams || {};
78-
const normalizedQueryKeys = new Map(
79-
Object.keys(originalQuery).map(key => [key.toLowerCase(), key])
80-
);
81-
const isPresignedGet = method === 'GET'
82-
&& (normalizedQueryKeys.has('x-amz-credential')
83-
|| normalizedQueryKeys.has('tagging'));
8488

85-
if (!isPresignedGet && normalizedQueryKeys.size > 0) {
86-
const hasUnsupportedKeys = Array.from(normalizedQueryKeys.keys())
87-
.some(lowerKey => {
88-
if (allowedSdkQueryKeys.has(lowerKey)) {
89-
return false;
90-
}
91-
if (lowerKey.startsWith('x-amz-sdk-')) {
92-
return false;
93-
}
94-
return true;
95-
});
96-
if (hasUnsupportedKeys) {
89+
for (const [key, value] of Object.entries(originalQuery)) {
90+
const normalizedKey = key.toLowerCase();
91+
92+
// Ensure x-id, when present, matches the expected action for the method.
93+
if (normalizedKey === 'x-id' && value !== apiToAction[method]) {
94+
return errorInstances.InvalidRequest
95+
.customizeDescription('The Veeam SOSAPI folder does not support this action.');
96+
}
97+
98+
const isAllowedSdkKey = allowedSdkQueryKeys.has(normalizedKey)
99+
|| normalizedKey.startsWith('x-amz-sdk-');
100+
const isAllowedPresignKey = allowedPresignQueryKeys.has(normalizedKey);
101+
102+
if (!isAllowedSdkKey && !isAllowedPresignKey) {
97103
return errorInstances.InvalidRequest
98104
.customizeDescription('The Veeam SOSAPI folder does not support this action.');
99105
}
100-
}
101-
const xIdOriginalKey = normalizedQueryKeys.get('x-id');
102-
const xIdValue = xIdOriginalKey ? originalQuery[xIdOriginalKey] : undefined;
103-
if (xIdValue && xIdValue !== apiToAction[method]) {
104-
return errorInstances.InvalidRequest
105-
.customizeDescription('The Veeam SOSAPI folder does not support this action.');
106106
}
107107
if (typeof objectKey !== 'string' || !validObjectKeys.includes(objectKey)) {
108108
log.debug('invalid object name', { objectKey });

tests/unit/internal/routeVeeam.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,57 @@ describe('RouteVeeam: checkBucketAndKey', () => {
7878
});
7979
});
8080

81+
it('should allow SigV4 presigned GET query parameters in mixed case', () => {
82+
const err = routeVeeam.checkBucketAndKey(
83+
'test',
84+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
85+
{
86+
'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
87+
'X-Amz-Credential': 'cred',
88+
'X-Amz-Date': '20240101T000000Z',
89+
'X-Amz-Expires': '900',
90+
'X-Amz-SignedHeaders': 'host',
91+
'X-Amz-Signature': 'signature',
92+
'X-Amz-Security-Token': 'token',
93+
},
94+
'GET',
95+
log,
96+
);
97+
assert.strictEqual(err, undefined);
98+
});
99+
100+
it('should allow SigV4-style query parameters on non-GET when they are presigned', () => {
101+
const err = routeVeeam.checkBucketAndKey(
102+
'test',
103+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
104+
{
105+
'x-amz-algorithm': 'AWS4-HMAC-SHA256',
106+
'x-amz-credential': 'cred',
107+
'x-amz-date': '20240101T000000Z',
108+
'x-amz-expires': '900',
109+
'x-amz-signedheaders': 'host',
110+
'x-amz-signature': 'signature',
111+
},
112+
'DELETE',
113+
log,
114+
);
115+
assert.strictEqual(err, undefined);
116+
});
117+
118+
it('should reject unexpected query parameters even when presigned GET keys are present', () => {
119+
const err = routeVeeam.checkBucketAndKey(
120+
'test',
121+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
122+
{
123+
'X-Amz-Credential': 'a',
124+
extra: 'not-allowed',
125+
},
126+
'GET',
127+
log,
128+
);
129+
assert.strictEqual(err.is.InvalidRequest, true);
130+
});
131+
81132
it('should allow AWS SDK x-id=PutObject query on PUT for system.xml', () => {
82133
const err = routeVeeam.checkBucketAndKey(
83134
'test',
@@ -115,6 +166,28 @@ describe('RouteVeeam: checkBucketAndKey', () => {
115166
);
116167
assert.strictEqual(err.is.InvalidRequest, true);
117168
});
169+
170+
it('should reject mismatched x-id value on GET for system.xml', () => {
171+
const err = routeVeeam.checkBucketAndKey(
172+
'test',
173+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
174+
{ 'x-id': 'PutObject' },
175+
'GET',
176+
log,
177+
);
178+
assert.strictEqual(err.is.InvalidRequest, true);
179+
});
180+
181+
it('should accept x-id with different casing when value matches action', () => {
182+
const err = routeVeeam.checkBucketAndKey(
183+
'test',
184+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
185+
{ 'X-Id': 'GetObject' },
186+
'GET',
187+
log,
188+
);
189+
assert.strictEqual(err, undefined);
190+
});
118191
});
119192

120193
describe('RouteVeeam: checkBucketAndKey', () => {

0 commit comments

Comments
 (0)