Completed kms
This commit is contained in:
parent
c34ea76e4e
commit
1dc45267ac
File diff suppressed because it is too large
Load Diff
|
|
@ -10,6 +10,7 @@
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-kms": "^3.716.0",
|
||||||
"@nestjs/common": "^10.4.15",
|
"@nestjs/common": "^10.4.15",
|
||||||
"@nestjs/config": "^3.3.0",
|
"@nestjs/config": "^3.3.0",
|
||||||
"@nestjs/core": "^10.4.15",
|
"@nestjs/core": "^10.4.15",
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `updatedAt` to the `KmsAlias` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_KmsAlias" (
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"kmsKeyId" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY ("accountId", "region", "name"),
|
||||||
|
CONSTRAINT "KmsAlias_kmsKeyId_fkey" FOREIGN KEY ("kmsKeyId") REFERENCES "KmsKey" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_KmsAlias" ("accountId", "kmsKeyId", "name", "region") SELECT "accountId", "kmsKeyId", "name", "region" FROM "KmsAlias";
|
||||||
|
DROP TABLE "KmsAlias";
|
||||||
|
ALTER TABLE "new_KmsAlias" RENAME TO "KmsAlias";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to alter the column `key` on the `KmsKey` table. The data in that column could be lost. The data in that column will be cast from `String` to `Binary`.
|
||||||
|
- Added the required column `enabled` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `keyState` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `multiRegion` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `origin` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `policy` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `updatedAt` to the `KmsKey` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_KmsKey" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"enabled" BOOLEAN NOT NULL,
|
||||||
|
"usage" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"keySpec" TEXT NOT NULL,
|
||||||
|
"keyState" TEXT NOT NULL,
|
||||||
|
"origin" TEXT NOT NULL,
|
||||||
|
"multiRegion" BOOLEAN NOT NULL,
|
||||||
|
"policy" TEXT NOT NULL,
|
||||||
|
"key" BLOB NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_KmsKey" ("accountId", "createdAt", "description", "id", "key", "keySpec", "region", "usage") SELECT "accountId", "createdAt", "description", "id", "key", "keySpec", "region", "usage" FROM "KmsKey";
|
||||||
|
DROP TABLE "KmsKey";
|
||||||
|
ALTER TABLE "new_KmsKey" RENAME TO "KmsKey";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "KmsKey" ADD COLUMN "nextRotation" DATETIME;
|
||||||
|
ALTER TABLE "KmsKey" ADD COLUMN "rotationPeriod" INTEGER;
|
||||||
|
|
@ -29,19 +29,33 @@ model KmsAlias {
|
||||||
accountId String
|
accountId String
|
||||||
region String
|
region String
|
||||||
kmsKeyId String
|
kmsKeyId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
kmsKey KmsKey @relation(fields: [kmsKeyId], references: [id])
|
||||||
|
|
||||||
@@id([accountId, region, name])
|
@@id([accountId, region, name])
|
||||||
}
|
}
|
||||||
|
|
||||||
model KmsKey {
|
model KmsKey {
|
||||||
id String @id
|
id String @id
|
||||||
|
enabled Boolean
|
||||||
usage String
|
usage String
|
||||||
description String
|
description String
|
||||||
keySpec String
|
keySpec String
|
||||||
key String
|
keyState String
|
||||||
|
origin String
|
||||||
|
multiRegion Boolean
|
||||||
|
policy String
|
||||||
|
key Bytes
|
||||||
|
rotationPeriod Int?
|
||||||
|
nextRotation DateTime?
|
||||||
accountId String
|
accountId String
|
||||||
region String
|
region String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
aliases KmsAlias[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Format } from "../abstract-action.handler";
|
||||||
export interface RequestContext {
|
export interface RequestContext {
|
||||||
action?: Action;
|
action?: Action;
|
||||||
format?: Format;
|
format?: Format;
|
||||||
requestId: string;
|
readonly requestId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRequest extends Request {
|
export interface IRequest extends Request {
|
||||||
|
|
|
||||||
|
|
@ -154,3 +154,12 @@ export class InvalidArnException extends AwsException {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
export class UnsupportedOperationException extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
UnsupportedOperationException.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ export class TagsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
async createMany(arn: string, records: { key: string, value: string }[]): Promise<void> {
|
||||||
await this.prismaService.attribute.createMany({
|
await this.prismaService.tag.createMany({
|
||||||
data: records.map(r => ({
|
data: records.map(r => ({
|
||||||
name: r.key,
|
name: r.key,
|
||||||
value: r.value,
|
value: r.value,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
AliasName: string;
|
||||||
|
TargetKeyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateAliasHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsCreateAlias;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
TargetKeyId: Joi.string().required(),
|
||||||
|
AliasName: Joi.string().min(1).max(256).regex(new RegExp(`^alias/[a-zA-Z0-9/_-]+$`)).required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ TargetKeyId, AliasName }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keyRecord = await this.kmsService.findOneByRef(TargetKeyId, awsProperties);
|
||||||
|
|
||||||
|
if (!keyRecord) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.kmsService.createAlias({
|
||||||
|
accountId: awsProperties.accountId,
|
||||||
|
region: awsProperties.region,
|
||||||
|
name: AliasName,
|
||||||
|
kmsKey: {
|
||||||
|
connect: {
|
||||||
|
id: keyRecord.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { CustomerMasterKeySpec, KeySpec, KeyState, KeyUsageType, OriginType, Tag } from '@aws-sdk/client-kms';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import { keySpecToUsageType } from './kms-key.entity';
|
||||||
|
import { UnsupportedOperationException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
|
|
||||||
|
type NoUndefinedField<T> = { [P in keyof T]-?: NoUndefinedField<NonNullable<T[P]>> };
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
BypassPolicyLockoutSafetyCheck: boolean;
|
||||||
|
CustomerMasterKeySpec: CustomerMasterKeySpec;
|
||||||
|
CustomKeyStoreId: string;
|
||||||
|
Description: string;
|
||||||
|
KeySpec: KeySpec;
|
||||||
|
KeyUsage: KeyUsageType;
|
||||||
|
MultiRegion: boolean;
|
||||||
|
Origin: OriginType;
|
||||||
|
Policy: string;
|
||||||
|
Tags: NoUndefinedField<Tag>[];
|
||||||
|
XksKeyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateDefaultPolicy = (accountId: string) => JSON.stringify({
|
||||||
|
"Sid": "Enable IAM User Permissions",
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {
|
||||||
|
"AWS": `arn:aws:iam::${accountId}:root`
|
||||||
|
},
|
||||||
|
"Action": "kms:*",
|
||||||
|
"Resource": "*"
|
||||||
|
})
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CreateKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
private readonly tagsService: TagsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsCreateKey;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
BypassPolicyLockoutSafetyCheck: Joi.boolean().default(false),
|
||||||
|
CustomerMasterKeySpec: Joi.string().allow(...Object.values(CustomerMasterKeySpec)),
|
||||||
|
CustomKeyStoreId: Joi.string().min(1).max(64),
|
||||||
|
Description: Joi.string().min(0).max(8192).default(''),
|
||||||
|
KeySpec: Joi.string().allow(...Object.values(KeySpec)).default(KeySpec.SYMMETRIC_DEFAULT),
|
||||||
|
KeyUsage: Joi.string().allow(...Object.values(KeyUsageType)).default(KeyUsageType.ENCRYPT_DECRYPT),
|
||||||
|
MultiRegion: Joi.boolean().default(false),
|
||||||
|
Origin: Joi.string().allow(...Object.values(OriginType)).default(OriginType.AWS_KMS),
|
||||||
|
Policy: Joi.string().min(1).max(32768),
|
||||||
|
Tags: Joi.array().items(
|
||||||
|
Joi.object<Tag, true>({
|
||||||
|
TagKey: Joi.string().min(1).max(128).required(),
|
||||||
|
TagValue: Joi.string().min(0).max(256).required(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
XksKeyId: Joi.when('Origin', {
|
||||||
|
is: OriginType.EXTERNAL_KEY_STORE,
|
||||||
|
then: Joi.string().min(1).max(128),
|
||||||
|
otherwise: Joi.forbidden(),
|
||||||
|
}) as unknown as Joi.StringSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyUsage, Description, KeySpec, Origin, MultiRegion, Policy, Tags, CustomerMasterKeySpec }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keySpec = CustomerMasterKeySpec ?? KeySpec;
|
||||||
|
|
||||||
|
if (!keySpecToUsageType[keySpec].includes(KeyUsage)) {
|
||||||
|
throw new UnsupportedOperationException(`KeySpec ${KeySpec} is not valid for KeyUsage ${KeyUsage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = this.keyGeneratorMap[keySpec]();
|
||||||
|
|
||||||
|
const createdKey = await this.kmsService.createKmsKey({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
enabled: true,
|
||||||
|
usage: KeyUsage,
|
||||||
|
description: Description,
|
||||||
|
keySpec: keySpec,
|
||||||
|
keyState: KeyState.Enabled,
|
||||||
|
origin: Origin,
|
||||||
|
multiRegion: MultiRegion,
|
||||||
|
policy: Policy ?? generateDefaultPolicy(awsProperties.accountId),
|
||||||
|
key,
|
||||||
|
accountId: awsProperties.accountId,
|
||||||
|
region: awsProperties.region,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.tagsService.createMany(createdKey.arn, Tags.map(({ TagKey, TagValue }) => ({ key: TagKey, value: TagValue })));
|
||||||
|
|
||||||
|
return {
|
||||||
|
KeyMetadata: createdKey.metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private keyGeneratorMap: Record<KeySpec, () => Buffer> = {
|
||||||
|
ECC_NIST_P256: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'X9_62_prime256v1' });
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
ECC_NIST_P384: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp384r1' });
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
ECC_NIST_P521: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp521r1' });
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
ECC_SECG_P256K1: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'secp256k1' });
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
HMAC_224: function (): Buffer {
|
||||||
|
return crypto.randomBytes(32);
|
||||||
|
},
|
||||||
|
HMAC_256: function (): Buffer {
|
||||||
|
return crypto.randomBytes(32);
|
||||||
|
},
|
||||||
|
HMAC_384: function (): Buffer {
|
||||||
|
return crypto.randomBytes(32);
|
||||||
|
},
|
||||||
|
HMAC_512: function (): Buffer {
|
||||||
|
return crypto.randomBytes(32);
|
||||||
|
},
|
||||||
|
RSA_2048: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||||
|
modulusLength: 2048,
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'pkcs1',
|
||||||
|
format: 'pem'
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
RSA_3072: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||||
|
modulusLength: 3072,
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'pkcs1',
|
||||||
|
format: 'pem'
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
RSA_4096: function (): Buffer {
|
||||||
|
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'pkcs1',
|
||||||
|
format: 'pem'
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Buffer.from(JSON.stringify({ privateKey, publicKey }));
|
||||||
|
},
|
||||||
|
SM2: function (): Buffer {
|
||||||
|
throw new Error('Function not implemented.');
|
||||||
|
},
|
||||||
|
SYMMETRIC_DEFAULT: function (): Buffer {
|
||||||
|
return crypto.randomBytes(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { breakdownArn } from '../util/breakdown-arn';
|
|
||||||
import { KmsService } from './kms.service';
|
import { KmsService } from './kms.service';
|
||||||
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
|
@ -29,20 +28,7 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
const searchable = KeyId.startsWith('arn') ? breakdownArn(KeyId) : {
|
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
|
||||||
service: 'kms',
|
|
||||||
region: awsProperties.region,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
identifier: KeyId,
|
|
||||||
};
|
|
||||||
const [ type, pk ] = searchable.identifier.split('/');
|
|
||||||
const keyId = await (type === 'key' ? Promise.resolve(pk) : this.kmsService.findKeyIdFromAlias(pk, searchable));
|
|
||||||
|
|
||||||
if (!keyId) {
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyRecord = await this.kmsService.findOneById(keyId);
|
|
||||||
|
|
||||||
if (!keyRecord) {
|
if (!keyRecord) {
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
KeyId: string;
|
||||||
|
RotationPeriodInDays: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class EnableKeyRotationHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsEnableKeyRotation;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
KeyId: Joi.string().required(),
|
||||||
|
RotationPeriodInDays: Joi.number().min(90).max(2560).default(365),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyId, RotationPeriodInDays }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
|
||||||
|
|
||||||
|
if (!keyRecord) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = new Date();
|
||||||
|
next.setDate(next.getDate() + RotationPeriodInDays);
|
||||||
|
|
||||||
|
await this.kmsService.updateKmsKey(keyRecord.id, {
|
||||||
|
rotationPeriod: RotationPeriodInDays,
|
||||||
|
nextRotation: next,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
PolicyName: string;
|
||||||
|
KeyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetKeyPolicyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsGetKeyPolicy;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
KeyId: Joi.string().required(),
|
||||||
|
PolicyName: Joi.string().min(1).max(128).default('default'),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyId, PolicyName }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
|
||||||
|
|
||||||
|
if (!keyRecord) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
PolicyName,
|
||||||
|
Policy: keyRecord.policy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
KeyId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetKeyRotationStatusHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsGetKeyRotationStatus;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
KeyId: Joi.string().required(),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
|
||||||
|
|
||||||
|
if (!keyRecord) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
KeyId: keyRecord.id,
|
||||||
|
KeyRotationEnabled: !!keyRecord.rotationPeriod,
|
||||||
|
NextRotationDate: keyRecord.nextRotation,
|
||||||
|
RotationPeriodInDays: keyRecord.rotationPeriod,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { KmsAlias as PrismaKeyAlias } from "@prisma/client"
|
||||||
|
|
||||||
|
export class KmsAlias implements PrismaKeyAlias {
|
||||||
|
|
||||||
|
name: string
|
||||||
|
accountId: string
|
||||||
|
region: string
|
||||||
|
kmsKeyId: string
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
|
constructor(p: PrismaKeyAlias) {
|
||||||
|
this.name = p.name;
|
||||||
|
this.accountId = p.accountId;
|
||||||
|
this.region = p.region;
|
||||||
|
this.kmsKeyId = p.kmsKeyId;
|
||||||
|
this.createdAt = p.createdAt;
|
||||||
|
this.updatedAt = p.updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
get arn() {
|
||||||
|
return `arn:aws:kms:${this.region}:${this.accountId}:${this.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toAws() {
|
||||||
|
return {
|
||||||
|
AliasArn: this.arn,
|
||||||
|
AliasName: this.name,
|
||||||
|
CreationDate: this.createdAt.getAwsTime(),
|
||||||
|
LastUpdatedDate: this.updatedAt.getAwsTime(),
|
||||||
|
TargetKeyId: this.kmsKeyId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
export class KmsKeyAlias {
|
|
||||||
|
|
||||||
// name: string;
|
|
||||||
// targetKeyId: string;
|
|
||||||
// accountId: string;
|
|
||||||
// region: string;
|
|
||||||
|
|
||||||
// get arn() {
|
|
||||||
// return `arn:aws:kms:${this.region}:${this.accountId}:alias/${this.name}`;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +1,58 @@
|
||||||
|
import { KeySpec, KeyUsageType, KeyState, AlgorithmSpec, OriginType, ExpirationModelType, KeyAgreementAlgorithmSpec, MacAlgorithmSpec, MultiRegionKeyType, SigningAlgorithmSpec } from '@aws-sdk/client-kms';
|
||||||
import { KmsKey as PrismaKmsKey } from '@prisma/client';
|
import { KmsKey as PrismaKmsKey } from '@prisma/client';
|
||||||
|
|
||||||
export type KeySpec = 'RSA_2048' | 'RSA_3072' | 'RSA_4096' | 'ECC_NIST_P256' | 'ECC_NIST_P384' | 'ECC_NIST_P521' | 'ECC_SECG_P256K1' | 'SYMMETRIC_DEFAULT' | 'HMAC_224' | 'HMAC_256' | 'HMAC_384' | 'HMAC_512' | 'SM2';
|
export const keySpecToUsageType: Record<KeySpec, KeyUsageType[]> = {
|
||||||
export type KeyUsage = 'SIGN_VERIFY' | 'ENCRYPT_DECRYPT' | 'GENERATE_VERIFY_MAC';
|
ECC_NIST_P256: [KeyUsageType.SIGN_VERIFY, KeyUsageType.KEY_AGREEMENT],
|
||||||
|
ECC_NIST_P384: [KeyUsageType.SIGN_VERIFY, KeyUsageType.KEY_AGREEMENT],
|
||||||
|
ECC_NIST_P521: [KeyUsageType.SIGN_VERIFY, KeyUsageType.KEY_AGREEMENT],
|
||||||
|
ECC_SECG_P256K1: [KeyUsageType.SIGN_VERIFY],
|
||||||
|
HMAC_224: [KeyUsageType.GENERATE_VERIFY_MAC],
|
||||||
|
HMAC_256: [KeyUsageType.GENERATE_VERIFY_MAC],
|
||||||
|
HMAC_384: [KeyUsageType.GENERATE_VERIFY_MAC],
|
||||||
|
HMAC_512: [KeyUsageType.GENERATE_VERIFY_MAC],
|
||||||
|
RSA_2048: [KeyUsageType.ENCRYPT_DECRYPT, KeyUsageType.SIGN_VERIFY],
|
||||||
|
RSA_3072: [KeyUsageType.ENCRYPT_DECRYPT, KeyUsageType.SIGN_VERIFY],
|
||||||
|
RSA_4096: [KeyUsageType.ENCRYPT_DECRYPT, KeyUsageType.SIGN_VERIFY],
|
||||||
|
SM2: [KeyUsageType.ENCRYPT_DECRYPT, KeyUsageType.SIGN_VERIFY, KeyUsageType.KEY_AGREEMENT],
|
||||||
|
SYMMETRIC_DEFAULT: [KeyUsageType.ENCRYPT_DECRYPT]
|
||||||
|
}
|
||||||
|
|
||||||
export class KmsKey implements PrismaKmsKey {
|
export class KmsKey implements PrismaKmsKey {
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
usage: KeyUsage;
|
enabled: boolean;
|
||||||
|
usage: KeyUsageType;
|
||||||
description: string;
|
description: string;
|
||||||
keySpec: KeySpec;
|
keySpec: KeySpec;
|
||||||
key: string;
|
keyState: KeyState;
|
||||||
|
origin: OriginType;
|
||||||
|
multiRegion: boolean;
|
||||||
|
policy: string;
|
||||||
|
key: Buffer;
|
||||||
|
nextRotation: Date | null;
|
||||||
|
rotationPeriod: number | null;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
region: string;
|
region: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
|
||||||
constructor(p: PrismaKmsKey) {
|
constructor(p: PrismaKmsKey) {
|
||||||
this.id = p.id;
|
this.id = p.id;
|
||||||
this.usage = p.usage as KeyUsage;
|
this.enabled = p.enabled;
|
||||||
|
this.usage = p.usage as KeyUsageType;
|
||||||
this.description = p.description;
|
this.description = p.description;
|
||||||
this.keySpec = p.keySpec as KeySpec;
|
this.keySpec = p.keySpec as KeySpec;
|
||||||
this.key = p.key;
|
this.keyState = p.keyState as KeyState;
|
||||||
|
this.origin = p.origin as OriginType;
|
||||||
|
this.multiRegion = p.multiRegion;
|
||||||
|
this.policy = p.policy;
|
||||||
|
this.key = Buffer.from(p.key);
|
||||||
|
this.nextRotation = p.nextRotation;
|
||||||
|
this.rotationPeriod = p.rotationPeriod;
|
||||||
this.accountId = p.accountId;
|
this.accountId = p.accountId;
|
||||||
this.region = p.region;
|
this.region = p.region;
|
||||||
this.createdAt = p.createdAt;
|
this.createdAt = p.createdAt;
|
||||||
|
this.updatedAt = p.updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
get arn() {
|
get arn() {
|
||||||
|
|
@ -30,27 +60,58 @@ export class KmsKey implements PrismaKmsKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
get metadata() {
|
get metadata() {
|
||||||
|
|
||||||
|
const dynamicContent: Record<string, any> = {};
|
||||||
|
|
||||||
|
if (keySpecToUsageType[this.keySpec].includes(KeyUsageType.ENCRYPT_DECRYPT)) {
|
||||||
|
dynamicContent.EncryptionAlgorithms = Object.values(AlgorithmSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.origin === OriginType.EXTERNAL) {
|
||||||
|
dynamicContent.ExpirationModel = ExpirationModelType.KEY_MATERIAL_DOES_NOT_EXPIRE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySpecToUsageType[this.keySpec].includes(KeyUsageType.KEY_AGREEMENT)) {
|
||||||
|
dynamicContent.KeyAgreementAlgorithms = Object.values(KeyAgreementAlgorithmSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySpecToUsageType[this.keySpec].includes(KeyUsageType.GENERATE_VERIFY_MAC)) {
|
||||||
|
dynamicContent.MacAlgorithms = Object.values(MacAlgorithmSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.multiRegion) {
|
||||||
|
dynamicContent.MultiRegionConfiguration = {
|
||||||
|
MultiRegionKeyType: MultiRegionKeyType.PRIMARY,
|
||||||
|
PrimaryKey: {
|
||||||
|
Arn: this.arn,
|
||||||
|
Region: this.region,
|
||||||
|
},
|
||||||
|
ReplicaKeys: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySpecToUsageType[this.keySpec].includes(KeyUsageType.SIGN_VERIFY)) {
|
||||||
|
dynamicContent.SigningAlgorithms = Object.values(SigningAlgorithmSpec);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
AWSAccountId: this.accountId,
|
AWSAccountId: this.accountId,
|
||||||
KeyId: this.id,
|
|
||||||
Arn: this.arn,
|
Arn: this.arn,
|
||||||
CreationDate: new Date(this.createdAt).toISOString(),
|
CreationDate: this.createdAt.getAwsTime(),
|
||||||
Enabled: true,
|
|
||||||
Description: this.description,
|
|
||||||
KeyUsage: this.usage,
|
|
||||||
KeyState: 'Enabled',
|
|
||||||
KeyManager: "CUSTOMER",
|
|
||||||
CustomerMasterKeySpec: this.keySpec,
|
CustomerMasterKeySpec: this.keySpec,
|
||||||
|
Description: this.description,
|
||||||
|
Enabled: true,
|
||||||
|
KeyId: this.id,
|
||||||
|
KeyManager: undefined,
|
||||||
KeySpec: this.keySpec,
|
KeySpec: this.keySpec,
|
||||||
DeletionDate: null,
|
KeyState: this.keyState,
|
||||||
SigningAlgorithms: [
|
KeyUsage: this.usage,
|
||||||
"RSASSA_PSS_SHA_256",
|
MultiRegion: this.multiRegion,
|
||||||
"RSASSA_PSS_SHA_384",
|
Origin: this.origin,
|
||||||
"RSASSA_PSS_SHA_512",
|
PendingDeletionWindowInDays: undefined,
|
||||||
"RSASSA_PKCS1_V1_5_SHA_256",
|
ValidTo: undefined,
|
||||||
"RSASSA_PKCS1_V1_5_SHA_384",
|
XksKeyConfiguration: undefined,
|
||||||
"RSASSA_PKCS1_V1_5_SHA_512"
|
...dynamicContent,
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,23 @@ import { KmsService } from './kms.service';
|
||||||
import { KMSHandlers } from './kms.constants';
|
import { KMSHandlers } from './kms.constants';
|
||||||
import { DescribeKeyHandler } from './describe-key.handler';
|
import { DescribeKeyHandler } from './describe-key.handler';
|
||||||
import { PrismaModule } from '../_prisma/prisma.module';
|
import { PrismaModule } from '../_prisma/prisma.module';
|
||||||
|
import { ListAliasesHandler } from './list-aliases.handler';
|
||||||
|
import { CreateKeyHandler } from './create-key.handler';
|
||||||
|
import { EnableKeyRotationHandler } from './enable-key-rotation.handler';
|
||||||
|
import { GetKeyRotationStatusHandler } from './get-key-rotation-status.handler';
|
||||||
|
import { GetKeyPolicyHandler } from './get-key-policy.handler';
|
||||||
|
import { ListResourceTagsHandler } from './list-resource-tags.handler';
|
||||||
|
import { CreateAliasHandler } from './create-alias.handler';
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
|
CreateAliasHandler,
|
||||||
|
CreateKeyHandler,
|
||||||
DescribeKeyHandler,
|
DescribeKeyHandler,
|
||||||
|
EnableKeyRotationHandler,
|
||||||
|
GetKeyPolicyHandler,
|
||||||
|
GetKeyRotationStatusHandler,
|
||||||
|
ListAliasesHandler,
|
||||||
|
ListResourceTagsHandler,
|
||||||
]
|
]
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
import { PrismaService } from '../_prisma/prisma.service';
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { ArnParts } from '../util/breakdown-arn';
|
import { breakdownArn } from '../util/breakdown-arn';
|
||||||
import { KmsKey } from './kms-key.entity';
|
import { KmsKey } from './kms-key.entity';
|
||||||
|
import { KmsAlias } from './kms-alias.entity';
|
||||||
|
import { AwsProperties } from '../abstract-action.handler';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KmsService {
|
export class KmsService {
|
||||||
|
|
@ -10,21 +14,103 @@ export class KmsService {
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findOneById(id: string): Promise<KmsKey | null> {
|
async findOneByRef(ref: string, awsProperties: AwsProperties): Promise<KmsKey> {
|
||||||
const pRecord = await this.prismaService.kmsKey.findFirst({
|
if (ref.startsWith('arn')) {
|
||||||
where: { id }
|
return await this.findOneByArn(ref);
|
||||||
});
|
}
|
||||||
return pRecord ? new KmsKey(pRecord) : null;
|
return await this.findOneById(awsProperties.accountId, awsProperties.region, ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string | null> {
|
async findOneByArn(arn: string): Promise<KmsKey> {
|
||||||
const record = await this.prismaService.kmsAlias.findFirst({
|
const parts = breakdownArn(arn);
|
||||||
where: {
|
return await this.findOneById(parts.accountId, parts.region, parts.identifier.split('/')[1]);
|
||||||
name: alias,
|
|
||||||
accountId: arn.accountId,
|
|
||||||
region: arn.region,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findOneById(accountId: string, region: string, ref: string): Promise<KmsKey> {
|
||||||
|
|
||||||
|
const [alias, record] = await Promise.all([
|
||||||
|
this.prismaService.kmsAlias.findFirst({
|
||||||
|
include: {
|
||||||
|
kmsKey: true
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
name: ref,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
this.prismaService.kmsKey.findFirst({
|
||||||
|
where: {
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
id: ref,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!alias?.kmsKey && !record) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return record ? new KmsKey(record) : new KmsKey(alias!.kmsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAndCountAliasesByKeyId(accountId: string, region: string, limit: number, kmsKeyId: string, marker = ''): Promise<KmsAlias[]> {
|
||||||
|
const take = limit + 1;
|
||||||
|
const records = await this.prismaService.kmsAlias.findMany({
|
||||||
|
where: {
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
kmsKeyId,
|
||||||
|
name: {
|
||||||
|
gte: marker,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
take,
|
||||||
|
orderBy: {
|
||||||
|
name: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return records.map(r => new KmsAlias(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAndCountAliases(accountId: string, region: string, limit: number, marker = ''): Promise<KmsAlias[]> {
|
||||||
|
const take = limit + 1;
|
||||||
|
const records = await this.prismaService.kmsAlias.findMany({
|
||||||
|
where: {
|
||||||
|
accountId,
|
||||||
|
region,
|
||||||
|
name: {
|
||||||
|
gte: marker,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
take,
|
||||||
|
orderBy: {
|
||||||
|
name: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return records.map(r => new KmsAlias(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
async createKmsKey(data: Prisma.KmsKeyCreateInput): Promise<KmsKey> {
|
||||||
|
const record = await this.prismaService.kmsKey.create({
|
||||||
|
data
|
||||||
|
});
|
||||||
|
return new KmsKey(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateKmsKey(id: string, data: Prisma.KmsKeyUpdateInput): Promise<void> {
|
||||||
|
await this.prismaService.kmsKey.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createAlias(data: Prisma.KmsAliasCreateInput) {
|
||||||
|
await this.prismaService.kmsAlias.create({
|
||||||
|
data
|
||||||
});
|
});
|
||||||
return record?.kmsKeyId ?? null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
KeyId?: string;
|
||||||
|
Limit: number;
|
||||||
|
Marker?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ListAliasesHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsListAliases;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
KeyId: Joi.string(),
|
||||||
|
Limit: Joi.number().min(1).max(100).default(50),
|
||||||
|
Marker: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyId, Limit, Marker }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const records = await (KeyId
|
||||||
|
? this.kmsService.findAndCountAliasesByKeyId(awsProperties.accountId, awsProperties.region, Limit, KeyId, Marker)
|
||||||
|
: this.kmsService.findAndCountAliases(awsProperties.accountId, awsProperties.region, Limit, Marker)
|
||||||
|
)
|
||||||
|
|
||||||
|
const nextMarker = records.length > Limit ? records.pop() : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
Aliases: records.map(r => r.toAws()),
|
||||||
|
NextMarker: nextMarker?.name,
|
||||||
|
Truncated: !!nextMarker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
KeyId: string;
|
||||||
|
Limit: number;
|
||||||
|
Marker: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ListResourceTagsHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly kmsService: KmsService,
|
||||||
|
private readonly tagsService: TagsService,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
format = Format.Json;
|
||||||
|
action = Action.KmsListResourceTags;
|
||||||
|
validator = Joi.object<QueryParams, true>({
|
||||||
|
KeyId: Joi.string().required(),
|
||||||
|
Limit: Joi.number().min(1).max(100).default(50),
|
||||||
|
Marker: Joi.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
||||||
|
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
|
||||||
|
|
||||||
|
if (!keyRecord) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = await this.tagsService.getByArn(keyRecord.arn);
|
||||||
|
|
||||||
|
return {
|
||||||
|
Tags: tags.map(({ name, value }) => ({ TagKey: name, TagValue: value })),
|
||||||
|
Truncated: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main.ts
10
src/main.ts
|
|
@ -8,6 +8,16 @@ import { AwsExceptionFilter } from './_context/exception.filter';
|
||||||
|
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Date {
|
||||||
|
getAwsTime(): number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Date.prototype.getAwsTime = function (this: Date) {
|
||||||
|
return Math.floor(this.getTime() / 1000);
|
||||||
|
};
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue