Completed kms

This commit is contained in:
2024-12-20 21:18:23 -05:00
parent c34ea76e4e
commit 1dc45267ac
24 changed files with 2062 additions and 71 deletions

View File

@@ -6,7 +6,7 @@ import { Format } from "../abstract-action.handler";
export interface RequestContext {
action?: Action;
format?: Format;
requestId: string;
readonly requestId: string;
}
export interface IRequest extends Request {

View File

@@ -154,3 +154,12 @@ export class InvalidArnException extends AwsException {
)
}
}
export class UnsupportedOperationException extends AwsException {
constructor(message: string) {
super(
message,
UnsupportedOperationException.name,
HttpStatus.BAD_REQUEST,
)
}
}

View File

@@ -20,7 +20,7 @@ export class TagsService {
}
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 => ({
name: r.key,
value: r.value,

View File

@@ -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,
},
},
});
}
}

View File

@@ -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);
}
}
}

View File

@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
import { Action } from '../action.enum';
import * as Joi from 'joi';
import { breakdownArn } from '../util/breakdown-arn';
import { KmsService } from './kms.service';
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) {
const searchable = KeyId.startsWith('arn') ? breakdownArn(KeyId) : {
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);
const keyRecord = await this.kmsService.findOneByRef(KeyId, awsProperties);
if (!keyRecord) {
throw new NotFoundException();

View File

@@ -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,
});
}
}

View File

@@ -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,
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -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}`;
// }
}

View File

@@ -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';
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 type KeyUsage = 'SIGN_VERIFY' | 'ENCRYPT_DECRYPT' | 'GENERATE_VERIFY_MAC';
export const keySpecToUsageType: Record<KeySpec, KeyUsageType[]> = {
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 {
id: string;
usage: KeyUsage;
enabled: boolean;
usage: KeyUsageType;
description: string;
keySpec: KeySpec;
key: string;
keyState: KeyState;
origin: OriginType;
multiRegion: boolean;
policy: string;
key: Buffer;
nextRotation: Date | null;
rotationPeriod: number | null;
accountId: string;
region: string;
createdAt: Date;
updatedAt: Date;
constructor(p: PrismaKmsKey) {
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.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.region = p.region;
this.createdAt = p.createdAt;
this.updatedAt = p.updatedAt;
}
get arn() {
@@ -30,27 +60,58 @@ export class KmsKey implements PrismaKmsKey {
}
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 {
AWSAccountId: this.accountId,
KeyId: this.id,
Arn: this.arn,
CreationDate: new Date(this.createdAt).toISOString(),
Enabled: true,
Description: this.description,
KeyUsage: this.usage,
KeyState: 'Enabled',
KeyManager: "CUSTOMER",
CreationDate: this.createdAt.getAwsTime(),
CustomerMasterKeySpec: this.keySpec,
Description: this.description,
Enabled: true,
KeyId: this.id,
KeyManager: undefined,
KeySpec: this.keySpec,
DeletionDate: null,
SigningAlgorithms: [
"RSASSA_PSS_SHA_256",
"RSASSA_PSS_SHA_384",
"RSASSA_PSS_SHA_512",
"RSASSA_PKCS1_V1_5_SHA_256",
"RSASSA_PKCS1_V1_5_SHA_384",
"RSASSA_PKCS1_V1_5_SHA_512"
]
KeyState: this.keyState,
KeyUsage: this.usage,
MultiRegion: this.multiRegion,
Origin: this.origin,
PendingDeletionWindowInDays: undefined,
ValidTo: undefined,
XksKeyConfiguration: undefined,
...dynamicContent,
}
}
}

View File

@@ -9,9 +9,23 @@ import { KmsService } from './kms.service';
import { KMSHandlers } from './kms.constants';
import { DescribeKeyHandler } from './describe-key.handler';
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 = [
CreateAliasHandler,
CreateKeyHandler,
DescribeKeyHandler,
EnableKeyRotationHandler,
GetKeyPolicyHandler,
GetKeyRotationStatusHandler,
ListAliasesHandler,
ListResourceTagsHandler,
]
const actions = [

View File

@@ -1,8 +1,12 @@
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
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 { KmsAlias } from './kms-alias.entity';
import { AwsProperties } from '../abstract-action.handler';
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
@Injectable()
export class KmsService {
@@ -10,21 +14,103 @@ export class KmsService {
private readonly prismaService: PrismaService,
) {}
async findOneById(id: string): Promise<KmsKey | null> {
const pRecord = await this.prismaService.kmsKey.findFirst({
where: { id }
});
return pRecord ? new KmsKey(pRecord) : null;
async findOneByRef(ref: string, awsProperties: AwsProperties): Promise<KmsKey> {
if (ref.startsWith('arn')) {
return await this.findOneByArn(ref);
}
return await this.findOneById(awsProperties.accountId, awsProperties.region, ref);
}
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string | null> {
const record = await this.prismaService.kmsAlias.findFirst({
async findOneByArn(arn: string): Promise<KmsKey> {
const parts = breakdownArn(arn);
return await this.findOneById(parts.accountId, parts.region, parts.identifier.split('/')[1]);
}
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: {
name: alias,
accountId: arn.accountId,
region: arn.region,
}
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;
}
}

View File

@@ -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,
}
}
}

View File

@@ -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,
}
}
}

View File

@@ -8,6 +8,16 @@ import { AwsExceptionFilter } from './_context/exception.filter';
const bodyParser = require('body-parser');
declare global {
interface Date {
getAwsTime(): number;
}
}
Date.prototype.getAwsTime = function (this: Date) {
return Math.floor(this.getTime() / 1000);
};
(async () => {
const app = await NestFactory.create(AppModule);