Skip to content

Commit 8092247

Browse files
committed
fixups post reviews
1 parent 9f45f9f commit 8092247

23 files changed

+551
-710
lines changed

lib/auth/v2/getCanonicalizedAmzHeaders.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export default function getCanonicalizedAmzHeaders(headers: Record<string, string>, clientType: string) {
1+
import type { ArsenalRequestHeaders } from '../../types/ArsenalRequest';
2+
import { normalizeHeaderValue } from '../../utils/normalizeHeaders';
3+
4+
export default function getCanonicalizedAmzHeaders(headers: ArsenalRequestHeaders, clientType: string) {
25
/*
36
Iterate through headers and pull any headers that are x-amz headers.
47
Need to include 'x-amz-date' here even though AWS docs
@@ -11,12 +14,7 @@ export default function getCanonicalizedAmzHeaders(headers: Record<string, strin
1114
.filter(filterFn)
1215
.map(val => {
1316
const headerValue = headers[val];
14-
// AWS SDK v3 can pass header values as arrays (for multiple values),
15-
// strings, or other types. We need to normalize them before calling .trim()
16-
// Per HTTP spec and AWS Signature v2, multiple values are joined with commas
17-
const stringValue = Array.isArray(headerValue)
18-
? headerValue.join(',')
19-
: String(headerValue);
17+
const stringValue = normalizeHeaderValue(headerValue);
2018
return [val.trim(), stringValue.trim()];
2119
});
2220
/*

lib/auth/v4/createCanonicalRequest.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as crypto from 'crypto';
22
import * as queryString from 'querystring';
33
import awsURIencode from './awsURIencode';
4+
import { normalizeHeaderValue } from '../../utils/normalizeHeaders';
45

56
/**
67
* createCanonicalRequest - creates V4 canonical request
@@ -77,9 +78,7 @@ export default function createCanonicalRequest(
7778
// AWS SDK v3 can pass header values as arrays (for multiple values),
7879
// strings, or other types. We need to normalize them before calling .trim()
7980
// Per HTTP spec and AWS Signature v4, multiple values are joined with commas
80-
const stringValue = Array.isArray(headerValue)
81-
? headerValue.join(',')
82-
: String(headerValue);
81+
const stringValue = normalizeHeaderValue(headerValue);
8382
const trimmedHeader = stringValue
8483
.trim().replace(/\s+/g, ' ');
8584
return `${signedHeader}:${trimmedHeader}\n`;

lib/auth/v4/streamingV4/constructChunkStringToSign.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ export default function constructChunkStringToSign(
2424
currentChunkHash = constants.emptyStringHash;
2525
} else {
2626
const hash = crypto.createHash('sha256');
27-
const temp = hash.update(justDataChunk);
28-
currentChunkHash = temp.digest('hex');
27+
const chunkHash = typeof justDataChunk === 'string'
28+
? hash.update(justDataChunk, 'binary')
29+
: hash.update(justDataChunk);
30+
currentChunkHash = chunkHash.digest('hex');
2931
}
3032
return `AWS4-HMAC-SHA256-PAYLOAD\n${timestamp}\n` +
3133
`${credentialScope}\n${lastSignature}\n` +

lib/network/kmsAWS/Client.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
import { arsenalErrorAWSKMS } from '../utils';
44
import { Agent as HttpAgent } from 'http';
55
import { Agent as HttpsAgent } from 'https';
6-
import { KMSClient,
7-
CreateKeyCommand,
6+
import {
7+
KMSClient,
8+
CreateKeyCommand,
89
ScheduleKeyDeletionCommand,
9-
GenerateDataKeyCommand,
10-
EncryptCommand,
11-
DecryptCommand,
12-
ListKeysCommand,
13-
NotFoundException,
14-
KMSInvalidStateException } from '@aws-sdk/client-kms';
10+
GenerateDataKeyCommand,
11+
EncryptCommand,
12+
DecryptCommand,
13+
NotFoundException,
14+
KMSInvalidStateException,
15+
} from '@aws-sdk/client-kms';
1516
const { NodeHttpHandler } = require('@smithy/node-http-handler');
1617
import * as werelogs from 'werelogs';
1718
import assert from 'assert';
@@ -60,7 +61,7 @@ export default class Client implements KMSInterface {
6061

6162
constructor(options: ClientOptions) {
6263
this._supportsDefaultKeyPerAccount = true;
63-
const { providerName,tls, ak, sk, region, endpoint, noAwsArn } = options.kmsAWS;
64+
const { providerName, tls, ak, sk, region, endpoint, noAwsArn } = options.kmsAWS;
6465

6566
const requestHandler = new NodeHttpHandler({
6667
httpAgent: !tls ? new HttpAgent({
@@ -134,9 +135,13 @@ export default class Client implements KMSInterface {
134135
// Prefer ARN, but fall back to KeyId if ARN is missing
135136
keyId = keyMetadata?.Arn ?? (keyMetadata?.KeyId || '');
136137
}
138+
// May produce double arn prefix: scality arn + aws arn
139+
// arn:scality:kms:external:aws_kms:custom:key/arn:aws:kms:region:accountId:key/cbd69d33-ba8e-4b56-8cfe
140+
// If this is a problem, a config flag should be used to hide the scality arn when returning the KMS KeyId
141+
// or aws arn when creating the KMS Key
137142
const arn = `${this.backend.arnPrefix}${keyId}`;
138143
cb(null, keyId, arn);
139-
}).catch(err => {
144+
}).catch((err: Error) => {
140145
const error = arsenalErrorAWSKMS(err);
141146
logger.error('AWS KMS: failed to create master encryption key', { err });
142147
cb(error);
@@ -170,9 +175,10 @@ export default class Client implements KMSInterface {
170175
return;
171176
}
172177
cb(null);
173-
}).catch(err => {
178+
}).catch((err: Error) => {
174179
if (err instanceof NotFoundException || err instanceof KMSInvalidStateException) {
175-
logger.info('AWS KMS: key does not exist or is already pending deletion', { masterKeyId, error: err });
180+
// master key does not exist or is already pending deletion
181+
logger.warn('AWS KMS: key does not exist or is already pending deletion', { masterKeyId, error: err });
176182
return cb(null);
177183
}
178184
const error = arsenalErrorAWSKMS(err);
@@ -204,7 +210,7 @@ export default class Client implements KMSInterface {
204210
const isolatedPlaintext = this.safePlaintext(data.Plaintext as Buffer);
205211
logger.debug('AWS KMS: data key generated');
206212
cb(null, isolatedPlaintext, Buffer.from(data.CiphertextBlob as Uint8Array));
207-
}).catch(err => {
213+
}).catch((err: Error) => {
208214
const error = arsenalErrorAWSKMS(err);
209215
logger.error('AWS KMS: failed to generate data key', { err });
210216
cb(error);
@@ -238,8 +244,7 @@ export default class Client implements KMSInterface {
238244

239245
logger.debug('AWS KMS: data key ciphered');
240246
cb(null, Buffer.from(data.CiphertextBlob as Uint8Array));
241-
return;
242-
}).catch(err => {
247+
}).catch((err: Error) => {
243248
const error = arsenalErrorAWSKMS(err);
244249
logger.error('AWS KMS: failed to cipher data key', { err });
245250
cb(error);
@@ -274,23 +279,29 @@ export default class Client implements KMSInterface {
274279

275280
logger.debug('AWS KMS: data key deciphered');
276281
cb(null, isolatedPlaintext);
277-
}).catch(err => {
282+
}).catch((err: Error) => {
278283
const error = arsenalErrorAWSKMS(err);
279284
logger.error('AWS KMS: failed to decipher data key', { err });
280285
cb(error);
281286
});
282287
}
283288

284289
/**
285-
* Healthcheck function to verify KMS connectivity
290+
* NOTE1: S3C-4833 KMS healthcheck is disabled in CloudServer.
291+
*
292+
* For the Arsenal client library we intentionally keep this as a no-op
293+
* to avoid making extra AWS KMS calls (which can incur costs and require
294+
* additional permissions). Callers should rely on higher-level health
295+
* checks provided by their services instead of this method.
286296
*/
297+
/*
287298
healthcheck(logger: werelogs.Logger, cb: (err: Error | null) => void): void {
288299
logger.debug("AWS KMS: performing healthcheck");
289-
300+
290301
const command = new ListKeysCommand({
291302
Limit: 1,
292303
});
293-
304+
294305
this.client.send(command).then(() => {
295306
logger.debug("AWS KMS healthcheck: list keys succeeded");
296307
cb(null);
@@ -300,4 +311,5 @@ export default class Client implements KMSInterface {
300311
cb(error);
301312
});
302313
}
314+
*/
303315
}

lib/network/utils.ts

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ArsenalError, errorInstances } from '../errors';
22
import { allowedKmsErrors } from '../errors/kmsErrors';
3+
import { S3ServiceException } from '@aws-sdk/client-s3';
4+
import { KMSServiceException } from '@aws-sdk/client-kms';
35

