More support for secrets manager
This commit is contained in:
parent
05208e1277
commit
07d3841cd7
|
|
@ -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 { ActionHandlers } from './app.constants';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { Action } from './action.enum';
|
import { Action } from './action.enum';
|
||||||
|
|
@ -6,6 +6,7 @@ import { AbstractActionHandler, Format } from './abstract-action.handler';
|
||||||
import * as js2xmlparser from 'js2xmlparser';
|
import * as js2xmlparser from 'js2xmlparser';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { CommonConfig } from './config/common-config.interface';
|
import { CommonConfig } from './config/common-config.interface';
|
||||||
|
import * as uuid from 'uuid';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
|
|
@ -17,6 +18,7 @@ export class AppController {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@Header('x-amzn-RequestId', uuid.v4())
|
||||||
async post(
|
async post(
|
||||||
@Body() body: Record<string, any>,
|
@Body() body: Record<string, any>,
|
||||||
@Headers() headers: Record<string, any>,
|
@Headers() headers: Record<string, any>,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,11 @@ export class TagsService {
|
||||||
return pairs;
|
return pairs;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getXmlSafeAttributesMap(tags: Tag[]) {
|
static getXmlSafeTagsMap(tags: Tag[]) {
|
||||||
return { Tags: { member: tags.map(({ name, value }) => ({ Key: name, Value: value })) } };
|
return { Tags: { member: tags.map(({ name, value }) => ({ Key: name, Value: value })) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getJsonSafeTagsMap(tags: Tag[]) {
|
||||||
|
return tags.map(({ name, value }) => ({ Key: name, Value: value }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ClassSerializerInterceptor } from '@nestjs/common';
|
||||||
import { NestFactory, Reflector } from '@nestjs/core';
|
import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import * as morgan from 'morgan';
|
import * as morgan from 'morgan';
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
|
|
@ -9,6 +10,7 @@ import * as morgan from 'morgan';
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.use(morgan('dev'));
|
app.use(morgan('dev'));
|
||||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
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}`));
|
await app.listen(port, () => console.log(`Listening on port ${port}`));
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,31 @@
|
||||||
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
import { BaseEntity, Column, CreateDateColumn, Entity, Index, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||||
|
|
||||||
@Entity('secret')
|
@Entity('secret')
|
||||||
export class Secret extends BaseEntity {
|
export class Secret extends BaseEntity {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn({ name: 'id' })
|
@PrimaryColumn({ name: 'versionId' })
|
||||||
id: string;
|
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}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,14 @@ import { Action } from '../action.enum';
|
||||||
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
||||||
import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider';
|
import { DefaultActionHandlerProvider } from '../default-action-handler/default-action-handler.provider';
|
||||||
import { ExistingActionHandlersProvider } from '../default-action-handler/existing-action-handlers.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';
|
import { SecretsManagerHandlers } from './secrets-manager.constants';
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
|
CreateSecretHandler,
|
||||||
|
DescribeSecretHandler,
|
||||||
]
|
]
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
|
|
@ -38,7 +42,7 @@ const actions = [
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([]),
|
TypeOrmModule.forFeature([Secret]),
|
||||||
AwsSharedEntitiesModule,
|
AwsSharedEntitiesModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,6 @@ export class ListTagsForResourceHandler extends AbstractActionHandler {
|
||||||
|
|
||||||
protected async handle({ ResourceArn }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ ResourceArn }: QueryParams, awsProperties: AwsProperties) {
|
||||||
const tags = await this.tagsService.getByArn(ResourceArn);
|
const tags = await this.tagsService.getByArn(ResourceArn);
|
||||||
return TagsService.getXmlSafeAttributesMap(tags);
|
return TagsService.getXmlSafeTagsMap(tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue