More support for secrets manager

This commit is contained in:
Matthew Bessette 2023-03-21 01:00:38 -04:00
parent 05208e1277
commit 07d3841cd7
9 changed files with 153 additions and 8 deletions

View File

@ -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<string, any>,
@Headers() headers: Record<string, any>,

View File

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

View File

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

View File

@ -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<QueryParams> {
constructor(
@InjectRepository(Secret)
private readonly secretRepo: Repository<Secret>,
) {
super();
}
format = Format.Json;
action = Action.SecretsManagerCreateSecret;
validator = Joi.object<QueryParams, true>({
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 };
}
}

View File

@ -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<Secret>,
private readonly tagsService: TagsService,
) {
super();
}
format = Format.Json;
action = Action.SecretsManagerDescribeSecret;
validator = Joi.object<QueryParams, true>({ 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,
}
}
}

View File

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

View File

@ -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: [

View File

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