46
/**
57
* Normalize errors according to arsenal definitions with a custom prefix
@@ -31,40 +33,50 @@ export function arsenalErrorKMIP(err: string | Error) {
3133

3234
const allowedKmsErrorCodes = Object.keys(allowedKmsErrors) as unknown as (keyof typeof allowedKmsErrors)[];
3335

34-
// Local AWSError type for compatibility with v3 error handling
35-
export type AWSError = Error & {
36-
name?: string;
37-
$fault?: 'client' | 'server';
38-
$metadata?: {
39-
httpStatusCode?: number;
40-
requestId?: string;
41-
attempts?: number;
42-
totalRetryDelay?: number;
43-
};
44-
$retryable?: {
45-
throttling?: boolean;
46-
};
47-
message?: string;
48-
};
36+
type AwsSdkError = (S3ServiceException | KMSServiceException | (Error & {
37+
name: string;
38+
$metadata?: { [key: string]: unknown };
39+
}));
4940

50-
function isAWSError(err: string | Error | AWSError): err is AWSError {
51-
return (err as AWSError).name !== undefined
52-
&& (err as AWSError).$metadata !== undefined;
41+
function getAwsErrorCode(err: unknown): string | undefined {
42+
if (err instanceof S3ServiceException || err instanceof KMSServiceException) {
43+
return err.name;
44+
}
45+
46+
if (err instanceof Error && typeof err.name === 'string') {
47+
// AWS SDK v3 errors inherit from Error but are not always instances of the
48+
// exported Exception classes once they cross async boundaries.
49+
// They still expose metadata markers such as `$metadata` and an error `name`.
50+
const maybeAwsMetadata = (err as AwsSdkError).$metadata;
51+
if (maybeAwsMetadata && typeof maybeAwsMetadata === 'object') {
52+
return err.name;
53+
}
54+
55+
if (allowedKmsErrorCodes.includes(err.name as keyof typeof allowedKmsErrors)) {
56+
return err.name;
57+
}
58+
}
59+
60+
return undefined;
5361
}
5462

55-
export function arsenalErrorAWSKMS(err: string | Error | AWSError) {
56-
if (isAWSError(err)) {
57-
const errorCode = err.name;
63+
export function arsenalErrorAWSKMS(err: string | Error | S3ServiceException) {
64+
const awsErrorCode = getAwsErrorCode(err);
65+
66+
if (awsErrorCode) {
67+
const errorCode = awsErrorCode;
68+
const errorMessage = err instanceof Error ? err.message : String(err);
69+
5870
if (allowedKmsErrorCodes.includes(errorCode as keyof typeof allowedKmsErrors)) {
59-
return errorInstances[`KMS.${errorCode}`].customizeDescription(err.message);
71+
return errorInstances[`KMS.${errorCode}`].customizeDescription(errorMessage);
6072
} else {
6173
// Encapsulate into a generic ArsenalError but keep the aws error code
6274
return ArsenalError.unflatten({
6375
is_arsenal_error: true,
6476
type: `KMS.${errorCode}`, // aws s3 prefix kms errors with KMS.
6577
code: 500,
6678
description: `unexpected AWS_KMS error`,
67-
stack: err.stack,
79+
stack: err instanceof Error ? err.stack : undefined,
6880
});
6981
}
7082
}

lib/storage/data/LocationConstraintParser.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const { http, https } = require('httpagent');
22
const url = require('url');
3-
const { S3Client } = require('@aws-sdk/client-s3');
43
const { NodeHttpHandler } = require('@smithy/node-http-handler');
54
const { fromIni } = require('@aws-sdk/credential-providers');
65
const Sproxy = require('sproxydclient');

0 commit comments

Comments
 (0)