General fixes and new kms support
This commit is contained in:
parent
e1aaeaa90e
commit
a5c90f7a26
|
|
@ -2,10 +2,10 @@ kind: pipeline
|
|||
type: docker
|
||||
name: default
|
||||
steps:
|
||||
- name: build backend
|
||||
- name: build
|
||||
image: docker.thiccdata.io/nvm-node-debian:latest
|
||||
commands:
|
||||
- docker build -t docker.thiccdata.io/mtg-event-manager-backend:latest -f ./backend/Dockerfile ./
|
||||
- docker build -t docker.thiccdata.io/mtg-event-manager-backend:latest ./
|
||||
- docker push docker.thiccdata.io/mtg-event-manager-backend:latest
|
||||
volumes:
|
||||
- name: docker
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class AppController {
|
|||
}).validate(queryParams, { allowUnknown: true });
|
||||
|
||||
if (actionError) {
|
||||
throw new BadRequestException(actionError);
|
||||
throw new BadRequestException(actionError.message, { cause: actionError });
|
||||
}
|
||||
|
||||
const action = queryParams[actionKey];
|
||||
|
|
@ -47,7 +47,7 @@ export class AppController {
|
|||
const { error: validatorError, value: validQueryParams } = handler.validator.validate(queryParams, { allowUnknown: true, abortEarly: false });
|
||||
|
||||
if (validatorError) {
|
||||
throw new BadRequestException(validatorError);
|
||||
throw new BadRequestException(validatorError.message, { cause: validatorError });
|
||||
}
|
||||
|
||||
const awsProperties = {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export class CreatePolicyHandler extends AbstractActionHandler<QueryParams> {
|
|||
return {
|
||||
Policy: {
|
||||
PolicyName: policy.name,
|
||||
DefaultVersionId: 'v1',
|
||||
DefaultVersionId: policy.version,
|
||||
PolicyId: policy.id,
|
||||
Path: '/',
|
||||
Arn: policy.arn,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type QueryParams = {
|
|||
RoleName: string;
|
||||
Path: string;
|
||||
AssumeRolePolicyDocument: string;
|
||||
MaxSessionDuration: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -32,9 +33,10 @@ export class CreateRoleHandler extends AbstractActionHandler<QueryParams> {
|
|||
RoleName: Joi.string().required(),
|
||||
Path: Joi.string().required(),
|
||||
AssumeRolePolicyDocument: Joi.string().required(),
|
||||
MaxSessionDuration: Joi.number().default(3600),
|
||||
});
|
||||
|
||||
protected async handle({ RoleName, Path, AssumeRolePolicyDocument }: QueryParams, awsProperties: AwsProperties) {
|
||||
protected async handle({ RoleName, Path, AssumeRolePolicyDocument, MaxSessionDuration }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const policy = await this.policyRepo.create({
|
||||
id: uuid.v4(),
|
||||
|
|
@ -51,6 +53,7 @@ export class CreateRoleHandler extends AbstractActionHandler<QueryParams> {
|
|||
path: Path,
|
||||
accountId: awsProperties.accountId,
|
||||
assumeRolePolicyDocumentId: policy.id,
|
||||
maxSessionDuration: MaxSessionDuration,
|
||||
}).save();
|
||||
|
||||
const role = await this.roleRepo.findOne({ where: { id }});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { Injectable, NotFoundException, Version } from '@nestjs/common';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { IamPolicy } from './iam-policy.entity';
|
||||
import { breakdownArn } from '../util/breakdown-arn';
|
||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
||||
|
||||
type QueryParams = {
|
||||
PolicyArn: string;
|
||||
VersionId: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class GetPolicyVersionHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(IamPolicy)
|
||||
private readonly policyRepo: Repository<IamPolicy>,
|
||||
@InjectRepository(IamRolePolicyAttachment)
|
||||
private readonly attachmentRepo: Repository<IamRolePolicyAttachment>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Xml;
|
||||
action = Action.IamGetPolicyVersion;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
PolicyArn: Joi.string().required(),
|
||||
VersionId: Joi.string().required(),
|
||||
});
|
||||
|
||||
protected async handle({ PolicyArn, VersionId }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const { identifier, accountId } = breakdownArn(PolicyArn);
|
||||
const [_policy, name] = identifier.split('/');
|
||||
const policy = await this.policyRepo.findOne({ where: { name, accountId, version: +VersionId }});
|
||||
|
||||
if (!policy) {
|
||||
throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.');
|
||||
}
|
||||
|
||||
return {
|
||||
PolicyVersion: {
|
||||
Document: policy.document,
|
||||
IsDefaultVersion: policy.isDefault,
|
||||
VersionId: `${policy.version}`,
|
||||
CreateDate: new Date(policy.createdAt).toISOString(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ export class GetPolicyHandler extends AbstractActionHandler<QueryParams> {
|
|||
return {
|
||||
Policy: {
|
||||
PolicyName: policy.name,
|
||||
DefaultVersionId: `v${policy.version}`,
|
||||
DefaultVersionId: policy.version,
|
||||
PolicyId: policy.id,
|
||||
Path: '/',
|
||||
Arn: policy.arn,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ export class IamRole extends BaseEntity {
|
|||
@Column({ name: 'account_id', nullable: false })
|
||||
accountId: string;
|
||||
|
||||
@Column({ name: 'max_session_duration', nullable: false, default: 0 })
|
||||
maxSessionDuration: number;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: string;
|
||||
|
||||
|
|
@ -43,6 +46,7 @@ export class IamRole extends BaseEntity {
|
|||
AssumeRolePolicyDocument: this.assumeRolePolicyDocument.document,
|
||||
CreateDate: new Date(this.createdAt).toISOString(),
|
||||
RoleId: this.id,
|
||||
MaxSessionDuration: this.maxSessionDuration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { AttachRolePolicyHandler } from './attach-role-policy.handler';
|
|||
import { CreatePolicyVersionHandler } from './create-policy-version.handler';
|
||||
import { CreatePolicyHandler } from './create-policy.handler';
|
||||
import { CreateRoleHandler } from './create-role.handler';
|
||||
import { GetPolicyVersionHandler } from './get-policy-version.handler';
|
||||
import { GetPolicyHandler } from './get-policy.handler';
|
||||
import { GetRoleHandler } from './get-role.handler';
|
||||
import { IamPolicy } from './iam-policy.entity';
|
||||
|
|
@ -25,6 +26,7 @@ const handlers = [
|
|||
CreateRoleHandler,
|
||||
GetPolicyHandler,
|
||||
GetRoleHandler,
|
||||
GetPolicyVersionHandler,
|
||||
ListAttachedRolePoliciesHandler,
|
||||
ListRolePoliciesHandler,
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { KmsKeyAlias } from './kms-key-alias.entity';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { KmsKey } from './kms-key.entity';
|
||||
import { ArnParts, breakdownArn } from '../util/breakdown-arn';
|
||||
import { breakdownArn } from '../util/breakdown-arn';
|
||||
import { KmsService } from './kms.service';
|
||||
|
||||
type QueryParams = {
|
||||
KeyId: string;
|
||||
|
|
@ -16,8 +16,7 @@ type QueryParams = {
|
|||
export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(KmsKeyAlias)
|
||||
private readonly aliasRepo: Repository<KmsKeyAlias>,
|
||||
private readonly kmsService: KmsService,
|
||||
@InjectRepository(KmsKey)
|
||||
private readonly keyRepo: Repository<KmsKey>,
|
||||
) {
|
||||
|
|
@ -41,7 +40,7 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
|||
const [ type, pk ] = searchable.identifier.split('/');
|
||||
const keyId: Promise<string> = type === 'key' ?
|
||||
Promise.resolve(pk) :
|
||||
this.findKeyIdFromAlias(pk, searchable);
|
||||
this.kmsService.findKeyIdFromAlias(pk, searchable);
|
||||
|
||||
|
||||
const keyRecord = await this.keyRepo.findOne({ where: {
|
||||
|
|
@ -54,13 +53,4 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
|||
KeyMetadata: keyRecord.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
private async findKeyIdFromAlias(alias: string ,arn: ArnParts): Promise<string> {
|
||||
const record = await this.aliasRepo.findOne({ where: {
|
||||
name: alias,
|
||||
accountId: arn.accountId,
|
||||
region: arn.region,
|
||||
}});
|
||||
return record.targetKeyId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { KeySpec, KeyUsage, KmsKey } from './kms-key.entity';
|
||||
import { breakdownArn } from '../util/breakdown-arn';
|
||||
import { KmsService } from './kms.service';
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
type QueryParams = {
|
||||
GrantTokens: string[];
|
||||
KeyId: string;
|
||||
}
|
||||
|
||||
interface StandardOutput {
|
||||
KeyId: string;
|
||||
KeySpec: KeySpec;
|
||||
KeyUsage: KeyUsage;
|
||||
PublicKey: string;
|
||||
CustomerMasterKeySpec: KeySpec;
|
||||
}
|
||||
|
||||
interface EncryptDecrypt extends StandardOutput {
|
||||
KeyUsage: 'ENCRYPT_DECRYPT';
|
||||
EncryptionAlgorithms: ('SYMMETRIC_DEFAULT' | 'RSAES_OAEP_SHA_1' | 'RSAES_OAEP_SHA_256' | 'SM2PKE')[];
|
||||
}
|
||||
|
||||
interface SignVerify extends StandardOutput {
|
||||
KeyUsage: 'SIGN_VERIFY';
|
||||
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' | 'ECDSA_SHA_256' | 'ECDSA_SHA_384' | 'ECDSA_SHA_512' | 'SM2DSA')[];
|
||||
}
|
||||
|
||||
type Output = EncryptDecrypt | SignVerify | StandardOutput;
|
||||
|
||||
@Injectable()
|
||||
export class GetPublicKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
|
||||
@InjectRepository(KmsKey)
|
||||
private readonly keyRepo: Repository<KmsKey>,
|
||||
private readonly kmsService: KmsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
format = Format.Json;
|
||||
action = Action.KmsGetPublicKey;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
KeyId: Joi.string().required(),
|
||||
GrantTokens: Joi.array().items(Joi.string()),
|
||||
});
|
||||
|
||||
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties): Promise<Output> {
|
||||
|
||||
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: Promise<string> = type === 'key' ?
|
||||
Promise.resolve(pk) :
|
||||
this.kmsService.findKeyIdFromAlias(pk, searchable);
|
||||
|
||||
|
||||
const keyRecord = await this.keyRepo.findOne({ where: {
|
||||
id: await keyId,
|
||||
region: searchable.region,
|
||||
accountId: searchable.accountId,
|
||||
}});
|
||||
|
||||
const pubKeyObject = crypto.createPublicKey({
|
||||
key: keyRecord.key,//.split(String.raw`\n`).join('\n'),
|
||||
format: 'pem',
|
||||
});
|
||||
|
||||
if (keyRecord.usage === 'ENCRYPT_DECRYPT') {
|
||||
return {
|
||||
CustomerMasterKeySpec: keyRecord.keySpec,
|
||||
EncryptionAlgorithms: [ "SYMMETRIC_DEFAULT" ],
|
||||
KeyId: keyRecord.arn,
|
||||
KeySpec: keyRecord.keySpec,
|
||||
KeyUsage: keyRecord.usage,
|
||||
PublicKey: Buffer.from(pubKeyObject.export({
|
||||
format: 'der',
|
||||
type: 'spki',
|
||||
})).toString('base64'),
|
||||
}
|
||||
}
|
||||
|
||||
if (keyRecord.usage === 'SIGN_VERIFY') {
|
||||
const PublicKey = Buffer.from(pubKeyObject.export({
|
||||
format: 'der',
|
||||
type: 'spki',
|
||||
})).toString('base64')
|
||||
|
||||
console.log({PublicKey})
|
||||
return {
|
||||
CustomerMasterKeySpec: keyRecord.keySpec,
|
||||
KeyId: keyRecord.arn,
|
||||
KeySpec: keyRecord.keySpec,
|
||||
KeyUsage: keyRecord.usage,
|
||||
PublicKey,
|
||||
SigningAlgorithms: [ 'RSASSA_PKCS1_V1_5_SHA_256' ]
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
CustomerMasterKeySpec: keyRecord.keySpec,
|
||||
KeyId: keyRecord.arn,
|
||||
KeySpec: keyRecord.keySpec,
|
||||
KeyUsage: keyRecord.usage,
|
||||
PublicKey: Buffer.from(pubKeyObject.export({
|
||||
format: 'pem',
|
||||
type: 'spki',
|
||||
})).toString('utf-8'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
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';
|
||||
|
||||
@Entity({ name: 'kms_key'})
|
||||
export class KmsKey extends BaseEntity {
|
||||
|
||||
|
|
@ -7,13 +10,13 @@ export class KmsKey extends BaseEntity {
|
|||
id: string;
|
||||
|
||||
@Column({ name: 'usage' })
|
||||
usage: string;
|
||||
usage: KeyUsage;
|
||||
|
||||
@Column({ name: 'description' })
|
||||
description: string;
|
||||
|
||||
@Column({ name: 'key_spec' })
|
||||
keySpec: string;
|
||||
keySpec: KeySpec;
|
||||
|
||||
@Column({ name: 'key' })
|
||||
key: string;
|
||||
|
|
|
|||
|
|
@ -10,10 +10,13 @@ import { DescribeKeyHandler } from './describe-key.handler';
|
|||
import { KmsKeyAlias } from './kms-key-alias.entity';
|
||||
import { KmsKey } from './kms-key.entity';
|
||||
import { KMSHandlers } from './kms.constants';
|
||||
import { KmsService } from './kms.service';
|
||||
import { GetPublicKeyHandler } from './get-public-key.handler';
|
||||
|
||||
const handlers = [
|
||||
CreateAliasHandler,
|
||||
DescribeKeyHandler,
|
||||
GetPublicKeyHandler,
|
||||
]
|
||||
|
||||
const actions = [
|
||||
|
|
@ -76,6 +79,7 @@ const actions = [
|
|||
],
|
||||
providers: [
|
||||
...handlers,
|
||||
KmsService,
|
||||
ExistingActionHandlersProvider(handlers),
|
||||
DefaultActionHandlerProvider(KMSHandlers, Format.Json, actions),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ArnParts } from '../util/breakdown-arn';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { KmsKeyAlias } from './kms-key-alias.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class KmsService {
|
||||
constructor(
|
||||
@InjectRepository(KmsKeyAlias)
|
||||
private readonly aliasRepo: Repository<KmsKeyAlias>,
|
||||
) {}
|
||||
|
||||
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string> {
|
||||
const record = await this.aliasRepo.findOne({ where: {
|
||||
name: alias,
|
||||
accountId: arn.accountId,
|
||||
region: arn.region,
|
||||
}});
|
||||
return record.targetKeyId;
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ export class DescribeSecretHandler extends AbstractActionHandler {
|
|||
|
||||
protected async handle({ SecretId }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
console.log({ SecretId })
|
||||
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const secret = await this.secretRepo.findOne({ where: { name }, order: { createdAt: 'DESC' } });
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { SqsQueue } from './sqs-queue.entity';
|
||||
import { SqsQueueEntryService } from './sqs-queue-entry.service';
|
||||
|
||||
type QueryParams = {
|
||||
QueueUrl: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
||||
constructor(
|
||||
private readonly sqsQueueEntryService: SqsQueueEntryService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
audit = false;
|
||||
format = Format.Xml;
|
||||
action = Action.SqsDeleteMessageBatch;
|
||||
validator = Joi.object<QueryParams>({
|
||||
QueueUrl: Joi.string().required(),
|
||||
});
|
||||
|
||||
protected async handle( params : QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const { QueueUrl } = params;
|
||||
const [accountId, name] = SqsQueue.tryGetAccountIdAndNameFromPathOrArn(QueueUrl);
|
||||
|
||||
for (const header of Object.keys(params)) {
|
||||
if (header.includes('DeleteMessageBatchRequestEntry') && header.includes('ReceiptHandle')) {
|
||||
const ReceiptHandle = params[header];
|
||||
await this.sqsQueueEntryService.deleteMessage(accountId, name, ReceiptHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,11 @@ import { SetQueueAttributesHandler } from './set-queue-attributes.handler';
|
|||
import { SqsQueueEntryService } from './sqs-queue-entry.service';
|
||||
import { SqsQueue } from './sqs-queue.entity';
|
||||
import { SqsHandlers } from './sqs.constants';
|
||||
import { DeleteMessageBatchHandler } from './delete-message-batch.handler';
|
||||
|
||||
const handlers = [
|
||||
CreateQueueHandler,
|
||||
DeleteMessageBatchHandler,
|
||||
DeleteMessageHandler,
|
||||
DeleteQueueHandler,
|
||||
GetQueueAttributesHandler,
|
||||
|
|
|
|||
Loading…
Reference in New Issue