Migrates secrets to prisma
This commit is contained in:
parent
a7fdedd310
commit
22da8d73d3
|
|
@ -24,6 +24,19 @@ model Audit {
|
|||
response String?
|
||||
}
|
||||
|
||||
model Secret {
|
||||
versionId String @id
|
||||
name String
|
||||
description String?
|
||||
secretString String
|
||||
accountId String
|
||||
region String
|
||||
createdAt DateTime @default(now())
|
||||
deletionDate DateTime?
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
||||
model SnsTopic {
|
||||
name String @id
|
||||
accountId String
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
export interface CreateSecretDto {
|
||||
versionId?: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
secretString?: string;
|
||||
accountId: string;
|
||||
region: string;
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
|
|
@ -34,7 +37,7 @@ export class CreateSecretHandler extends AbstractActionHandler<QueryParams> {
|
|||
const { Name: name, Description: description, SecretString: secretString, ClientRequestToken } = params;
|
||||
|
||||
const secret = await this.secretService.create({
|
||||
versionId: ClientRequestToken,
|
||||
versionId: ClientRequestToken ?? randomUUID(),
|
||||
description,
|
||||
name,
|
||||
secretString,
|
||||
|
|
@ -42,6 +45,8 @@ export class CreateSecretHandler extends AbstractActionHandler<QueryParams> {
|
|||
region: awsProperties.region,
|
||||
});
|
||||
|
||||
return { ARN: secret.arn, VersionId: secret.versionId, Name: secret.name };
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
|
||||
return { ARN: arn, VersionId: secret.versionId, Name: secret.name };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { PrismaService } from '../_prisma/prisma.service';
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { Secret } from './secret.entity';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
|
|
@ -15,6 +17,7 @@ export class DeleteSecretHandler extends AbstractActionHandler {
|
|||
|
||||
constructor(
|
||||
private readonly secretService: SecretService,
|
||||
private readonly prismaService: PrismaService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
|
@ -26,9 +29,9 @@ export class DeleteSecretHandler extends AbstractActionHandler {
|
|||
VersionId: Joi.string().allow(null, ''),
|
||||
});
|
||||
|
||||
protected async handle({ SecretId, VersionId}: QueryParams, awsProperties: AwsProperties) {
|
||||
protected async handle({ SecretId, VersionId }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const secret = VersionId ?
|
||||
await this.secretService.findByNameAndVersion(name, VersionId) :
|
||||
await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
|
@ -37,10 +40,20 @@ export class DeleteSecretHandler extends AbstractActionHandler {
|
|||
throw new BadRequestException('ResourceNotFoundException', "Secrets Manager can't find the resource that you asked for.");
|
||||
}
|
||||
|
||||
secret.deletionDate = new Date(Date.now() + 1000 * 60 * 60 * 24 * 5).toISOString();
|
||||
await secret.save();
|
||||
await this.prismaService.secret.update({
|
||||
data: {
|
||||
deletionDate: new Date(Date.now() + 1000 * 60 * 60 * 24 * 5),
|
||||
},
|
||||
where: {
|
||||
versionId: secret.versionId,
|
||||
name: secret.name,
|
||||
}
|
||||
});
|
||||
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
|
||||
return {
|
||||
Arn: secret.arn,
|
||||
Arn: arn,
|
||||
DeletionDate: secret.deletionDate,
|
||||
Name: secret.name,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
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';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
SecretId: string;
|
||||
|
|
@ -15,8 +15,7 @@ type QueryParams = {
|
|||
export class DescribeSecretHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Secret)
|
||||
private readonly secretRepo: Repository<Secret>,
|
||||
private readonly secretService: SecretService,
|
||||
private readonly tagsService: TagsService,
|
||||
) {
|
||||
super();
|
||||
|
|
@ -28,20 +27,19 @@ 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' } });
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const secret = await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
||||
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 arn = ArnUtil.fromSecret(secret);
|
||||
const tags = await this.tagsService.getByArn(arn);
|
||||
const listOfTagPairs = TagsService.getJsonSafeTagsMap(tags);
|
||||
|
||||
return {
|
||||
"ARN": secret.arn,
|
||||
"ARN": arn,
|
||||
"CreatedDate": new Date(secret.createdAt).toISOString(),
|
||||
"DeletedDate": secret.deletionDate ? new Date(secret.deletionDate).toISOString() : null,
|
||||
"Description": secret.description,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { Secret } from './secret.entity';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
SecretId: string;
|
||||
|
|
@ -15,8 +15,7 @@ type QueryParams = {
|
|||
export class GetResourcePolicyHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Secret)
|
||||
private readonly secretRepo: Repository<Secret>,
|
||||
private readonly secretService: SecretService,
|
||||
private readonly attributesService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
|
|
@ -28,16 +27,17 @@ export class GetResourcePolicyHandler extends AbstractActionHandler {
|
|||
|
||||
protected async handle({ SecretId }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const secret = await this.secretRepo.findOne({ where: { name }, order: { createdAt: 'DESC' } });
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const secret = await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
||||
if (!secret) {
|
||||
throw new BadRequestException('ResourceNotFoundException', "Secrets Manager can't find the resource that you asked for.");
|
||||
}
|
||||
|
||||
const attribute = await this.attributesService.getResourcePolicyByArn(secret.arn);
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
const attribute = await this.attributesService.getResourcePolicyByArn(arn);
|
||||
return {
|
||||
ARN: secret.arn,
|
||||
ARN: arn,
|
||||
Name: secret.name,
|
||||
ResourcePolicy: attribute?.value,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { Secret } from './secret.entity';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
|
|
@ -28,7 +29,7 @@ export class GetSecretValueHandler extends AbstractActionHandler {
|
|||
|
||||
protected async handle({ SecretId, VersionId}: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const secret = VersionId ?
|
||||
await this.secretService.findByNameAndVersion(name, VersionId) :
|
||||
await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
|
@ -37,8 +38,10 @@ export class GetSecretValueHandler extends AbstractActionHandler {
|
|||
throw new BadRequestException('ResourceNotFoundException', "Secrets Manager can't find the resource that you asked for.");
|
||||
}
|
||||
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
|
||||
return {
|
||||
ARN: secret.arn,
|
||||
ARN: arn,
|
||||
CreatedDate: new Date(secret.createdAt).valueOf() / 1000,
|
||||
Name: secret.name,
|
||||
SecretString: secret.secretString,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { Secret } from './secret.entity';
|
||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
SecretId: string;
|
||||
|
|
@ -16,8 +16,7 @@ type QueryParams = {
|
|||
export class PutResourcePolicyHandler extends AbstractActionHandler {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Secret)
|
||||
private readonly secretRepo: Repository<Secret>,
|
||||
private readonly secretService: SecretService,
|
||||
private readonly attributesService: AttributesService,
|
||||
) {
|
||||
super();
|
||||
|
|
@ -32,16 +31,17 @@ export class PutResourcePolicyHandler extends AbstractActionHandler {
|
|||
|
||||
protected async handle({ SecretId, ResourcePolicy }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const secret = await this.secretRepo.findOne({ where: { name }, order: { createdAt: 'DESC' } });
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const secret = await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
||||
if (!secret) {
|
||||
throw new BadRequestException('ResourceNotFoundException', "Secrets Manager can't find the resource that you asked for.");
|
||||
}
|
||||
|
||||
await this.attributesService.createResourcePolicy(secret.arn, ResourcePolicy);
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
await this.attributesService.createResourcePolicy(arn, ResourcePolicy);
|
||||
return {
|
||||
ARN: secret.arn,
|
||||
ARN: arn,
|
||||
Name: secret.name,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as Joi from 'joi';
|
||||
|
||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import * as Joi from 'joi';
|
||||
import { Secret } from './secret.entity';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { SecretService } from './secret.service';
|
||||
|
||||
type QueryParams = {
|
||||
|
|
@ -31,7 +33,7 @@ export class PutSecretValueHandler extends AbstractActionHandler<QueryParams> {
|
|||
protected async handle(params: QueryParams, awsProperties: AwsProperties) {
|
||||
|
||||
const { SecretId, SecretString: secretString, ClientRequestToken } = params;
|
||||
const name = Secret.getNameFromSecretId(SecretId);
|
||||
const name = ArnUtil.getSecretNameFromSecretId(SecretId);
|
||||
const oldSecret = await this.secretService.findLatestByNameAndRegion(name, awsProperties.region);
|
||||
|
||||
if (!oldSecret) {
|
||||
|
|
@ -39,13 +41,15 @@ export class PutSecretValueHandler extends AbstractActionHandler<QueryParams> {
|
|||
}
|
||||
|
||||
const secret = await this.secretService.create({
|
||||
versionId: ClientRequestToken,
|
||||
versionId: ClientRequestToken ?? randomUUID(),
|
||||
name: oldSecret.name,
|
||||
secretString,
|
||||
accountId: awsProperties.accountId,
|
||||
region: awsProperties.region,
|
||||
});
|
||||
|
||||
return { ARN: secret.arn, VersionId: secret.versionId, Name: secret.name, VersionStages: [] }
|
||||
const arn = ArnUtil.fromSecret(secret);
|
||||
|
||||
return { ARN: arn, VersionId: secret.versionId, Name: secret.name, VersionStages: [] }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import { BaseEntity, Column, CreateDateColumn, Entity, Index, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('secret')
|
||||
export class Secret extends BaseEntity {
|
||||
|
||||
@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;
|
||||
|
||||
@Column({ name: 'deletion_date', nullable: true })
|
||||
deletionDate: string;
|
||||
|
||||
get arn(): string {
|
||||
return `arn:aws:secretsmanager:${this.region}:${this.accountId}:${this.name}`;
|
||||
}
|
||||
|
||||
static getNameFromSecretId(secretId: string) {
|
||||
const parts = secretId.split(':');
|
||||
return parts.length > 1 ? parts.pop() : secretId;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,30 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { CreateSecretDto } from './create-secret.dto';
|
||||
import { Secret } from './secret.entity';
|
||||
import * as uuid from 'uuid';
|
||||
import { Prisma, Secret } from '@prisma/client';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
import { PrismaService } from '../_prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class SecretService {
|
||||
|
||||
constructor(
|
||||
@InjectRepository(Secret)
|
||||
private readonly secretRepo: Repository<Secret>,
|
||||
private readonly prismaService: PrismaService,
|
||||
) {}
|
||||
|
||||
async findLatestByNameAndRegion(name: string, region: string): Promise<Secret> {
|
||||
return await this.secretRepo.findOne({ where: { name, region }, order: { createdAt: 'DESC' } });
|
||||
async findLatestByNameAndRegion(name: string, region: string): Promise<Secret | null> {
|
||||
return await this.prismaService.secret.findFirst({ where: { name, region }, orderBy: { createdAt: 'desc' } });
|
||||
}
|
||||
|
||||
async findByNameAndVersion(name: string, versionId: string): Promise<Secret> {
|
||||
// TypeORM BUG: https://github.com/typeorm/typeorm/issues/5694 - Cannot use findOne here
|
||||
const [ secret ] = await this.secretRepo.find({ where: { name, versionId } });
|
||||
return secret;
|
||||
async findByNameAndVersion(name: string, versionId: string): Promise<Secret | null> {
|
||||
return await this.prismaService.secret.findFirst({ where: { name, versionId } });
|
||||
}
|
||||
|
||||
async create(dto: CreateSecretDto): Promise<Secret> {
|
||||
return await this.secretRepo.create({
|
||||
...dto,
|
||||
versionId: dto.versionId ?? uuid.v4(),
|
||||
}).save();
|
||||
async create(data: Prisma.SecretCreateInput): Promise<Secret> {
|
||||
return await this.prismaService.secret.create({
|
||||
data: {
|
||||
...data,
|
||||
versionId: data.versionId ?? randomUUID(),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { PrismaModule } from '../_prisma/prisma.module';
|
||||
import { Format } from '../abstract-action.handler';
|
||||
import { Action } from '../action.enum';
|
||||
import { AwsSharedEntitiesModule } from '../aws-shared-entities/aws-shared-entities.module';
|
||||
|
|
@ -12,7 +13,6 @@ import { GetResourcePolicyHandler } from './get-resource-policy.handler';
|
|||
import { GetSecretValueHandler } from './get-secret-value.handler';
|
||||
import { PutResourcePolicyHandler } from './put-resource-policy.handler';
|
||||
import { PutSecretValueHandler } from './put-secret-value.handler';
|
||||
import { Secret } from './secret.entity';
|
||||
import { SecretService } from './secret.service';
|
||||
import { SecretsManagerHandlers } from './secrets-manager.constants';
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ const actions = [
|
|||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Secret]),
|
||||
PrismaModule,
|
||||
AwsSharedEntitiesModule,
|
||||
],
|
||||
providers: [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SnsTopic, SnsTopicSubscription } from "@prisma/client";
|
||||
import { Secret, SnsTopic, SnsTopicSubscription } from "@prisma/client";
|
||||
|
||||
export class ArnUtil {
|
||||
|
||||
|
|
@ -9,4 +9,13 @@ export class ArnUtil {
|
|||
static fromTopicSub(topicSub: SnsTopicSubscription): string {
|
||||
return `${topicSub.topicArn}:${topicSub.id}`;
|
||||
}
|
||||
|
||||
static fromSecret(secret: Secret): string {
|
||||
return `arn:aws:secretsmanager:${secret.region}:${secret.accountId}:${secret.name}`;
|
||||
}
|
||||
|
||||
static getSecretNameFromSecretId(secretId: string): string {
|
||||
const parts = secretId.split(':');
|
||||
return parts.length > 1 ? parts.pop() as string : secretId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue