Skip to content

Commit 579ea15

Browse files
ARSN-503: Update AWS KMS error render
Always return aws error code as is prefixed with KMS For some error return the same error code and message For others hide potentially sensitive message
1 parent a0d1d15 commit 579ea15

File tree

3 files changed

+123
-8
lines changed

3 files changed

+123
-8
lines changed

lib/errors/arsenalErrors.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { allowedKmsErrors } from './kmsErrors';
2+
13
export type ErrorFormat = {
24
code: number,
35
description: string,
@@ -1044,13 +1046,13 @@ export const AuthMethodNotImplemented: ErrorFormat = {
10441046
};
10451047

10461048
// -------------------- KMS --------------------
1047-
// Most other KMS Exception can't be converted to from KMIP
1048-
const NotFoundException: ErrorFormat = {
1049-
code: 400, // Not 404 because it's the KMS (Encrypt/Decrypt) that fails, not the object API
1050-
description: 'The request was rejected because the specified entity or resource could not be found.'
1051-
};
1049+
type Entries<T> = {
1050+
[K in keyof T]: [K, T[K]];
1051+
}[keyof T];
10521052

10531053
/** need typescript@5.6 for string literal export
10541054
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-6.html#support-for-arbitrary-module-identifiers
10551055
*/
1056-
module.exports['KMS.NotFoundException'] = NotFoundException;
1056+
for (const [kmsErr, err] of Object.entries(allowedKmsErrors) as Entries<typeof allowedKmsErrors>[]) {
1057+
module.exports[`KMS.${kmsErr}`] = err;
1058+
}

lib/errors/kmsErrors.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* List of AWS KMS errors that are specific to KMS and where the error
3+
* can be returned to the user as is with a specific status code.
4+
*
5+
* Avoid any error that might leak sensitive information such as hostnames
6+
* or IP addresses from network related errors.
7+
* KeyId is not considered sensitive.
8+
*
9+
* Reference: https://docs.aws.amazon.com/kms/latest/APIReference/CommonErrors.html
10+
* See specific actions like CreateKey, Encrypt, Decrypt, etc. for more details.
11+
*
12+
* Other errors not listed will return only the same error code but HTTP status 500,
13+
* and message will be generic and details will be in logs only.
14+
*/
15+
export const allowedKmsErrors = {
16+
AccessDeniedException: {
17+
code: 400,
18+
description: 'You do not have sufficient access to perform this action.',
19+
},
20+
AlreadyExistsException: {
21+
code: 400,
22+
description: 'The request was rejected because it attempted to create a resource that already exists.',
23+
},
24+
DisabledException: {
25+
code: 400,
26+
description: 'The request was rejected because the specified KMS key is not enabled.',
27+
},
28+
IncorrectKeyException: {
29+
code: 400,
30+
description: 'The request was rejected because the specified KMS key cannot decrypt the data',
31+
},
32+
InvalidAliasNameException: {
33+
code: 400,
34+
description: 'The request was rejected because the specified alias name is not valid.',
35+
},
36+
InvalidArnException: {
37+
code: 400,
38+
description: 'The request was rejected because a specified ARN, or an ARN in a key policy, is not valid.',
39+
},
40+
InvalidCiphertextException: {
41+
code: 400,
42+
description: 'The request was rejected because the specified ciphertext, or additional authenticated data, ' +
43+
'is corrupted, missing, or otherwise invalid.',
44+
},
45+
InvalidGrantTokenException: {
46+
code: 400,
47+
description: 'The request was rejected because the specified grant token is not valid.',
48+
},
49+
InvalidKeyUsageException: {
50+
code: 400,
51+
description: 'The KeyUsage or algorithm is incompatible with the API operation.',
52+
},
53+
KMSInternalException: {
54+
code: 500,
55+
description: 'The request was rejected because an internal exception occurred. The request can be retried.',
56+
},
57+
KMSInvalidStateException: {
58+
code: 400,
59+
description:
60+
'The request was rejected because the state of the specified resource is not valid for this request.',
61+
},
62+
KeyUnavailableException: {
63+
code: 500,
64+
description: 'The request was rejected because the specified KMS key was not available. ' +
65+
'You can retry the request.',
66+
},
67+
LimitExceededException: {
68+
code: 400,
69+
description: 'The request was rejected because a length constraint or quota was exceeded.',
70+
},
71+
MalformedPolicyDocumentException: {
72+
code: 400,
73+
description:
74+
'The request was rejected because the specified policy is not syntactically or semantically correct.',
75+
},
76+
/** Not 404 because it's the KMS (Encrypt/Decrypt) that fails, not the object API */
77+
NotFoundException: {
78+
code: 400,
79+
description: 'The request was rejected because the specified entity or resource could not be found.',
80+
},
81+
TagException: {
82+
code: 400,
83+
description: 'The request was rejected because one or more tags are not valid.',
84+
},
85+
UnsupportedOperationException: {
86+
code: 400,
87+
description: 'The request was rejected because a specified parameter is not supported or a specified ' +
88+
'resource is not valid for this operation.',
89+
},
90+
} as const;

lib/network/utils.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { errorInstances } from '../errors';
1+
import type { AWSError } from 'aws-sdk';
2+
import { ArsenalError, errorInstances } from '../errors';
3+
import { allowedKmsErrors } from '../errors/kmsErrors';
24

35
/**
46
* Normalize errors according to arsenal definitions with a custom prefix
@@ -28,6 +30,27 @@ export function arsenalErrorKMIP(err: string | Error) {
2830
return _normalizeArsenalError(err, 'KMIP:');
2931
}
3032

31-
export function arsenalErrorAWSKMS(err: string | Error) {
33+
const allowedKmsErrorCodes = Object.keys(allowedKmsErrors) as unknown as (keyof typeof allowedKmsErrors)[];
34+
35+
function isAWSError(err: string | Error | AWSError): err is AWSError {
36+
return (err as AWSError).code !== undefined
37+
&& (err as AWSError).retryable !== undefined;
38+
}
39+
40+
export function arsenalErrorAWSKMS(err: string | Error | AWSError) {
41+
if (isAWSError(err)) {
42+
if (allowedKmsErrorCodes.includes(err.code as keyof typeof allowedKmsErrors)) {
43+
return errorInstances[`KMS.${err.code}`].customizeDescription(err.message);
44+
} else {
45+
// Encapsulate into a generic ArsenalError but keep the aws error code
46+
return ArsenalError.unflatten({
47+
is_arsenal_error: true,
48+
type: `KMS.${err.code}`, // aws s3 prefix kms errors with KMS.
49+
code: 500,
50+
description: `unexpected AWS_KMS error`,
51+
stack: err.stack,
52+
});
53+
}
54+
}
3255
return _normalizeArsenalError(err, 'AWS_KMS:');
3356
}

0 commit comments

Comments
 (0)