Skip to content

Commit 54f8afb

Browse files
CLDSRV-670: Test KMS error returned to end user
1 parent 79f0227 commit 54f8afb

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

tests/functional/sse-kms-migration/arnPrefix.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,3 +505,163 @@ describe('ensure MPU use good SSE', () => {
505505
);
506506
});
507507
});
508+
describe('KMS error', () => {
509+
const sseConfig = { algo: 'aws:kms', masterKeyId: true };
510+
const Bucket = 'bkt-kms-err';
511+
const Key = 'obj';
512+
const body = 'content';
513+
514+
let mpuEncrypted;
515+
let mpuPlaintext;
516+
517+
let masterKeyId;
518+
let masterKeyArn;
519+
520+
let expected;
521+
522+
const expectedKMIP = {
523+
code: 'KMS.NotFoundException',
524+
msg: (action, keyId) => new RegExp(`^KMS \\(KMIP\\) error for ${action} on ${keyId}\\..*`),
525+
};
526+
const expectedAWS = {
527+
code: 'KMS.KMSInvalidStateException',
528+
msg: (_, keyId) => new RegExp(`${keyId} is pending deletion\\.`),
529+
};
530+
/**
531+
* localkms container returns a different error message when the key is pending deletion
532+
* as we decrypt without passing the keyId, so we need to handle it separately
533+
*/
534+
const expectedLocalKms = {
535+
code: 'KMS.AccessDeniedException',
536+
msg: () => new RegExp(
537+
'The ciphertext refers to a customer master key that does not exist, ' +
538+
'does not exist in this region, or you are not allowed to access\\.'
539+
),
540+
};
541+
if (helpers.config.backends.kms === 'kmip') {
542+
expected = expectedKMIP;
543+
} else if (helpers.config.backends.kms === 'aws') {
544+
expected = expectedAWS;
545+
} else {
546+
throw new Error(`Unsupported KMS backend: ${helpers.config.backends.kms}`);
547+
}
548+
549+
function assertKmsError(action, keyId) {
550+
return err => {
551+
if (helpers.config.backends.kms === 'aws' && action === 'Decrypt') {
552+
assert.strictEqual(err.name, expectedLocalKms.code);
553+
assert.match(err.message, expectedLocalKms.msg(action, keyId));
554+
return true;
555+
}
556+
assert.strictEqual(err.name, expected.code);
557+
assert.match(err.message, expected.msg(action, keyId));
558+
return true;
559+
};
560+
}
561+
562+
before(async () => {
563+
void await helpers.s3.createBucket({ Bucket }).promise();
564+
565+
await helpers.s3.putObject({
566+
...helpers.putObjParams(Bucket, 'plaintext', {}, null),
567+
Body: body,
568+
}).promise();
569+
570+
mpuPlaintext = await helpers.s3.createMultipartUpload(
571+
helpers.putObjParams(Bucket, 'mpuPlaintext', {}, null)).promise();
572+
573+
({ masterKeyId, masterKeyArn } = await helpers.createKmsKey(log));
574+
575+
await helpers.putEncryptedObject(Bucket, Key, sseConfig, masterKeyArn, body);
576+
// ensure we can decrypt and read the object
577+
const obj = await helpers.s3.getObject({ Bucket, Key }).promise();
578+
assert.strictEqual(obj.Body.toString(), body);
579+
580+
mpuEncrypted = await helpers.s3.createMultipartUpload(
581+
helpers.putObjParams(Bucket, 'mpuEncrypted', sseConfig, masterKeyArn)).promise();
582+
583+
// make key unavailable
584+
void await helpers.destroyKmsKey(masterKeyArn, log);
585+
});
586+
587+
after(async () => {
588+
void await helpers.cleanup(Bucket);
589+
if (masterKeyArn) {
590+
try {
591+
void await helpers.destroyKmsKey(masterKeyArn, log);
592+
} catch (e) { void e; }
593+
[masterKeyArn, masterKeyId] = [null, null];
594+
}
595+
});
596+
597+
const testCases = [
598+
{
599+
action: 'putObject', kmsAction: 'Encrypt',
600+
fct: async ({ masterKeyArn }) =>
601+
helpers.putEncryptedObject(Bucket, 'fail', sseConfig, masterKeyArn, body),
602+
},
603+
{
604+
action: 'getObject', kmsAction: 'Decrypt',
605+
fct: async () => helpers.s3.getObject({ Bucket, Key }).promise(),
606+
},
607+
{
608+
action: 'copyObject', detail: ' when getting from source', kmsAction: 'Decrypt',
609+
fct: async () =>
610+
helpers.s3.copyObject({ Bucket, Key: 'copy', CopySource: `${Bucket}/${Key}` }).promise(),
611+
},
612+
{
613+
action: 'copyObject', detail: ' when putting to destination', kmsAction: 'Encrypt',
614+
fct: async ({ masterKeyArn }) => helpers.s3.copyObject({
615+
Bucket,
616+
Key: 'copyencrypted',
617+
CopySource: `${Bucket}/plaintext`,
618+
ServerSideEncryption: 'aws:kms',
619+
SSEKMSKeyId: masterKeyArn,
620+
}).promise(),
621+
},
622+
{
623+
action: 'createMPU', kmsAction: 'Encrypt',
624+
fct: async ({ masterKeyArn }) => helpers.s3.createMultipartUpload(
625+
helpers.putObjParams(Bucket, 'mpuKeyEncryptedFail', sseConfig, masterKeyArn)).promise(),
626+
},
627+
{
628+
action: 'mpu uploadPartCopy', detail: ' when getting from source', kmsAction: 'Decrypt',
629+
fct: async ({ mpuPlaintext }) => helpers.s3.uploadPartCopy({
630+
UploadId: mpuPlaintext.UploadId,
631+
Bucket,
632+
Key: 'mpuPlaintext',
633+
PartNumber: 1,
634+
CopySource: `${Bucket}/${Key}`,
635+
}).promise(),
636+
},
637+
{
638+
action: 'mpu uploadPart', detail: ' when putting to destination', kmsAction: 'Encrypt',
639+
fct: async ({ mpuEncrypted }) => helpers.s3.uploadPart({
640+
UploadId: mpuEncrypted.UploadId,
641+
Bucket,
642+
Key: 'mpuEncrypted',
643+
PartNumber: 1,
644+
Body: body,
645+
}).promise(),
646+
},
647+
{
648+
action: 'mpu uploadPartCopy', detail: ' when putting to destination', kmsAction: 'Encrypt',
649+
fct: async ({ mpuEncrypted }) => helpers.s3.uploadPartCopy({
650+
UploadId: mpuEncrypted.UploadId,
651+
Bucket,
652+
Key: 'mpuEncrypted',
653+
PartNumber: 1,
654+
CopySource: `${Bucket}/plaintext`,
655+
}).promise(),
656+
},
657+
];
658+
659+
testCases.forEach(({ action, kmsAction, fct, detail }) => {
660+
it(`${action} should fail with kms error${detail || ''}`, async () => {
661+
await assert.rejects(
662+
fct({ masterKeyArn, mpuEncrypted, mpuPlaintext }),
663+
assertKmsError(kmsAction, masterKeyId),
664+
);
665+
});
666+
});
667+
});

tests/functional/sse-kms-migration/helpers.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ async function createKmsKey(log) {
9494
});
9595
}
9696

97+
const destroyKmsKey = promisify(kms.destroyBucketKey);
98+
9799
async function cleanup(Bucket) {
98100
await bucketUtil.empty(Bucket);
99101
await s3.deleteBucket({ Bucket }).promise();
@@ -112,5 +114,6 @@ module.exports = {
112114
putEncryptedObject,
113115
getObjectMDSSE,
114116
createKmsKey,
117+
destroyKmsKey,
115118
cleanup,
116119
};

0 commit comments

Comments
 (0)