11'use strict' ;
22
33import { 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' ;
76import * as werelogs from 'werelogs' ;
87import assert from 'assert' ;
98import { KMSInterface , KmsBackend , getKeyIdFromArn , KmsProtocol , KmsType , makeBackend } from '../KMSInterface' ;
@@ -45,42 +44,25 @@ interface ClientOptions {
4544
4645export 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}
0 commit comments