Skip to content

Commit 1dde787

Browse files
committed
code migration from aws-sdkv2 to v3
Issue: ARSN-514
1 parent ff38a02 commit 1dde787

File tree

14 files changed

+2150
-1701
lines changed

14 files changed

+2150
-1701
lines changed

index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,8 @@ export const storage = {
153153
AwsClient: require('./lib/storage/data/external/AwsClient'),
154154
AzureClient: require('./lib/storage/data/external/AzureClient'),
155155
GcpClient: require('./lib/storage/data/external/GcpClient'),
156-
GCP: require('./lib/storage/data/external/GCP/GcpService'),
156+
GCP: require('./lib/storage/data/external/GCP'),
157157
GcpUtils: require('./lib/storage/data/external/GCP/GcpUtils'),
158-
GcpSigner: require('./lib/storage/data/external/GCP/GcpSigner'),
159158
PfsClient: require('./lib/storage/data/external/PfsClient'),
160159
backendUtils: require('./lib/storage/data/external/utils'),
161160
},

lib/network/kmsAWS/Client.ts

Lines changed: 51 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
'use strict';
22

33
import { arsenalErrorAWSKMS } from '../utils';
4-
import { Agent as HttpAgent } from 'http';
5-
import { Agent as HttpsAgent } from 'https';
6-
import { KMS, AWSError } from 'aws-sdk';
4+
import { KMSClient, CreateKeyCommand, ScheduleKeyDeletionCommand,
5+
GenerateDataKeyCommand, EncryptCommand, DecryptCommand, ListKeysCommand } from '@aws-sdk/client-kms';
76
import * as werelogs from 'werelogs';
87
import assert from 'assert';
98
import { KMSInterface, KmsBackend, getKeyIdFromArn, KmsProtocol, KmsType, makeBackend } from '../KMSInterface';
@@ -45,42 +44,25 @@ interface ClientOptions {
4544

4645
export default class Client implements KMSInterface {
4746
private _supportsDefaultKeyPerAccount: boolean;
48-
private client: KMS;
47+
private client: KMSClient;
4948
public readonly backend: KmsBackend<KmsType.external>;
5049
public readonly noAwsArn?: boolean;
5150

5251
constructor(options: ClientOptions) {
5352
this._supportsDefaultKeyPerAccount = true;
54-
const { providerName, tls, ak, sk, region, endpoint, noAwsArn } = options.kmsAWS;
53+
const { providerName, ak, sk, region, endpoint, noAwsArn } = options.kmsAWS;
5554

56-
const httpOptions = tls ? {
57-
agent: new HttpsAgent({
58-
keepAlive: true,
59-
rejectUnauthorized: tls.rejectUnauthorized,
60-
ca: tls.ca,
61-
cert: tls.cert,
62-
minVersion: tls.minVersion,
63-
maxVersion: tls.maxVersion,
64-
key: tls.key,
65-
}),
66-
} : {
67-
agent: new HttpAgent({
68-
keepAlive: true,
69-
}),
70-
};
7155

7256
const credentials = (ak && sk) ? {
73-
credentials: {
74-
accessKeyId: ak,
75-
secretAccessKey: sk,
76-
},
57+
accessKeyId: ak,
58+
secretAccessKey: sk,
7759
} : undefined;
7860

79-
this.client = new KMS({
61+
this.client = new KMSClient({
8062
region,
8163
endpoint,
82-
httpOptions,
83-
...credentials,
64+
requestHandler: undefined, // v3 handles agents differently
65+
credentials,
8466
});
8567
this.backend = makeBackend(KmsType.external, KmsProtocol.aws_kms, providerName);
8668
this.noAwsArn = noAwsArn;
@@ -118,31 +100,21 @@ export default class Client implements KMSInterface {
118100

119101
createMasterKey(logger: werelogs.Logger, cb: (err: Error | null, keyId?: string, keyArn?: string) => void): void {
120102
logger.debug('AWS KMS: creating master encryption key');
121-
this.client.createKey({}, (err: AWSError, data) => {
122-
if (err) {
123-
const error = arsenalErrorAWSKMS(err);
124-
logger.error('AWS KMS: failed to create master encryption key', { err });
125-
cb(error);
126-
return;
127-
}
103+
this.client.send(new CreateKeyCommand({})).then(data => {
128104
const keyMetadata = data?.KeyMetadata;
129105
logger.debug("AWS KMS: master encryption key created", { KeyMetadata: keyMetadata });
130106
let keyId: string;
131107
if (this.noAwsArn) {
132-
// Use KeyId when ARN is not wanted
133-
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
134-
keyId = keyMetadata?.KeyId!;
108+
keyId = keyMetadata?.KeyId || '';
135109
} else {
136-
// Prefer ARN, but fall back to KeyId if ARN is missing
137-
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
138-
keyId = keyMetadata?.Arn ?? keyMetadata?.KeyId!;
110+
keyId = keyMetadata?.Arn ?? (keyMetadata?.KeyId || '');
139111
}
140-
// May produce double arn prefix: scality arn + aws arn
141-
// arn:scality:kms:external:aws_kms:custom:key/arn:aws:kms:region:accountId:key/cbd69d33-ba8e-4b56-8cfe
142-
// If this is a problem, a config flag should be used to hide the scality arn when returning the KMS KeyId
143-
// or aws arn when creating the KMS Key
144112
const arn = `${this.backend.arnPrefix}${keyId}`;
145113
cb(null, keyId, arn);
114+
}).catch(err => {
115+
const error = arsenalErrorAWSKMS(err);
116+
logger.error('AWS KMS: failed to create master encryption key', { err });
117+
cb(error);
146118
});
147119
}
148120

@@ -163,30 +135,23 @@ export default class Client implements KMSInterface {
163135
KeyId: masterKeyId,
164136
PendingWindowInDays: 7,
165137
};
166-
this.client.scheduleKeyDeletion(params, (err: AWSError, data) => {
167-
if (err) {
168-
if (err.code === 'NotFoundException' || err.code === 'KMSInvalidStateException') {
169-
// master key does not exist or is already pending deletion
170-
logger.info('AWS KMS: key does not exist or is already pending deletion',
171-
{ masterKeyId, error: err });
172-
cb(null);
173-
return;
174-
}
175-
176-
const error = arsenalErrorAWSKMS(err);
177-
logger.error('AWS KMS: failed to delete master encryption key', { err });
178-
cb(error);
179-
return;
180-
}
181-
138+
this.client.send(new ScheduleKeyDeletionCommand(params)).then(data => {
182139
if (data?.KeyState && data.KeyState !== 'PendingDeletion') {
183140
const error = arsenalErrorAWSKMS('key is not in PendingDeletion state');
184-
logger.error('AWS KMS: failed to delete master encryption key', { err, data });
141+
logger.error('AWS KMS: failed to delete master encryption key', { data });
185142
cb(error);
186143
return;
187144
}
188-
189145
cb(null);
146+
}).catch(err => {
147+
if (err.name === 'NotFoundException' || err.name === 'KMSInvalidStateException') {
148+
logger.info('AWS KMS: key does not exist or is already pending deletion', { masterKeyId, error: err });
149+
cb(null);
150+
return;
151+
}
152+
const error = arsenalErrorAWSKMS(err);
153+
logger.error('AWS KMS: failed to delete master encryption key', { err });
154+
cb(error);
190155
});
191156
}
192157

@@ -199,31 +164,24 @@ export default class Client implements KMSInterface {
199164
const masterKeyId = getKeyIdFromArn(masterKeyIdOrArn);
200165
logger.debug("AWS KMS: generating data key", { cryptoScheme, masterKeyId, masterKeyIdOrArn });
201166
assert.strictEqual(cryptoScheme, 1);
202-
203167
const params = {
204168
KeyId: masterKeyId,
205-
KeySpec: 'AES_256',
169+
KeySpec: "AES_256" as const,
206170
};
207-
208-
this.client.generateDataKey(params, (err: AWSError, data) => {
209-
if (err) {
210-
const error = arsenalErrorAWSKMS(err);
211-
logger.error('AWS KMS: failed to generate data key', { err });
212-
cb(error);
213-
return;
214-
}
215-
171+
this.client.send(new GenerateDataKeyCommand(params)).then(data => {
216172
if (!data) {
217173
const error = arsenalErrorAWSKMS("failed to generate data key: empty response");
218174
logger.error("AWS KMS: failed to generate data key: empty response");
219175
cb(error);
220176
return;
221177
}
222-
223178
const isolatedPlaintext = this.safePlaintext(data.Plaintext as Buffer);
224-
225179
logger.debug('AWS KMS: data key generated');
226180
cb(null, isolatedPlaintext, Buffer.from(data.CiphertextBlob as Uint8Array));
181+
}).catch(err => {
182+
const error = arsenalErrorAWSKMS(err);
183+
logger.error('AWS KMS: failed to generate data key', { err });
184+
cb(error);
227185
});
228186
}
229187

@@ -244,14 +202,7 @@ export default class Client implements KMSInterface {
244202
Plaintext: plainTextDataKey,
245203
};
246204

247-
this.client.encrypt(params, (err: AWSError, data) => {
248-
if (err) {
249-
const error = arsenalErrorAWSKMS(err);
250-
logger.error('AWS KMS: failed to cipher data key', { err });
251-
cb(error);
252-
return;
253-
}
254-
205+
this.client.send(new EncryptCommand(params)).then(data => {
255206
if (!data) {
256207
const error = arsenalErrorAWSKMS("failed to cipher data key: empty response");
257208
logger.error("AWS KMS: failed to cipher data key: empty response");
@@ -262,6 +213,10 @@ export default class Client implements KMSInterface {
262213
logger.debug('AWS KMS: data key ciphered');
263214
cb(null, Buffer.from(data.CiphertextBlob as Uint8Array));
264215
return;
216+
}).catch(err => {
217+
const error = arsenalErrorAWSKMS(err);
218+
logger.error('AWS KMS: failed to cipher data key', { err });
219+
cb(error);
265220
});
266221
}
267222

@@ -281,14 +236,7 @@ export default class Client implements KMSInterface {
281236
CiphertextBlob: cipheredDataKey,
282237
};
283238

284-
this.client.decrypt(params, (err: AWSError, data) => {
285-
if (err) {
286-
const error = arsenalErrorAWSKMS(err);
287-
logger.error('AWS KMS: failed to decipher data key', { err });
288-
cb(error);
289-
return;
290-
}
291-
239+
this.client.send(new DecryptCommand(params)).then(data => {
292240
if (!data) {
293241
const error = arsenalErrorAWSKMS("failed to decipher data key: empty response");
294242
logger.error("AWS KMS: failed to decipher data key: empty response");
@@ -300,41 +248,30 @@ export default class Client implements KMSInterface {
300248

301249
logger.debug('AWS KMS: data key deciphered');
302250
cb(null, isolatedPlaintext);
251+
}).catch(err => {
252+
const error = arsenalErrorAWSKMS(err);
253+
logger.error('AWS KMS: failed to decipher data key', { err });
254+
cb(error);
303255
});
304256
}
305257

306258
/**
307-
* NOTE1: S3C-4833 KMS healthcheck is disabled in CloudServer
308-
* NOTE2: The best approach for implementing the AWS KMS health check is still under consideration.
309-
* In the meantime, this method is commented out to prevent potential issues related to costs or permissions.
310-
*
311-
* Reasons for commenting out:
312-
* - frequent API calls can lead to increased expenses.
313-
* - access key secret key used must have `kms:ListKeys` permissions
314-
*
315-
* Future potential actions:
316-
* - implement caching mechanisms to reduce the number of API calls.
317-
* - differentiate between error types (e.g., 500 vs. 403) for more effective error handling.
259+
* Healthcheck function to verify KMS connectivity
318260
*/
319-
/*
320261
healthcheck(logger: werelogs.Logger, cb: (err: Error | null) => void): void {
321262
logger.debug("AWS KMS: performing healthcheck");
322263

323-
const params = {
264+
const command = new ListKeysCommand({
324265
Limit: 1,
325-
};
326-
327-
this.client.listKeys(params, (err, data) => {
328-
if (err) {
329-
const error = arsenalErrorAWSKMS(err);
330-
logger.error("AWS KMS healthcheck: failed to list keys", { err });
331-
cb(error);
332-
return;
333-
}
266+
});
334267

268+
this.client.send(command).then(() => {
335269
logger.debug("AWS KMS healthcheck: list keys succeeded");
336270
cb(null);
271+
}).catch(err => {
272+
const error = arsenalErrorAWSKMS(err);
273+
logger.error("AWS KMS healthcheck: failed to list keys", { err });
274+
cb(error);
337275
});
338276
}
339-
*/
340277
}

lib/network/utils.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { AWSError } from 'aws-sdk';
21
import { ArsenalError, errorInstances } from '../errors';
32
import { allowedKmsErrors } from '../errors/kmsErrors';
43

@@ -32,6 +31,15 @@ export function arsenalErrorKMIP(err: string | Error) {
3231

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

34+
// Local AWSError type for compatibility with v3 error handling
35+
export type AWSError = Error & {
36+
code?: string;
37+
retryable?: boolean;
38+
statusCode?: number;
39+
time?: Date;
40+
requestId?: string;
41+
};
42+
3543
function isAWSError(err: string | Error | AWSError): err is AWSError {
3644
return (err as AWSError).code !== undefined
3745
&& (err as AWSError).retryable !== undefined;

lib/storage/data/LocationConstraintParser.js

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const { http, https } = require('httpagent');
22
const url = require('url');
3-
const AWS = require('aws-sdk');
3+
const { S3Client } = require('@aws-sdk/client-s3');
4+
const { fromIni } = require('@aws-sdk/credential-providers');
45
const Sproxy = require('sproxydclient');
56
const Hyperdrive = require('@scality/hdclient');
67
const HttpsProxyAgent = require('https-proxy-agent');
@@ -83,30 +84,19 @@ function parseLC(config, vault) {
8384
const s3Params = {
8485
endpoint: `${protocol}://${endpoint}`,
8586
region,
86-
debug: false,
87-
// Not implemented yet for streams in node sdk,
88-
// and has no negative impact if stream, so let's
89-
// leave it in for future use
90-
computeChecksums: true,
91-
httpOptions,
92-
// needed for encryption
93-
signatureVersion,
94-
sslEnabled,
95-
maxRetries: 0,
96-
s3ForcePathStyle: pathStyle,
97-
customUserAgent: constants.productName,
87+
forcePathStyle: pathStyle,
88+
maxAttempts: 1,
89+
requestHandler: undefined, // v3 handles agents differently
90+
// v3 does not use signatureVersion or sslEnabled directly
91+
// v3 does not use customUserAgent directly
9892
};
99-
// users can either include the desired profile name from their
100-
// ~/.aws/credentials file or include the accessKeyId and
101-
// secretAccessKey directly in the locationConfig
10293
if (locationObj.details.credentialsProfile) {
103-
s3Params.credentials = new AWS.SharedIniFileCredentials({
104-
profile: locationObj.details.credentialsProfile });
94+
s3Params.credentials = fromIni({ profile: locationObj.details.credentialsProfile });
10595
} else {
106-
s3Params.accessKeyId =
107-
locationObj.details.credentials.accessKey;
108-
s3Params.secretAccessKey =
109-
locationObj.details.credentials.secretKey;
96+
s3Params.credentials = {
97+
accessKeyId: locationObj.details.credentials.accessKey,
98+
secretAccessKey: locationObj.details.credentials.secretKey,
99+
};
110100
}
111101
const clientConfig = {
112102
s3Params,

0 commit comments

Comments
 (0)