diff --git a/src/app.controller.ts b/src/app.controller.ts index a072a96..010bd7d 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Body, Controller, Get, Inject, Post, Headers } from '@nestjs/common'; +import { BadRequestException, Body, Controller, Get, Inject, Post, Headers, Header } from '@nestjs/common'; import { ActionHandlers } from './app.constants'; import * as Joi from 'joi'; import { Action } from './action.enum'; @@ -6,6 +6,7 @@ import { AbstractActionHandler, Format } from './abstract-action.handler'; import * as js2xmlparser from 'js2xmlparser'; import { ConfigService } from '@nestjs/config'; import { CommonConfig } from './config/common-config.interface'; +import * as uuid from 'uuid'; @Controller() export class AppController { @@ -17,6 +18,7 @@ export class AppController { ) {} @Post() + @Header('x-amzn-RequestId', uuid.v4()) async post( @Body() body: Record, @Headers() headers: Record, diff --git a/src/aws-shared-entities/tags.service.ts b/src/aws-shared-entities/tags.service.ts index 5efa4e7..6efef43 100644 --- a/src/aws-shared-entities/tags.service.ts +++ b/src/aws-shared-entities/tags.service.ts @@ -47,7 +47,11 @@ export class TagsService { return pairs; } - static getXmlSafeAttributesMap(tags: Tag[]) { + static getXmlSafeTagsMap(tags: Tag[]) { return { Tags: { member: tags.map(({ name, value }) => ({ Key: name, Value: value })) } }; } + + static getJsonSafeTagsMap(tags: Tag[]) { + return tags.map(({ name, value }) => ({ Key: name, Value: value })); + } } diff --git a/src/main.ts b/src/main.ts index acdf0cf..41abb7a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,6 +2,7 @@ import { ClassSerializerInterceptor } from '@nestjs/common'; import { NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; import * as morgan from 'morgan'; +const bodyParser = require('body-parser'); (async () => { @@ -9,6 +10,7 @@ import * as morgan from 'morgan'; const app = await NestFactory.create(AppModule); app.use(morgan('dev')); app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector))); + app.use(bodyParser.json({ type: 'application/x-amz-json-1.1'})); await app.listen(port, () => console.log(`Listening on port ${port}`)); })(); diff --git a/src/secrets-manager/create-secret.handler.ts b/src/secrets-manager/create-secret.handler.ts new file mode 100644 index 0000000..b8c3305 --- /dev/null +++ b/src/secrets-manager/create-secret.handler.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { Secret } from './secret.entity'; +import * as uuid from 'uuid'; + +type QueryParams = { + Name: string; + Description: string; + SecretString: string; + ClientRequestToken: string; +} + +@Injectable() +export class CreateSecretHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(Secret) + private readonly secretRepo: Repository, + ) { + super(); + } + + format = Format.Json; + action = Action.SecretsManagerCreateSecret; + validator = Joi.object({ + Name: Joi.string().required(), + Description: Joi.string().allow('', null), + SecretString: Joi.string().allow('', null), + ClientRequestToken: Joi.string().required(), + }); + + protected async handle(params: QueryParams, awsProperties: AwsProperties) { + + const { Name: name, Description: description, SecretString: secretString, ClientRequestToken } = params; + + const secret = await this.secretRepo.create({ + versionId: ClientRequestToken, + description, + name, + secretString, + accountId: awsProperties.accountId, + region: awsProperties.region, + }).save(); + + return { ARN: secret.arn, VersionId: secret.versionId, Name: secret.name }; + } +} diff --git a/src/secrets-manager/describe-secret.handler.ts b/src/secrets-manager/describe-secret.handler.ts new file mode 100644 index 0000000..d58e1d7 --- /dev/null +++ b/src/secrets-manager/describe-secret.handler.ts @@ -0,0 +1,60 @@ +import { BadRequestException, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler'; +import { Action } from '../action.enum'; +import * as Joi from 'joi'; +import { Secret } from './secret.entity'; +import { TagsService } from '../aws-shared-entities/tags.service'; + +type QueryParams = { + SecretId: string; +} + +@Injectable() +export class DescribeSecretHandler extends AbstractActionHandler { + + constructor( + @InjectRepository(Secret) + private readonly secretRepo: Repository, + private readonly tagsService: TagsService, + ) { + super(); + } + + format = Format.Json; + action = Action.SecretsManagerDescribeSecret; + validator = Joi.object({ SecretId: Joi.string().required() }); + + protected async handle({ SecretId }: QueryParams, awsProperties: AwsProperties) { + + const parts = SecretId.split(':'); + const name = parts.length > 1 ? parts[-1] : SecretId; + + const secret = await this.secretRepo.findOne({ where: { name } }); + + if (!secret) { + throw new BadRequestException('ResourceNotFoundException', "Secrets Manager can't find the resource that you asked for."); + } + + const tags = await this.tagsService.getByArn(secret.arn); + const listOfTagPairs = TagsService.getJsonSafeTagsMap(tags); + + return { + "ARN": secret.arn, + "CreatedDate": new Date(secret.createdAt).getMilliseconds(), + "DeletedDate": 0, + "Description": secret.description, + "KmsKeyId": "", + "LastAccessedDate": new Date().getMilliseconds(), + "LastChangedDate": new Date(secret.createdAt).getMilliseconds(), + "LastRotatedDate": 0, + "Name": secret.name, + "OwningService": secret.accountId, + "PrimaryRegion": secret.region, + "ReplicationStatus": [], + "RotationEnabled": false, + "Tags": listOfTagPairs, + } + } +} diff --git a/src/secrets-manager/get-resource-policy.handler.ts b/src/secrets-manager/get-resource-policy.handler.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/secrets-manager/secret.entity.ts b/src/secrets-manager/secret.entity.ts index 422f97c..c84e118 100644 --- a/src/secrets-manager/secret.entity.ts +++ b/src/secrets-manager/secret.entity.ts @@ -1,9 +1,31 @@ -import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { BaseEntity, Column, CreateDateColumn, Entity, Index, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm'; @Entity('secret') export class Secret extends BaseEntity { - @PrimaryGeneratedColumn({ name: 'id' }) - id: string; + @PrimaryColumn({ name: 'versionId' }) + versionId: string; + @Column({ name: 'name', nullable: false }) + @Index() + name: string; + + @Column({ name: 'description', nullable: true }) + description: string; + + @Column({ name: 'secret_string', nullable: true }) + secretString: string; + + @Column({ name: 'account_id', nullable: false }) + accountId: string; + + @Column({ name: 'region', nullable: false }) + region: string; + + @CreateDateColumn() + createdAt: string; + + get arn(): string { + return `arn:aws:secretsmanager:${this.region}:${this.accountId}:${this.name}`; + } } diff --git a/src/secrets-manager/secrets-manager.module.ts b/src/secrets-manager/secrets-manager.module.ts index c8159db..5d85679 100644 --- a/src/secrets-manager/secrets-manager.module.ts +++ b/src/secrets-manager/secrets-manager.module.ts @@ -5,10 +5,14 @@ import { Action } from '../action.enum'; import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module'; import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider'; import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.provider'; +import { CreateSecretHandler } from './create-secret.handler'; +import { DescribeSecretHandler } from './describe-secret.handler'; +import { Secret } from './secret.entity'; import { SecretsManagerHandlers } from './secrets-manager.constants'; const handlers = [ - + CreateSecretHandler, + DescribeSecretHandler, ] const actions = [ @@ -38,7 +42,7 @@ const actions = [ @Module({ imports: [ - TypeOrmModule.forFeature([]), + TypeOrmModule.forFeature([Secret]), AwsSharedEntitiesModule, ], providers: [ diff --git a/src/sns/list-tags-for-resource.handler.ts b/src/sns/list-tags-for-resource.handler.ts index 4be287d..b52fb97 100644 --- a/src/sns/list-tags-for-resource.handler.ts +++ b/src/sns/list-tags-for-resource.handler.ts @@ -26,6 +26,6 @@ export class ListTagsForResourceHandler extends AbstractActionHandler { protected async handle({ ResourceArn }: QueryParams, awsProperties: AwsProperties) { const tags = await this.tagsService.getByArn(ResourceArn); - return TagsService.getXmlSafeAttributesMap(tags); + return TagsService.getXmlSafeTagsMap(tags); } }