Sts addition, kms updates, context object, improved exception handling
This commit is contained in:
parent
095ecbd643
commit
c34ea76e4e
File diff suppressed because it is too large
Load Diff
|
|
@ -23,6 +23,7 @@
|
||||||
"sqlite3": "^5.1.6"
|
"sqlite3": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^10.4.9",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"@types/joi": "^17.2.2",
|
"@types/joi": "^17.2.2",
|
||||||
"@types/node": "^22.10.2",
|
"@types/node": "^22.10.2",
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,93 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Attribute" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"arn" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Audit" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"action" TEXT,
|
||||||
|
"request" TEXT,
|
||||||
|
"response" TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Secret" (
|
||||||
|
"versionId" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"secretString" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"deletionDate" DATETIME
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SnsTopic" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SnsTopicSubscription" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"topicArn" TEXT NOT NULL,
|
||||||
|
"endpoint" TEXT,
|
||||||
|
"protocol" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SqsQueue" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SqsQueueMessage" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"queueId" INTEGER NOT NULL,
|
||||||
|
"senderId" TEXT NOT NULL,
|
||||||
|
"message" TEXT NOT NULL,
|
||||||
|
"inFlightRelease" DATETIME NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CONSTRAINT "SqsQueueMessage_queueId_fkey" FOREIGN KEY ("queueId") REFERENCES "SqsQueue" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Tag" (
|
||||||
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"arn" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Attribute_arn_name_key" ON "Attribute"("arn", "name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Secret_name_idx" ON "Secret"("name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "SnsTopic_accountId_region_name_key" ON "SnsTopic"("accountId", "region", "name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "SqsQueue_accountId_region_name_key" ON "SqsQueue"("accountId", "region", "name");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SqsQueueMessage_queueId_idx" ON "SqsQueueMessage"("queueId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tag_arn_name_key" ON "Tag"("arn", "name");
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "KmsAlias" (
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"kmsKeyId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY ("accountId", "region", "name")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "KmsKey" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"usage" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"keySpec" TEXT NOT NULL,
|
||||||
|
"key" TEXT NOT NULL,
|
||||||
|
"accountId" TEXT NOT NULL,
|
||||||
|
"region" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "sqlite"
|
||||||
|
|
@ -4,7 +4,7 @@ generator client {
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = ":memory:"
|
url = "file:local-aws-state.sqlite"
|
||||||
}
|
}
|
||||||
|
|
||||||
model Attribute {
|
model Attribute {
|
||||||
|
|
@ -24,6 +24,26 @@ model Audit {
|
||||||
response String?
|
response String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model KmsAlias {
|
||||||
|
name String
|
||||||
|
accountId String
|
||||||
|
region String
|
||||||
|
kmsKeyId String
|
||||||
|
|
||||||
|
@@id([accountId, region, name])
|
||||||
|
}
|
||||||
|
|
||||||
|
model KmsKey {
|
||||||
|
id String @id
|
||||||
|
usage String
|
||||||
|
description String
|
||||||
|
keySpec String
|
||||||
|
key String
|
||||||
|
accountId String
|
||||||
|
region String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
versionId String @id
|
versionId String @id
|
||||||
name String
|
name String
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { CallHandler, ExecutionContext, HttpException, Inject, Injectable, Logger, NestInterceptor, RequestTimeoutException } from '@nestjs/common';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { catchError, Observable, tap, throwError } from 'rxjs';
|
||||||
|
import { Request as ExpressRequest, Response } from 'express';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
|
import { ActionHandlers } from '../app.constants';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import { Format } from '../abstract-action.handler';
|
||||||
|
import { AwsException, InternalFailure } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
import { IRequest, RequestContext } from './request.context';
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuditInterceptor<T> implements NestInterceptor<T, Response> {
|
||||||
|
|
||||||
|
private readonly logger = new Logger(AuditInterceptor.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ActionHandlers)
|
||||||
|
private readonly handlers: ActionHandlers,
|
||||||
|
private readonly prismaService: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
|
||||||
|
|
||||||
|
const requestContext: RequestContext = {
|
||||||
|
requestId: randomUUID(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpContext = context.switchToHttp();
|
||||||
|
const request = httpContext.getRequest<IRequest>();
|
||||||
|
request.context = requestContext;
|
||||||
|
|
||||||
|
const hasTargetHeader = Object.keys(request.headers).some( k => k.toLocaleLowerCase() === 'x-amz-target');
|
||||||
|
const action = hasTargetHeader ? request.headers['x-amz-target'] : request.body.Action;
|
||||||
|
const { value: resolvedAction } = Joi.string().required().valid(...Object.values(Action)).validate(action) as { value: Action | undefined };
|
||||||
|
requestContext.action = resolvedAction;
|
||||||
|
|
||||||
|
const response = context.switchToHttp().getResponse<Response>();
|
||||||
|
response.header('x-amzn-RequestId', requestContext.requestId);
|
||||||
|
|
||||||
|
if (!resolvedAction || !this.handlers[resolvedAction]?.audit) {
|
||||||
|
return next.handle().pipe(
|
||||||
|
catchError(async (error: Error) => {
|
||||||
|
await this.prismaService.audit.create({
|
||||||
|
data: {
|
||||||
|
id: requestContext.requestId,
|
||||||
|
action,
|
||||||
|
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
||||||
|
response: JSON.stringify(error),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.logger.error(error.message);
|
||||||
|
return error;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = this.handlers[resolvedAction];
|
||||||
|
requestContext.format = handler.format;
|
||||||
|
|
||||||
|
return next.handle().pipe(
|
||||||
|
catchError((error: Error) => {
|
||||||
|
|
||||||
|
return throwError(() => {
|
||||||
|
|
||||||
|
if (error instanceof AwsException) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultError = new InternalFailure('Unexpected local AWS exception...');
|
||||||
|
this.logger.error(error.message);
|
||||||
|
defaultError.requestId = requestContext.requestId;
|
||||||
|
return defaultError;
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
tap({
|
||||||
|
|
||||||
|
next: async (data) => await this.prismaService.audit.create({
|
||||||
|
data: {
|
||||||
|
id: requestContext.requestId,
|
||||||
|
action,
|
||||||
|
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
||||||
|
response: JSON.stringify(data),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
error: async (error) => await this.prismaService.audit.create({
|
||||||
|
data: {
|
||||||
|
id: requestContext.requestId,
|
||||||
|
action,
|
||||||
|
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
||||||
|
response: JSON.stringify(error),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { ArgumentsHost, Catch, ExceptionFilter } from "@nestjs/common";
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
import { AwsException } from "../aws-shared-entities/aws-exceptions";
|
||||||
|
import { IRequest } from "./request.context";
|
||||||
|
import { Format } from "../abstract-action.handler";
|
||||||
|
|
||||||
|
@Catch(AwsException)
|
||||||
|
export class AwsExceptionFilter implements ExceptionFilter {
|
||||||
|
catch(exception: AwsException, host: ArgumentsHost) {
|
||||||
|
const ctx = host.switchToHttp();
|
||||||
|
const request = ctx.getRequest<IRequest>();
|
||||||
|
const response = ctx.getResponse<Response>();
|
||||||
|
|
||||||
|
exception.requestId = request.context.requestId;
|
||||||
|
|
||||||
|
if (request.context.format === Format.Xml) {
|
||||||
|
const xml = exception.toXml();
|
||||||
|
return response.status(exception.statusCode).send(xml);
|
||||||
|
}
|
||||||
|
const [newError, newHeaders] = exception.toJson();
|
||||||
|
response.setHeaders(new Map(Object.entries(newHeaders)));
|
||||||
|
return response.status(exception.statusCode).json(newError.getResponse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Request } from "express";
|
||||||
|
|
||||||
|
import { Action } from "../action.enum";
|
||||||
|
import { Format } from "../abstract-action.handler";
|
||||||
|
|
||||||
|
export interface RequestContext {
|
||||||
|
action?: Action;
|
||||||
|
format?: Format;
|
||||||
|
requestId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRequest extends Request {
|
||||||
|
context: RequestContext;
|
||||||
|
headers: {
|
||||||
|
'x-amz-target'?: string;
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
'Action'?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,5 @@ import { PrismaClient } from "@prisma/client";
|
||||||
export class PrismaService extends PrismaClient implements OnModuleInit {
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
await this.$connect();
|
await this.$connect();
|
||||||
const tables = await this.$queryRawUnsafe('.tables');
|
|
||||||
console.log({ tables })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -301,4 +301,15 @@ export enum Action {
|
||||||
SqsSetQueueAttributes = 'SetQueueAttributes',
|
SqsSetQueueAttributes = 'SetQueueAttributes',
|
||||||
SqsTagQueue = 'TagQueue',
|
SqsTagQueue = 'TagQueue',
|
||||||
SqsUntagQueue = 'UntagQueue',
|
SqsUntagQueue = 'UntagQueue',
|
||||||
|
|
||||||
|
// STS
|
||||||
|
StsAssumeRole = 'AssumeRole',
|
||||||
|
StsAssumeRoleWithSaml = 'AssumeRoleWithSaml',
|
||||||
|
StsAssumeRoleWithWebIdentity = 'AssumeRoleWithWebIdentity',
|
||||||
|
StsAssumeRoot = 'AssumeRoot',
|
||||||
|
StsDecodeAuthorizationMessage = 'DecodeAuthorizationMessage',
|
||||||
|
StsGetAccessKeyInfo = 'GetAccessKeyInfo',
|
||||||
|
StsGetCallerIdentity = 'GetCallerIdentity',
|
||||||
|
StsGetFederationToken = 'GetFederationToken',
|
||||||
|
StsGetSessionToken = 'GetSessionToken',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
import { BadRequestException, Body, Controller, Inject, Post, Headers, Req, HttpCode, UseInterceptors } from '@nestjs/common';
|
import { BadRequestException, Body, Controller, Headers, HttpCode, Inject, Post, Req, UseInterceptors } from '@nestjs/common';
|
||||||
import { ActionHandlers } from './app.constants';
|
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { Action } from './action.enum';
|
|
||||||
import { AbstractActionHandler, Format } from './abstract-action.handler';
|
|
||||||
import * as js2xmlparser from 'js2xmlparser';
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { CommonConfig } from './config/common-config.interface';
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { AuditInterceptor } from './audit/audit.interceptor';
|
import * as Joi from 'joi';
|
||||||
|
import * as js2xmlparser from 'js2xmlparser';
|
||||||
|
|
||||||
|
import { AbstractActionHandler, Format } from './abstract-action.handler';
|
||||||
|
import { Action } from './action.enum';
|
||||||
|
import { ActionHandlers } from './app.constants';
|
||||||
|
import { AuditInterceptor } from './_context/audit.interceptor';
|
||||||
|
import { CommonConfig } from './config/common-config.interface';
|
||||||
|
import { InvalidAction, ValidationError } from './aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
|
type QueryParams = {
|
||||||
|
__path: string;
|
||||||
|
} & Record<string, string>;
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
|
|
@ -30,24 +36,24 @@ export class AppController {
|
||||||
const lowerCasedHeaders = Object.keys(headers).reduce((o, k) => {
|
const lowerCasedHeaders = Object.keys(headers).reduce((o, k) => {
|
||||||
o[k.toLocaleLowerCase()] = headers[k];
|
o[k.toLocaleLowerCase()] = headers[k];
|
||||||
return o;
|
return o;
|
||||||
}, {})
|
}, {} as Record<string, string>)
|
||||||
|
|
||||||
const queryParams = { __path: request.path, ...body, ...lowerCasedHeaders };
|
const queryParams: QueryParams = { __path: request.path, ...body, ...lowerCasedHeaders };
|
||||||
const actionKey = queryParams['x-amz-target'] ? 'x-amz-target' : 'Action';
|
const actionKey = queryParams['x-amz-target'] ? 'x-amz-target' : 'Action';
|
||||||
const { error: actionError } = Joi.object({
|
const { error: actionError } = Joi.object({
|
||||||
[actionKey]: Joi.string().valid(...Object.values(Action)).required(),
|
[actionKey]: Joi.string().valid(...Object.values(Action)).required(),
|
||||||
}).validate(queryParams, { allowUnknown: true });
|
}).validate(queryParams, { allowUnknown: true });
|
||||||
|
|
||||||
if (actionError) {
|
if (actionError) {
|
||||||
throw new BadRequestException(actionError.message, { cause: actionError });
|
throw new InvalidAction(actionError.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = queryParams[actionKey];
|
const action = queryParams[actionKey] as Action;
|
||||||
const handler: AbstractActionHandler = this.actionHandlers[action];
|
const handler: AbstractActionHandler = this.actionHandlers[action];
|
||||||
const { error: validatorError, value: validQueryParams } = handler.validator.validate(queryParams, { allowUnknown: true, abortEarly: false });
|
const { error: validatorError, value: validQueryParams } = handler.validator.validate(queryParams, { allowUnknown: true, abortEarly: false });
|
||||||
|
|
||||||
if (validatorError) {
|
if (validatorError) {
|
||||||
throw new BadRequestException(validatorError.message, { cause: validatorError });
|
throw new ValidationError(validatorError.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const awsProperties = {
|
const awsProperties = {
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
import { ActionHandlers } from './app.constants';
|
import { ActionHandlers } from './app.constants';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AuditInterceptor } from './audit/audit.interceptor';
|
import { AuditInterceptor } from './_context/audit.interceptor';
|
||||||
import { AwsSharedEntitiesModule } from './aws-shared-entities/aws-shared-entities.module';
|
import { AwsSharedEntitiesModule } from './aws-shared-entities/aws-shared-entities.module';
|
||||||
import localConfig from './config/local.config';
|
import localConfig from './config/local.config';
|
||||||
import { IAMHandlers } from './iam/iam.constants';
|
|
||||||
import { IamModule } from './iam/iam.module';
|
|
||||||
import { KMSHandlers } from './kms/kms.constants';
|
import { KMSHandlers } from './kms/kms.constants';
|
||||||
import { KmsModule } from './kms/kms.module';
|
import { KmsModule } from './kms/kms.module';
|
||||||
import { SecretsManagerHandlers } from './secrets-manager/secrets-manager.constants';
|
import { SecretsManagerHandlers } from './secrets-manager/secrets-manager.constants';
|
||||||
|
|
@ -16,6 +14,9 @@ import { SnsHandlers } from './sns/sns.constants';
|
||||||
import { SnsModule } from './sns/sns.module';
|
import { SnsModule } from './sns/sns.module';
|
||||||
import { SqsHandlers } from './sqs/sqs.constants';
|
import { SqsHandlers } from './sqs/sqs.constants';
|
||||||
import { SqsModule } from './sqs/sqs.module';
|
import { SqsModule } from './sqs/sqs.module';
|
||||||
|
import { PrismaModule } from './_prisma/prisma.module';
|
||||||
|
import { StsModule } from './sts/sts.module';
|
||||||
|
import { StsHandlers } from './sts/sts.constants';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -23,12 +24,13 @@ import { SqsModule } from './sqs/sqs.module';
|
||||||
load: [localConfig],
|
load: [localConfig],
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
IamModule,
|
PrismaModule,
|
||||||
|
AwsSharedEntitiesModule,
|
||||||
KmsModule,
|
KmsModule,
|
||||||
SecretsManagerModule,
|
SecretsManagerModule,
|
||||||
SnsModule,
|
SnsModule,
|
||||||
SqsModule,
|
SqsModule,
|
||||||
AwsSharedEntitiesModule,
|
StsModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
AppController,
|
AppController,
|
||||||
|
|
@ -39,11 +41,11 @@ import { SqsModule } from './sqs/sqs.module';
|
||||||
provide: ActionHandlers,
|
provide: ActionHandlers,
|
||||||
useFactory: (...args) => args.reduce((m, hs) => ({ ...m, ...hs }), {}),
|
useFactory: (...args) => args.reduce((m, hs) => ({ ...m, ...hs }), {}),
|
||||||
inject: [
|
inject: [
|
||||||
|
KMSHandlers,
|
||||||
|
SecretsManagerHandlers,
|
||||||
SnsHandlers,
|
SnsHandlers,
|
||||||
SqsHandlers,
|
SqsHandlers,
|
||||||
SecretsManagerHandlers,
|
StsHandlers,
|
||||||
KMSHandlers,
|
|
||||||
IAMHandlers,
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
|
|
||||||
import { randomUUID } from 'crypto';
|
|
||||||
import { Observable, tap } from 'rxjs';
|
|
||||||
import { Request as ExpressRequest } from 'express';
|
|
||||||
import * as Joi from 'joi';
|
|
||||||
|
|
||||||
import { PrismaService } from '../_prisma/prisma.service';
|
|
||||||
import { ActionHandlers } from '../app.constants';
|
|
||||||
import { Action } from '../action.enum';
|
|
||||||
|
|
||||||
interface Request extends ExpressRequest {
|
|
||||||
headers: {
|
|
||||||
'x-amz-target'?: string;
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
'Action'?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AuditInterceptor<T> implements NestInterceptor<T, Response> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(ActionHandlers)
|
|
||||||
private readonly handlers: ActionHandlers,
|
|
||||||
private readonly prismaService: PrismaService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
|
|
||||||
|
|
||||||
const requestId = randomUUID();
|
|
||||||
const httpContext = context.switchToHttp();
|
|
||||||
const request = httpContext.getRequest<Request>();
|
|
||||||
|
|
||||||
const hasTargetHeader = Object.keys(request.headers).some( k => k.toLocaleLowerCase() === 'x-amz-target');
|
|
||||||
const action = hasTargetHeader ? request.headers['x-amz-target'] : request.body.Action;
|
|
||||||
const { value: resolvedAction } = Joi.string().required().valid(...Object.values(Action)).validate(action) as { value: Action | undefined };
|
|
||||||
|
|
||||||
const response = context.switchToHttp().getResponse();
|
|
||||||
response.header('x-amzn-RequestId', requestId);
|
|
||||||
|
|
||||||
if (!resolvedAction || !this.handlers[resolvedAction]?.audit) {
|
|
||||||
return next.handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
return next.handle().pipe(
|
|
||||||
tap({
|
|
||||||
|
|
||||||
next: async (data) => await this.prismaService.audit.create({
|
|
||||||
data: {
|
|
||||||
id: requestId,
|
|
||||||
action,
|
|
||||||
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
|
||||||
response: JSON.stringify(data),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
error: async (error) => await this.prismaService.audit.create({
|
|
||||||
data: {
|
|
||||||
id: requestId,
|
|
||||||
action,
|
|
||||||
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
|
|
||||||
response: JSON.stringify(error),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { HttpException, HttpStatus } from "@nestjs/common";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import * as js2xmlparser from 'js2xmlparser';
|
||||||
|
|
||||||
|
export abstract class AwsException {
|
||||||
|
|
||||||
|
requestId: string = randomUUID();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly message: string,
|
||||||
|
readonly errorType: string,
|
||||||
|
readonly statusCode: HttpStatus,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
toXml(): string {
|
||||||
|
return js2xmlparser.parse(`ErrorResponse`, {
|
||||||
|
RequestId: this.requestId,
|
||||||
|
Error: {
|
||||||
|
Code: this.errorType,
|
||||||
|
Message: this.message,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson(): [HttpException, Record<string, string>] {
|
||||||
|
return [
|
||||||
|
new HttpException({
|
||||||
|
message: this.message,
|
||||||
|
__type: this.errorType,
|
||||||
|
}, this.statusCode),
|
||||||
|
{
|
||||||
|
'Server': 'NestJS/local-aws',
|
||||||
|
'X-Amzn-Errortype': this.errorType,
|
||||||
|
'x-amzn-requestid': this.requestId,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AccessDeniedException extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
AccessDeniedException.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class IncompleteSignature extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
IncompleteSignature.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class InternalFailure extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
InternalFailure.name,
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class InvalidAction extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
InvalidAction.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class InvalidClientTokenId extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
InvalidClientTokenId.name,
|
||||||
|
HttpStatus.FORBIDDEN,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class NotAuthorized extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
NotAuthorized.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class OptInRequired extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
OptInRequired.name,
|
||||||
|
HttpStatus.FORBIDDEN,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class RequestExpired extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
RequestExpired.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ServiceUnavailable extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
ServiceUnavailable.name,
|
||||||
|
HttpStatus.SERVICE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ThrottlingException extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
ThrottlingException.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ValidationError extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
ValidationError.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class NotFoundException extends AwsException {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
'The request was rejected because the specified entity or resource could not be found.',
|
||||||
|
NotFoundException.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class InvalidArnException extends AwsException {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(
|
||||||
|
message,
|
||||||
|
InvalidArnException.name,
|
||||||
|
HttpStatus.BAD_REQUEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
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 * as uuid from 'uuid';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
|
||||||
import { IamRole } from './iam-role.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
PolicyArn: string;
|
|
||||||
RoleName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AttachRolePolicyHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamRole)
|
|
||||||
private readonly roleRepo: Repository<IamRole>,
|
|
||||||
@InjectRepository(IamRolePolicyAttachment)
|
|
||||||
private readonly attachRepo: Repository<IamRolePolicyAttachment>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamAttachRolePolicy;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
PolicyArn: Joi.string().required(),
|
|
||||||
RoleName: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ PolicyArn, RoleName }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId} });
|
|
||||||
|
|
||||||
await this.attachRepo.create({
|
|
||||||
id: uuid.v4(),
|
|
||||||
policyArn: PolicyArn,
|
|
||||||
roleId: role.id,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
}).save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
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 * as uuid from 'uuid';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
import { breakdownArn } from '../util/breakdown-arn';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
PolicyArn: string;
|
|
||||||
PolicyDocument: string;
|
|
||||||
SetAsDefault: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreatePolicyVersionHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamPolicy)
|
|
||||||
private readonly policyRepo: Repository<IamPolicy>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamCreatePolicyVersion;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
PolicyArn: Joi.string().required(),
|
|
||||||
PolicyDocument: Joi.string().required(),
|
|
||||||
SetAsDefault: Joi.boolean().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ PolicyArn, PolicyDocument, SetAsDefault }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const { identifier, accountId } = breakdownArn(PolicyArn);
|
|
||||||
const [_policy, name] = identifier.split('/');
|
|
||||||
const currentPolicy = await this.policyRepo.findOne({ where: { accountId, name, isDefault: true } });
|
|
||||||
|
|
||||||
if (SetAsDefault) {
|
|
||||||
await this.policyRepo.update({ accountId, name }, { isDefault: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
const policy = await this.policyRepo.create({
|
|
||||||
id: uuid.v4(),
|
|
||||||
name: name,
|
|
||||||
isDefault: SetAsDefault,
|
|
||||||
version: currentPolicy.version + 1,
|
|
||||||
document: PolicyDocument,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
return {
|
|
||||||
PolicyVersion: {
|
|
||||||
IsDefaultVersion: policy.isDefault,
|
|
||||||
VersionId: `v${policy.version}`,
|
|
||||||
CreateDate: new Date(policy.createdAt).toISOString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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 * as uuid from 'uuid';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
PolicyName: string;
|
|
||||||
PolicyDocument: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreatePolicyHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamPolicy)
|
|
||||||
private readonly policyRepo: Repository<IamPolicy>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamCreatePolicy;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
PolicyName: Joi.string().required(),
|
|
||||||
PolicyDocument: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ PolicyName, PolicyDocument }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const policy = await this.policyRepo.create({
|
|
||||||
id: uuid.v4(),
|
|
||||||
name: PolicyName,
|
|
||||||
document: PolicyDocument,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
return {
|
|
||||||
Policy: {
|
|
||||||
PolicyName: policy.name,
|
|
||||||
DefaultVersionId: policy.version,
|
|
||||||
PolicyId: policy.id,
|
|
||||||
Path: '/',
|
|
||||||
Arn: policy.arn,
|
|
||||||
AttachmentCount: 0,
|
|
||||||
CreateDate: new Date(policy.createdAt).toISOString(),
|
|
||||||
UpdateDate: new Date(policy.updatedAt).toISOString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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 { IamRole } from './iam-role.entity';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
RoleName: string;
|
|
||||||
Path: string;
|
|
||||||
AssumeRolePolicyDocument: string;
|
|
||||||
MaxSessionDuration: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreateRoleHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamRole)
|
|
||||||
private readonly roleRepo: Repository<IamRole>,
|
|
||||||
@InjectRepository(IamPolicy)
|
|
||||||
private readonly policyRepo: Repository<IamPolicy>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamCreateRole;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
RoleName: Joi.string().required(),
|
|
||||||
Path: Joi.string().required(),
|
|
||||||
AssumeRolePolicyDocument: Joi.string().required(),
|
|
||||||
MaxSessionDuration: Joi.number().default(3600),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ RoleName, Path, AssumeRolePolicyDocument, MaxSessionDuration }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const policy = await this.policyRepo.create({
|
|
||||||
id: uuid.v4(),
|
|
||||||
name: `${RoleName}-AssumeRolePolicyDocument`,
|
|
||||||
document: AssumeRolePolicyDocument,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
const id = uuid.v4();
|
|
||||||
|
|
||||||
await this.roleRepo.create({
|
|
||||||
id,
|
|
||||||
roleName: RoleName,
|
|
||||||
path: Path,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
assumeRolePolicyDocumentId: policy.id,
|
|
||||||
maxSessionDuration: MaxSessionDuration,
|
|
||||||
}).save();
|
|
||||||
|
|
||||||
const role = await this.roleRepo.findOne({ where: { id }});
|
|
||||||
|
|
||||||
return {
|
|
||||||
Role: role.metadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import { Injectable, NotFoundException } 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetPolicyHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamPolicy)
|
|
||||||
private readonly policyRepo: Repository<IamPolicy>,
|
|
||||||
@InjectRepository(IamRolePolicyAttachment)
|
|
||||||
private readonly attachmentRepo: Repository<IamRolePolicyAttachment>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamGetPolicy;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
PolicyArn: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ PolicyArn }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const { identifier, accountId } = breakdownArn(PolicyArn);
|
|
||||||
const [_policy, name] = identifier.split('/');
|
|
||||||
const policy = await this.policyRepo.findOne({ where: { name, accountId, isDefault: true }});
|
|
||||||
|
|
||||||
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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachmentCount = await this.attachmentRepo.count({ where: { policyArn: policy.arn } });
|
|
||||||
|
|
||||||
return {
|
|
||||||
Policy: {
|
|
||||||
PolicyName: policy.name,
|
|
||||||
DefaultVersionId: policy.version,
|
|
||||||
PolicyId: policy.id,
|
|
||||||
Path: '/',
|
|
||||||
Arn: policy.arn,
|
|
||||||
AttachmentCount: attachmentCount,
|
|
||||||
CreateDate: new Date(policy.createdAt).toISOString(),
|
|
||||||
UpdateDate: new Date(policy.updatedAt).toISOString(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import { Injectable, NotFoundException } 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 { IamRole } from './iam-role.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
RoleName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class GetRoleHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamRole)
|
|
||||||
private readonly roleRepo: Repository<IamRole>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamGetRole;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
RoleName: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } });
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
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 {
|
|
||||||
Role: role.metadata,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, OneToMany, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
|
||||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
|
||||||
import { IamRole } from './iam-role.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'iam_policy' })
|
|
||||||
export class IamPolicy extends BaseEntity {
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@Column({ default: 1 })
|
|
||||||
version: number;
|
|
||||||
|
|
||||||
@Column({ name: 'is_default', default: true })
|
|
||||||
isDefault: boolean;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
document: string;
|
|
||||||
|
|
||||||
@Column({ name: 'account_id', nullable: false })
|
|
||||||
accountId: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: string;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: string;
|
|
||||||
|
|
||||||
@OneToOne(() => IamRole, role => role.assumeRolePolicyDocument)
|
|
||||||
iamRole: IamRole;
|
|
||||||
|
|
||||||
get arn() {
|
|
||||||
return `arn:aws:iam::${this.accountId}:policy/${this.name}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
import { BaseEntity, Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'iam_role_policy_attachment' })
|
|
||||||
export class IamRolePolicyAttachment extends BaseEntity {
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@Column({ name: 'policy_arn' })
|
|
||||||
policyArn: string;
|
|
||||||
|
|
||||||
@Column({ name: 'role_name' })
|
|
||||||
roleId: string;
|
|
||||||
|
|
||||||
@Column({ name: 'account_id'})
|
|
||||||
accountId: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
import { BaseEntity, Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
|
|
||||||
@Entity({ name: 'iam_role' })
|
|
||||||
export class IamRole extends BaseEntity {
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: string
|
|
||||||
|
|
||||||
@Column({ name: 'role_name' })
|
|
||||||
roleName: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
path: string;
|
|
||||||
|
|
||||||
@Column({ name: 'assume_role_policy_document_id', nullable: false })
|
|
||||||
assumeRolePolicyDocumentId: string;
|
|
||||||
|
|
||||||
@Column({ name: 'account_id', nullable: false })
|
|
||||||
accountId: string;
|
|
||||||
|
|
||||||
@Column({ name: 'max_session_duration', nullable: false, default: 0 })
|
|
||||||
maxSessionDuration: number;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: string;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: string;
|
|
||||||
|
|
||||||
@OneToOne(() => IamPolicy, (policy) => policy.id, { eager: true })
|
|
||||||
@JoinColumn({ name: 'assume_role_policy_document_id' })
|
|
||||||
assumeRolePolicyDocument: IamPolicy;
|
|
||||||
|
|
||||||
get arn() {
|
|
||||||
const identifier = this.path.split('/');
|
|
||||||
identifier.push(this.roleName);
|
|
||||||
return `arn:aws:iam::${this.accountId}:role/${identifier.join('/')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
get metadata() {
|
|
||||||
return {
|
|
||||||
Path: this.path,
|
|
||||||
Arn: this.arn,
|
|
||||||
RoleName: this.roleName,
|
|
||||||
AssumeRolePolicyDocument: this.assumeRolePolicyDocument.document,
|
|
||||||
CreateDate: new Date(this.createdAt).toISOString(),
|
|
||||||
RoleId: this.id,
|
|
||||||
MaxSessionDuration: this.maxSessionDuration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { AbstractActionHandler } from '../abstract-action.handler';
|
|
||||||
import { Action } from '../action.enum';
|
|
||||||
|
|
||||||
export type IAMHandlers = Record<Action, AbstractActionHandler>;
|
|
||||||
export const IAMHandlers = Symbol.for('IAMHandlers');
|
|
||||||
|
|
@ -1,207 +0,0 @@
|
||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { Format } from '../abstract-action.handler';
|
|
||||||
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 { 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';
|
|
||||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
|
||||||
import { IamRole } from './iam-role.entity';
|
|
||||||
import { IAMHandlers } from './iam.constants';
|
|
||||||
import { ListAttachedRolePoliciesHandler } from './list-attached-role-policies';
|
|
||||||
import { ListRolePoliciesHandler } from './list-role-policies.handler';
|
|
||||||
|
|
||||||
const handlers = [
|
|
||||||
AttachRolePolicyHandler,
|
|
||||||
CreatePolicyHandler,
|
|
||||||
CreatePolicyVersionHandler,
|
|
||||||
CreateRoleHandler,
|
|
||||||
GetPolicyHandler,
|
|
||||||
GetRoleHandler,
|
|
||||||
GetPolicyVersionHandler,
|
|
||||||
ListAttachedRolePoliciesHandler,
|
|
||||||
ListRolePoliciesHandler,
|
|
||||||
]
|
|
||||||
|
|
||||||
const actions = [
|
|
||||||
Action.IamAddClientIDToOpenIDConnectProvider,
|
|
||||||
Action.IamAddRoleToInstanceProfile,
|
|
||||||
Action.IamAddUserToGroup,
|
|
||||||
Action.IamAttachGroupPolicy,
|
|
||||||
Action.IamAttachRolePolicy,
|
|
||||||
Action.IamAttachUserPolicy,
|
|
||||||
Action.IamChangePassword,
|
|
||||||
Action.IamCreateAccessKey,
|
|
||||||
Action.IamCreateAccountAlias,
|
|
||||||
Action.IamCreateGroup,
|
|
||||||
Action.IamCreateInstanceProfile,
|
|
||||||
Action.IamCreateLoginProfile,
|
|
||||||
Action.IamCreateOpenIDConnectProvider,
|
|
||||||
Action.IamCreatePolicy,
|
|
||||||
Action.IamCreatePolicyVersion,
|
|
||||||
Action.IamCreateRole,
|
|
||||||
Action.IamCreateSAMLProvider,
|
|
||||||
Action.IamCreateServiceLinkedRole,
|
|
||||||
Action.IamCreateServiceSpecificCredential,
|
|
||||||
Action.IamCreateUser,
|
|
||||||
Action.IamCreateVirtualMFADevice,
|
|
||||||
Action.IamDeactivateMFADevice,
|
|
||||||
Action.IamDeleteAccessKey,
|
|
||||||
Action.IamDeleteAccountAlias,
|
|
||||||
Action.IamDeleteAccountPasswordPolicy,
|
|
||||||
Action.IamDeleteGroup,
|
|
||||||
Action.IamDeleteGroupPolicy,
|
|
||||||
Action.IamDeleteInstanceProfile,
|
|
||||||
Action.IamDeleteLoginProfile,
|
|
||||||
Action.IamDeleteOpenIDConnectProvider,
|
|
||||||
Action.IamDeletePolicy,
|
|
||||||
Action.IamDeletePolicyVersion,
|
|
||||||
Action.IamDeleteRole,
|
|
||||||
Action.IamDeleteRolePermissionsBoundary,
|
|
||||||
Action.IamDeleteRolePolicy,
|
|
||||||
Action.IamDeleteSAMLProvider,
|
|
||||||
Action.IamDeleteServerCertificate,
|
|
||||||
Action.IamDeleteServiceLinkedRole,
|
|
||||||
Action.IamDeleteServiceSpecificCredential,
|
|
||||||
Action.IamDeleteSigningCertificate,
|
|
||||||
Action.IamDeleteSSHPublicKey,
|
|
||||||
Action.IamDeleteUser,
|
|
||||||
Action.IamDeleteUserPermissionsBoundary,
|
|
||||||
Action.IamDeleteUserPolicy,
|
|
||||||
Action.IamDeleteVirtualMFADevice,
|
|
||||||
Action.IamDetachGroupPolicy,
|
|
||||||
Action.IamDetachRolePolicy,
|
|
||||||
Action.IamDetachUserPolicy,
|
|
||||||
Action.IamEnableMFADevice,
|
|
||||||
Action.IamGenerateCredentialReport,
|
|
||||||
Action.IamGenerateOrganizationsAccessReport,
|
|
||||||
Action.IamGenerateServiceLastAccessedDetails,
|
|
||||||
Action.IamGetAccessKeyLastUsed,
|
|
||||||
Action.IamGetAccountAuthorizationDetails,
|
|
||||||
Action.IamGetAccountPasswordPolicy,
|
|
||||||
Action.IamGetAccountSummary,
|
|
||||||
Action.IamGetContextKeysForCustomPolicy,
|
|
||||||
Action.IamGetContextKeysForPrincipalPolicy,
|
|
||||||
Action.IamGetCredentialReport,
|
|
||||||
Action.IamGetGroup,
|
|
||||||
Action.IamGetGroupPolicy,
|
|
||||||
Action.IamGetInstanceProfile,
|
|
||||||
Action.IamGetLoginProfile,
|
|
||||||
Action.IamGetOpenIDConnectProvider,
|
|
||||||
Action.IamGetOrganizationsAccessReport,
|
|
||||||
Action.IamGetPolicy,
|
|
||||||
Action.IamGetPolicyVersion,
|
|
||||||
Action.IamGetRole,
|
|
||||||
Action.IamGetRolePolicy,
|
|
||||||
Action.IamGetSAMLProvider,
|
|
||||||
Action.IamGetServerCertificate,
|
|
||||||
Action.IamGetServiceLastAccessedDetails,
|
|
||||||
Action.IamGetServiceLastAccessedDetailsWithEntities,
|
|
||||||
Action.IamGetServiceLinkedRoleDeletionStatus,
|
|
||||||
Action.IamGetSSHPublicKey,
|
|
||||||
Action.IamGetUser,
|
|
||||||
Action.IamGetUserPolicy,
|
|
||||||
Action.IamListAccessKeys,
|
|
||||||
Action.IamListAccountAliases,
|
|
||||||
Action.IamListAttachedGroupPolicies,
|
|
||||||
Action.IamListAttachedRolePolicies,
|
|
||||||
Action.IamListAttachedUserPolicies,
|
|
||||||
Action.IamListEntitiesForPolicy,
|
|
||||||
Action.IamListGroupPolicies,
|
|
||||||
Action.IamListGroups,
|
|
||||||
Action.IamListGroupsForUser,
|
|
||||||
Action.IamListInstanceProfiles,
|
|
||||||
Action.IamListInstanceProfilesForRole,
|
|
||||||
Action.IamListInstanceProfileTags,
|
|
||||||
Action.IamListMFADevices,
|
|
||||||
Action.IamListMFADeviceTags,
|
|
||||||
Action.IamListOpenIDConnectProviders,
|
|
||||||
Action.IamListOpenIDConnectProviderTags,
|
|
||||||
Action.IamListPolicies,
|
|
||||||
Action.IamListPoliciesGrantingServiceAccess,
|
|
||||||
Action.IamListPolicyTags,
|
|
||||||
Action.IamListPolicyVersions,
|
|
||||||
Action.IamListRolePolicies,
|
|
||||||
Action.IamListRoles,
|
|
||||||
Action.IamListRoleTags,
|
|
||||||
Action.IamListSAMLProviders,
|
|
||||||
Action.IamListSAMLProviderTags,
|
|
||||||
Action.IamListServerCertificates,
|
|
||||||
Action.IamListServerCertificateTags,
|
|
||||||
Action.IamListServiceSpecificCredentials,
|
|
||||||
Action.IamListSigningCertificates,
|
|
||||||
Action.IamListSSHPublicKeys,
|
|
||||||
Action.IamListUserPolicies,
|
|
||||||
Action.IamListUsers,
|
|
||||||
Action.IamListUserTags,
|
|
||||||
Action.IamListVirtualMFADevices,
|
|
||||||
Action.IamPutGroupPolicy,
|
|
||||||
Action.IamPutRolePermissionsBoundary,
|
|
||||||
Action.IamPutRolePolicy,
|
|
||||||
Action.IamPutUserPermissionsBoundary,
|
|
||||||
Action.IamPutUserPolicy,
|
|
||||||
Action.IamRemoveClientIDFromOpenIDConnectProvider,
|
|
||||||
Action.IamRemoveRoleFromInstanceProfile,
|
|
||||||
Action.IamRemoveUserFromGroup,
|
|
||||||
Action.IamResetServiceSpecificCredential,
|
|
||||||
Action.IamResyncMFADevice,
|
|
||||||
Action.IamSetDefaultPolicyVersion,
|
|
||||||
Action.IamSetSecurityTokenServicePreferences,
|
|
||||||
Action.IamSimulateCustomPolicy,
|
|
||||||
Action.IamSimulatePrincipalPolicy,
|
|
||||||
Action.IamTagInstanceProfile,
|
|
||||||
Action.IamTagMFADevice,
|
|
||||||
Action.IamTagOpenIDConnectProvider,
|
|
||||||
Action.IamTagPolicy,
|
|
||||||
Action.IamTagRole,
|
|
||||||
Action.IamTagSAMLProvider,
|
|
||||||
Action.IamTagServerCertificate,
|
|
||||||
Action.IamTagUser,
|
|
||||||
Action.IamUntagInstanceProfile,
|
|
||||||
Action.IamUntagMFADevice,
|
|
||||||
Action.IamUntagOpenIDConnectProvider,
|
|
||||||
Action.IamUntagPolicy,
|
|
||||||
Action.IamUntagRole,
|
|
||||||
Action.IamUntagSAMLProvider,
|
|
||||||
Action.IamUntagServerCertificate,
|
|
||||||
Action.IamUntagUser,
|
|
||||||
Action.IamUpdateAccessKey,
|
|
||||||
Action.IamUpdateAccountPasswordPolicy,
|
|
||||||
Action.IamUpdateAssumeRolePolicy,
|
|
||||||
Action.IamUpdateGroup,
|
|
||||||
Action.IamUpdateLoginProfile,
|
|
||||||
Action.IamUpdateOpenIDConnectProviderThumbprint,
|
|
||||||
Action.IamUpdateRole,
|
|
||||||
Action.IamUpdateRoleDescription,
|
|
||||||
Action.IamUpdateSAMLProvider,
|
|
||||||
Action.IamUpdateServerCertificate,
|
|
||||||
Action.IamUpdateServiceSpecificCredential,
|
|
||||||
Action.IamUpdateSigningCertificate,
|
|
||||||
Action.IamUpdateSSHPublicKey,
|
|
||||||
Action.IamUpdateUser,
|
|
||||||
Action.IamUploadServerCertificate,
|
|
||||||
Action.IamUploadSigningCertificate,
|
|
||||||
Action.IamUploadSSHPublicKey,
|
|
||||||
]
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forFeature([IamPolicy, IamRole, IamRolePolicyAttachment]),
|
|
||||||
AwsSharedEntitiesModule,
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
...handlers,
|
|
||||||
ExistingActionHandlersProvider(handlers),
|
|
||||||
DefaultActionHandlerProvider(IAMHandlers, Format.Xml, actions),
|
|
||||||
],
|
|
||||||
exports: [IAMHandlers],
|
|
||||||
})
|
|
||||||
export class IamModule {}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import { Injectable, NotFoundException } 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 { In, Repository } from 'typeorm';
|
|
||||||
import { IamRole } from './iam-role.entity';
|
|
||||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
|
||||||
import { IamPolicy } from './iam-policy.entity';
|
|
||||||
import { breakdownArn } from '../util/breakdown-arn';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
RoleName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ListAttachedRolePoliciesHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamRole)
|
|
||||||
private readonly roleRepo: Repository<IamRole>,
|
|
||||||
@InjectRepository(IamPolicy)
|
|
||||||
private readonly policyRepo: Repository<IamPolicy>,
|
|
||||||
@InjectRepository(IamRolePolicyAttachment)
|
|
||||||
private readonly attachmentRepo: Repository<IamRolePolicyAttachment>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamListAttachedRolePolicies;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
RoleName: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } });
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
throw new NotFoundException('NoSuchEntity', 'The request was rejected because it referenced a resource entity that does not exist. The error message describes the resource.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const attachments = await this.attachmentRepo.find({ where: { roleId: role.id } })
|
|
||||||
const policyIds = attachments.map(({ policyArn }) => breakdownArn(policyArn)).map(({ identifier }) => identifier.split('/')[1]);
|
|
||||||
const policies = await this.policyRepo.find({ where: { name: In(policyIds), isDefault: true } });
|
|
||||||
|
|
||||||
return {
|
|
||||||
AttachedPolicies: {
|
|
||||||
member: [role.assumeRolePolicyDocument, ...policies].map(p => ({
|
|
||||||
PolicyName: p.name,
|
|
||||||
PolicyArn: p.arn,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import { Injectable, NotFoundException } 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 { IamRole } from './iam-role.entity';
|
|
||||||
import { IamRolePolicyAttachment } from './iam-role-policy-attachment.entity';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
RoleName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ListRolePoliciesHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(IamRole)
|
|
||||||
private readonly roleRepo: Repository<IamRole>,
|
|
||||||
@InjectRepository(IamRolePolicyAttachment)
|
|
||||||
private readonly attachmentRepo: Repository<IamRolePolicyAttachment>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Xml;
|
|
||||||
action = Action.IamListRolePolicies;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
RoleName: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ RoleName }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
const role = await this.roleRepo.findOne({ where: { roleName: RoleName, accountId: awsProperties.accountId } });
|
|
||||||
|
|
||||||
if (!role) {
|
|
||||||
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 {
|
|
||||||
PolicyNames: [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
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';
|
|
||||||
|
|
||||||
type QueryParams = {
|
|
||||||
AliasName: string;
|
|
||||||
TargetKeyId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CreateAliasHandler extends AbstractActionHandler<QueryParams> {
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(KmsKeyAlias)
|
|
||||||
private readonly aliasRepo: Repository<KmsKeyAlias>,
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
format = Format.Json;
|
|
||||||
action = Action.KmsCreateAlias;
|
|
||||||
validator = Joi.object<QueryParams, true>({
|
|
||||||
AliasName: Joi.string().required(),
|
|
||||||
TargetKeyId: Joi.string().required(),
|
|
||||||
});
|
|
||||||
|
|
||||||
protected async handle({ AliasName, TargetKeyId }: QueryParams, awsProperties: AwsProperties) {
|
|
||||||
|
|
||||||
await this.aliasRepo.save({
|
|
||||||
name: AliasName.split('/')[1],
|
|
||||||
targetKeyId: TargetKeyId,
|
|
||||||
accountId: awsProperties.accountId,
|
|
||||||
region: awsProperties.region,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
import { AbstractActionHandler, AwsProperties, Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
import { Action } from '../action.enum';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { KmsKey } from './kms-key.entity';
|
|
||||||
import { breakdownArn } from '../util/breakdown-arn';
|
import { breakdownArn } from '../util/breakdown-arn';
|
||||||
import { KmsService } from './kms.service';
|
import { KmsService } from './kms.service';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
|
GrantTokens?: string[];
|
||||||
KeyId: string;
|
KeyId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,8 +16,6 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly kmsService: KmsService,
|
private readonly kmsService: KmsService,
|
||||||
@InjectRepository(KmsKey)
|
|
||||||
private readonly keyRepo: Repository<KmsKey>,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
@ -27,6 +24,7 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
action = Action.KmsDescribeKey;
|
action = Action.KmsDescribeKey;
|
||||||
validator = Joi.object<QueryParams, true>({
|
validator = Joi.object<QueryParams, true>({
|
||||||
KeyId: Joi.string().required(),
|
KeyId: Joi.string().required(),
|
||||||
|
GrantTokens: Joi.array().items(Joi.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
|
@ -38,16 +36,17 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
||||||
identifier: KeyId,
|
identifier: KeyId,
|
||||||
};
|
};
|
||||||
const [ type, pk ] = searchable.identifier.split('/');
|
const [ type, pk ] = searchable.identifier.split('/');
|
||||||
const keyId: Promise<string> = type === 'key' ?
|
const keyId = await (type === 'key' ? Promise.resolve(pk) : this.kmsService.findKeyIdFromAlias(pk, searchable));
|
||||||
Promise.resolve(pk) :
|
|
||||||
this.kmsService.findKeyIdFromAlias(pk, searchable);
|
|
||||||
|
|
||||||
|
if (!keyId) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
const keyRecord = await this.keyRepo.findOne({ where: {
|
const keyRecord = await this.kmsService.findOneById(keyId);
|
||||||
id: await keyId,
|
|
||||||
region: searchable.region,
|
if (!keyRecord) {
|
||||||
accountId: searchable.accountId,
|
throw new NotFoundException();
|
||||||
}});
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
KeyMetadata: keyRecord.metadata,
|
KeyMetadata: keyRecord.metadata,
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
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,21 +1,11 @@
|
||||||
import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm';
|
export class KmsKeyAlias {
|
||||||
|
|
||||||
@Entity({ name: 'kms_key_alias' })
|
// name: string;
|
||||||
export class KmsKeyAlias extends BaseEntity {
|
// targetKeyId: string;
|
||||||
|
// accountId: string;
|
||||||
|
// region: string;
|
||||||
|
|
||||||
@PrimaryColumn()
|
// get arn() {
|
||||||
name: string;
|
// return `arn:aws:kms:${this.region}:${this.accountId}:alias/${this.name}`;
|
||||||
|
// }
|
||||||
@Column({ name: 'target_key_id' })
|
|
||||||
targetKeyId: string;
|
|
||||||
|
|
||||||
@Column({ name: 'account_id', nullable: false })
|
|
||||||
accountId: string;
|
|
||||||
|
|
||||||
@Column({ name: 'region', nullable: false })
|
|
||||||
region: string;
|
|
||||||
|
|
||||||
get arn() {
|
|
||||||
return `arn:aws:kms:${this.region}:${this.accountId}:alias/${this.name}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,29 @@
|
||||||
import { BaseEntity, Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm';
|
import { KmsKey as PrismaKmsKey } from '@prisma/client';
|
||||||
|
|
||||||
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 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';
|
export type KeyUsage = 'SIGN_VERIFY' | 'ENCRYPT_DECRYPT' | 'GENERATE_VERIFY_MAC';
|
||||||
|
|
||||||
@Entity({ name: 'kms_key'})
|
export class KmsKey implements PrismaKmsKey {
|
||||||
export class KmsKey extends BaseEntity {
|
|
||||||
|
|
||||||
@PrimaryColumn()
|
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@Column({ name: 'usage' })
|
|
||||||
usage: KeyUsage;
|
usage: KeyUsage;
|
||||||
|
|
||||||
@Column({ name: 'description' })
|
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@Column({ name: 'key_spec' })
|
|
||||||
keySpec: KeySpec;
|
keySpec: KeySpec;
|
||||||
|
|
||||||
@Column({ name: 'key' })
|
|
||||||
key: string;
|
key: string;
|
||||||
|
|
||||||
@Column({ name: 'account_id', nullable: false })
|
|
||||||
accountId: string;
|
accountId: string;
|
||||||
|
|
||||||
@Column({ name: 'region', nullable: false })
|
|
||||||
region: string;
|
region: string;
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
@CreateDateColumn()
|
constructor(p: PrismaKmsKey) {
|
||||||
createdAt: string;
|
this.id = p.id;
|
||||||
|
this.usage = p.usage as KeyUsage;
|
||||||
|
this.description = p.description;
|
||||||
|
this.keySpec = p.keySpec as KeySpec;
|
||||||
|
this.key = p.key;
|
||||||
|
this.accountId = p.accountId;
|
||||||
|
this.region = p.region;
|
||||||
|
this.createdAt = p.createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
get arn() {
|
get arn() {
|
||||||
return `arn:aws:kms:${this.region}:${this.accountId}:key/${this.id}`;
|
return `arn:aws:kms:${this.region}:${this.accountId}:key/${this.id}`;
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,17 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { Format } from '../abstract-action.handler';
|
import { Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
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 { CreateAliasHandler } from './create-alias.handler';
|
|
||||||
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 { KmsService } from './kms.service';
|
||||||
import { GetPublicKeyHandler } from './get-public-key.handler';
|
import { KMSHandlers } from './kms.constants';
|
||||||
|
import { DescribeKeyHandler } from './describe-key.handler';
|
||||||
|
import { PrismaModule } from '../_prisma/prisma.module';
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
CreateAliasHandler,
|
|
||||||
DescribeKeyHandler,
|
DescribeKeyHandler,
|
||||||
GetPublicKeyHandler,
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
|
|
@ -74,8 +69,8 @@ const actions = [
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([KmsKey, KmsKeyAlias]),
|
|
||||||
AwsSharedEntitiesModule,
|
AwsSharedEntitiesModule,
|
||||||
|
PrismaModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...handlers,
|
...handlers,
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,30 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { ArnParts } from '../util/breakdown-arn';
|
import { ArnParts } from '../util/breakdown-arn';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { KmsKey } from './kms-key.entity';
|
||||||
import { KmsKeyAlias } from './kms-key-alias.entity';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KmsService {
|
export class KmsService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(KmsKeyAlias)
|
private readonly prismaService: PrismaService,
|
||||||
private readonly aliasRepo: Repository<KmsKeyAlias>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string> {
|
async findOneById(id: string): Promise<KmsKey | null> {
|
||||||
const record = await this.aliasRepo.findOne({ where: {
|
const pRecord = await this.prismaService.kmsKey.findFirst({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
return pRecord ? new KmsKey(pRecord) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string | null> {
|
||||||
|
const record = await this.prismaService.kmsAlias.findFirst({
|
||||||
|
where: {
|
||||||
name: alias,
|
name: alias,
|
||||||
accountId: arn.accountId,
|
accountId: arn.accountId,
|
||||||
region: arn.region,
|
region: arn.region,
|
||||||
}});
|
}
|
||||||
return record.targetKeyId;
|
});
|
||||||
|
return record?.kmsKeyId ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,15 @@ import { NestFactory, Reflector } from '@nestjs/core';
|
||||||
|
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { CommonConfig } from './config/common-config.interface';
|
import { CommonConfig } from './config/common-config.interface';
|
||||||
|
import { AwsExceptionFilter } from './_context/exception.filter';
|
||||||
|
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
// app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
|
||||||
|
app.useGlobalFilters(new AwsExceptionFilter());
|
||||||
app.use(bodyParser.json({ type: 'application/x-amz-json-1.1'}));
|
app.use(bodyParser.json({ type: 'application/x-amz-json-1.1'}));
|
||||||
|
|
||||||
const configService: ConfigService<CommonConfig, true> = app.get(ConfigService);
|
const configService: ConfigService<CommonConfig, true> = app.get(ConfigService);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { TagsService } from '../aws-shared-entities/tags.service';
|
||||||
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
import { AttributesService } from '../aws-shared-entities/attributes.service';
|
||||||
import { PrismaService } from '../_prisma/prisma.service';
|
import { PrismaService } from '../_prisma/prisma.service';
|
||||||
import { ArnUtil } from '../util/arn-util.static';
|
import { ArnUtil } from '../util/arn-util.static';
|
||||||
|
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
SubscriptionArn: string;
|
SubscriptionArn: string;
|
||||||
|
|
@ -34,7 +35,7 @@ export class UnsubscribeHandler extends AbstractActionHandler<QueryParams> {
|
||||||
const subscription = await this.prismaService.snsTopicSubscription.findFirst({ where: { id } });
|
const subscription = await this.prismaService.snsTopicSubscription.findFirst({ where: { id } });
|
||||||
|
|
||||||
if (!subscription) {
|
if (!subscription) {
|
||||||
return;
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
const arn = ArnUtil.fromTopicSub(subscription);
|
const arn = ArnUtil.fromTopicSub(subscription);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { SqsQueue } from './sqs-queue.entity';
|
||||||
|
|
||||||
type QueryParams = {
|
type QueryParams = {
|
||||||
QueueUrl: string;
|
QueueUrl: string;
|
||||||
}
|
} & Record<string, string>;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams> {
|
export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
@ -34,7 +34,7 @@ export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams
|
||||||
for (const header of Object.keys(params)) {
|
for (const header of Object.keys(params)) {
|
||||||
if (header.includes('DeleteMessageBatchRequestEntry') && header.includes('ReceiptHandle')) {
|
if (header.includes('DeleteMessageBatchRequestEntry') && header.includes('ReceiptHandle')) {
|
||||||
const ReceiptHandle = params[header];
|
const ReceiptHandle = params[header];
|
||||||
await this.sqsQueueEntryService.deleteMessage(accountId, name, ReceiptHandle);
|
await this.sqsQueueEntryService.deleteMessage(ReceiptHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,6 @@ export class DeleteMessageHandler extends AbstractActionHandler<QueryParams> {
|
||||||
});
|
});
|
||||||
|
|
||||||
protected async handle({ QueueUrl, ReceiptHandle }: QueryParams, awsProperties: AwsProperties) {
|
protected async handle({ QueueUrl, ReceiptHandle }: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
await this.sqsQueueEntryService.deleteMessage(ReceiptHandle);
|
||||||
const [accountId, name] = SqsQueue.tryGetAccountIdAndNameFromPathOrArn(QueueUrl);
|
|
||||||
await this.sqsQueueEntryService.deleteMessage(accountId, name, ReceiptHandle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { Format } from '../abstract-action.handler';
|
import { Format } from '../abstract-action.handler';
|
||||||
import { Action } from '../action.enum';
|
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';
|
||||||
|
|
@ -14,9 +14,9 @@ import { PurgeQueueHandler } from './purge-queue.handler';
|
||||||
import { ReceiveMessageHandler } from './receive-message.handler';
|
import { ReceiveMessageHandler } from './receive-message.handler';
|
||||||
import { SetQueueAttributesHandler } from './set-queue-attributes.handler';
|
import { SetQueueAttributesHandler } from './set-queue-attributes.handler';
|
||||||
import { SqsQueueEntryService } from './sqs-queue-entry.service';
|
import { SqsQueueEntryService } from './sqs-queue-entry.service';
|
||||||
import { SqsQueue } from './sqs-queue.entity';
|
|
||||||
import { SqsHandlers } from './sqs.constants';
|
import { SqsHandlers } from './sqs.constants';
|
||||||
import { DeleteMessageBatchHandler } from './delete-message-batch.handler';
|
import { DeleteMessageBatchHandler } from './delete-message-batch.handler';
|
||||||
|
import { PrismaModule } from '../_prisma/prisma.module';
|
||||||
|
|
||||||
const handlers = [
|
const handlers = [
|
||||||
CreateQueueHandler,
|
CreateQueueHandler,
|
||||||
|
|
@ -55,8 +55,8 @@ const actions = [
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([SqsQueue]),
|
|
||||||
AwsSharedEntitiesModule,
|
AwsSharedEntitiesModule,
|
||||||
|
PrismaModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...handlers,
|
...handlers,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import * as Joi from "joi";
|
||||||
|
|
||||||
|
import { AbstractActionHandler, AwsProperties, Format } from "../abstract-action.handler";
|
||||||
|
import { Action } from "../action.enum";
|
||||||
|
|
||||||
|
type QueryParams = {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GetCallerIdentityHandler extends AbstractActionHandler<QueryParams> {
|
||||||
|
|
||||||
|
format = Format.Xml;
|
||||||
|
action = Action.StsGetCallerIdentity;
|
||||||
|
validator = Joi.object<QueryParams, true>();
|
||||||
|
|
||||||
|
protected async handle(queryParams: QueryParams, awsProperties: AwsProperties) {
|
||||||
|
return {
|
||||||
|
"UserId": "AIDASAMPLEUSERID",
|
||||||
|
"Account": awsProperties.accountId,
|
||||||
|
"Arn": `arn:aws:iam::${awsProperties.accountId}:user/DevAdmin`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { AbstractActionHandler } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
|
||||||
|
export type StsHandlers = Record<Action, AbstractActionHandler>;
|
||||||
|
export const StsHandlers = Symbol.for('STS_HANDLERS');
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { Format } from '../abstract-action.handler';
|
||||||
|
import { Action } from '../action.enum';
|
||||||
|
import { PrismaModule } from '../_prisma/prisma.module';
|
||||||
|
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 { GetCallerIdentityHandler } from './get-caller-identity.handler';
|
||||||
|
import { StsHandlers } from './sts.constants';
|
||||||
|
|
||||||
|
const handlers = [
|
||||||
|
GetCallerIdentityHandler,
|
||||||
|
]
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
Action.StsAssumeRole,
|
||||||
|
Action.StsAssumeRoleWithSaml,
|
||||||
|
Action.StsAssumeRoleWithWebIdentity,
|
||||||
|
Action.StsAssumeRoot,
|
||||||
|
Action.StsDecodeAuthorizationMessage,
|
||||||
|
Action.StsGetAccessKeyInfo,
|
||||||
|
Action.StsGetCallerIdentity,
|
||||||
|
Action.StsGetFederationToken,
|
||||||
|
Action.StsGetSessionToken,
|
||||||
|
]
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
AwsSharedEntitiesModule,
|
||||||
|
PrismaModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
...handlers,
|
||||||
|
ExistingActionHandlersProvider(handlers),
|
||||||
|
DefaultActionHandlerProvider(StsHandlers, Format.Xml, actions),
|
||||||
|
],
|
||||||
|
exports: [StsHandlers]
|
||||||
|
})
|
||||||
|
export class StsModule {}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { InvalidArnException } from "../aws-shared-entities/aws-exceptions";
|
||||||
|
|
||||||
export type ArnParts = {
|
export type ArnParts = {
|
||||||
service: string;
|
service: string;
|
||||||
region: string;
|
region: string;
|
||||||
|
|
@ -7,7 +9,7 @@ export type ArnParts = {
|
||||||
|
|
||||||
export const breakdownArn = (arn: string): ArnParts => {
|
export const breakdownArn = (arn: string): ArnParts => {
|
||||||
if (!arn.startsWith('arn')) {
|
if (!arn.startsWith('arn')) {
|
||||||
throw new Error('Invalid arn');
|
throw new InvalidArnException('Invalid arn');
|
||||||
}
|
}
|
||||||
|
|
||||||
const [_arn, _aws, service, region, accountId, ...identifierData] = arn.split(':');
|
const [_arn, _aws, service, region, accountId, ...identifierData] = arn.split(':');
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"exclude": ["node_modules", "dist", "**/*spec.ts"]
|
"exclude": ["node_modules", "dist", "src/iam"]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue