Skip to content

Commit 5bc9d4b

Browse files
committed
Merge remote-tracking branch 'origin/improvement/CLDSRV-806-handling-additionnal-headers' into w/9.1/improvement/CLDSRV-806-handling-additionnal-headers
2 parents b3a3444 + 55ea857 commit 5bc9d4b

File tree

4 files changed

+153
-101
lines changed

4 files changed

+153
-101
lines changed

.github/pykmip/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ RUN apk add --no-cache \
1717
pip3 install --upgrade typing-extensions>=4.13.2 && \
1818
git clone https://github.com/openkmip/pykmip.git && \
1919
cd pykmip && \
20-
python3 setup.py install && \
20+
git checkout 6cd44b572b0ca55adf01a8a12078b2284602e64c && \
21+
pip3 install . && \
2122
apk del .build-deps && \
22-
rm -rf /pykmip && \
23+
rm -rf /var/cache/apk/* /pykmip && \
2324
mkdir /pykmip
2425

2526
ADD ./bin /usr/local/bin

.github/workflows/tests.yaml

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -816,98 +816,6 @@ jobs:
816816
source: /tmp/artifacts
817817
if: always()
818818

819-
ceph-backend-test:
820-
runs-on: ubuntu-24.04
821-
needs: build
822-
env:
823-
S3BACKEND: mem
824-
S3DATA: multiple
825-
S3KMS: file
826-
CI_CEPH: 'true'
827-
MPU_TESTING: "yes"
828-
S3_LOCATION_FILE: /usr/src/app/tests/locationConfig/locationConfigCeph.json
829-
MONGODB_IMAGE: ghcr.io/${{ github.repository }}/ci-mongodb:${{ github.sha }}
830-
CLOUDSERVER_IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }}-testcoverage
831-
JOB_NAME: ${{ github.job }}
832-
ENABLE_NULL_VERSION_COMPAT_MODE: true # needed with mongodb backend
833-
steps:
834-
- name: Checkout
835-
uses: actions/checkout@v4
836-
- name: Login to GitHub Registry
837-
uses: docker/login-action@v3
838-
with:
839-
registry: ghcr.io
840-
username: ${{ github.repository_owner }}
841-
password: ${{ github.token }}
842-
- name: Setup CI environment
843-
uses: ./.github/actions/setup-ci
844-
- uses: ruby/setup-ruby@v1
845-
with:
846-
ruby-version: '3.2'
847-
- name: Install Ruby dependencies
848-
run: |
849-
gem install nokogiri:1.15.5 excon:0.111.0 fog-aws:3.19.0 json:2.7.6 mime-types:3.5.2 rspec:3.12.0
850-
- name: Install Java dependencies
851-
run: |
852-
sudo apt-get update && sudo apt-get install -y --fix-missing default-jdk maven
853-
- name: Setup CI services
854-
run: docker compose --profile ceph up -d
855-
working-directory: .github/docker
856-
env:
857-
S3METADATA: mongodb
858-
- name: Run Ceph multiple backend tests
859-
run: |-
860-
set -ex -o pipefail;
861-
bash .github/ceph/wait_for_ceph.sh
862-
bash wait_for_local_port.bash 27018 40
863-
bash wait_for_local_port.bash 8000 40
864-
yarn run multiple_backend_test | tee /tmp/artifacts/${{ github.job }}/multibackend-tests.log
865-
env:
866-
S3_LOCATION_FILE: tests/locationConfig/locationConfigTests.json
867-
S3METADATA: mem
868-
- name: Run Java tests
869-
run: |-
870-
set -ex -o pipefail;
871-
mvn test | tee /tmp/artifacts/${{ github.job }}/java-tests.log
872-
working-directory: tests/functional/jaws
873-
- name: Run Ruby tests
874-
run: |-
875-
set -ex -o pipefail;
876-
rspec -fd --backtrace tests.rb | tee /tmp/artifacts/${{ github.job }}/ruby-tests.log
877-
working-directory: tests/functional/fog
878-
- name: Run Javascript AWS SDK tests
879-
run: |-
880-
set -ex -o pipefail;
881-
yarn run ft_awssdk | tee /tmp/artifacts/${{ github.job }}/js-awssdk-tests.log;
882-
yarn run ft_s3cmd | tee /tmp/artifacts/${{ github.job }}/js-s3cmd-tests.log;
883-
env:
884-
S3_LOCATION_FILE: tests/locationConfig/locationConfigCeph.json
885-
S3BACKEND: file
886-
S3VAULT: mem
887-
S3METADATA: mongodb
888-
- name: Cleanup and upload coverage
889-
uses: ./.github/actions/cleanup-and-coverage
890-
with:
891-
profiles: ceph
892-
codecov-token: ${{ secrets.CODECOV_TOKEN }}
893-
if: always()
894-
- name: Upload test results to Codecov
895-
uses: codecov/test-results-action@v1
896-
with:
897-
token: ${{ secrets.CODECOV_TOKEN }}
898-
files: '**/junit/*junit*.xml'
899-
flags: ceph-backend-test
900-
if: always() && !cancelled()
901-
- name: Upload logs to artifacts
902-
uses: scality/action-artifacts@v4
903-
with:
904-
method: upload
905-
url: https://artifacts.scality.net
906-
user: ${{ secrets.ARTIFACTS_USER }}
907-
password: ${{ secrets.ARTIFACTS_PASSWORD }}
908-
source: /tmp/artifacts
909-
if: always()
910-
911819
# This test with the final yarn run ft_sse_arn covers more code than the kmip tests
912820
sse-kms-migration-tests:
913821
strategy:

lib/routes/routeVeeam.js

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,24 @@ const apiToAction = {
2929
LIST: 'ListObjects',
3030
};
3131

32+
const allowedSdkQueryKeys = new Set([
33+
'x-id',
34+
'x-amz-user-agent',
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',
48+
]);
49+
3250
const routeMap = {
3351
GET: getVeeamFile,
3452
PUT: putVeeamFile,
@@ -64,13 +82,27 @@ function checkBucketAndKey(bucketName, objectKey, requestQueryParams, method, lo
6482
}
6583
if (method !== 'LIST') {
6684
// Reject any unsupported request, but allow downloads and deletes from UI
67-
// Download relies on GETs calls with auth in query parameters, that can be
68-
// checked if 'X-Amz-Credential' is included.
69-
// Deletion requires that the tags of the object are returned.
70-
if (requestQueryParams && Object.keys(requestQueryParams).length > 0
71-
&& !(method === 'GET' && (requestQueryParams['X-Amz-Credential'] || ('tagging' in requestQueryParams)))) {
72-
return errorInstances.InvalidRequest
73-
.customizeDescription('The Veeam SOSAPI folder does not support this action.');
85+
// Download relies on GETs calls with auth in query parameters, and delete
86+
// requires that the tags of the object are returned.
87+
const originalQuery = requestQueryParams || {};
88+
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) {
103+
return errorInstances.InvalidRequest
104+
.customizeDescription('The Veeam SOSAPI folder does not support this action.');
105+
}
74106
}
75107
if (typeof objectKey !== 'string' || !validObjectKeys.includes(objectKey)) {
76108
log.debug('invalid object name', { objectKey });

tests/unit/internal/routeVeeam.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,117 @@ describe('RouteVeeam: checkBucketAndKey', () => {
7777
assert.strictEqual(routeVeeam.checkBucketAndKey(...test), undefined);
7878
});
7979
});
80+
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+
132+
it('should allow AWS SDK x-id=PutObject query on PUT for system.xml', () => {
133+
const err = routeVeeam.checkBucketAndKey(
134+
'test',
135+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
136+
{ 'x-id': 'PutObject' },
137+
'PUT',
138+
log,
139+
);
140+
assert.strictEqual(err, undefined);
141+
});
142+
143+
it('should allow AWS SDK auxiliary x-amz-sdk-* query params on PUT for system.xml', () => {
144+
const err = routeVeeam.checkBucketAndKey(
145+
'test',
146+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
147+
{
148+
'x-id': 'PutObject',
149+
'x-amz-sdk-request': 'attempt=1',
150+
'x-amz-sdk-invocation-id': 'abc-123',
151+
'x-amz-user-agent': 'aws-sdk-js-v3',
152+
},
153+
'PUT',
154+
log,
155+
);
156+
assert.strictEqual(err, undefined);
157+
});
158+
159+
it('should reject mismatched x-id value on PUT for system.xml', () => {
160+
const err = routeVeeam.checkBucketAndKey(
161+
'test',
162+
'.system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/system.xml',
163+
{ 'x-id': 'GetObject' },
164+
'PUT',
165+
log,
166+
);
167+
assert.strictEqual(err.is.InvalidRequest, true);
168+
});
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+
});
80191
});
81192

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

0 commit comments

Comments
 (0)