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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/joi": "^17.2.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 {
|
||||
provider = "sqlite"
|
||||
url = ":memory:"
|
||||
url = "file:local-aws-state.sqlite"
|
||||
}
|
||||
|
||||
model Attribute {
|
||||
|
|
@ -24,6 +24,26 @@ model Audit {
|
|||
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 {
|
||||
versionId String @id
|
||||
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 {
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
const tables = await this.$queryRawUnsafe('.tables');
|
||||
console.log({ tables })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -301,4 +301,15 @@ export enum Action {
|
|||
SqsSetQueueAttributes = 'SetQueueAttributes',
|
||||
SqsTagQueue = 'TagQueue',
|
||||
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 { 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 { BadRequestException, Body, Controller, Headers, HttpCode, Inject, Post, Req, UseInterceptors } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { CommonConfig } from './config/common-config.interface';
|
||||
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()
|
||||
export class AppController {
|
||||
|
|
@ -30,24 +36,24 @@ export class AppController {
|
|||
const lowerCasedHeaders = Object.keys(headers).reduce((o, k) => {
|
||||
o[k.toLocaleLowerCase()] = headers[k];
|
||||
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 { error: actionError } = Joi.object({
|
||||
[actionKey]: Joi.string().valid(...Object.values(Action)).required(),
|
||||
}).validate(queryParams, { allowUnknown: true });
|
||||
|
||||
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 { error: validatorError, value: validQueryParams } = handler.validator.validate(queryParams, { allowUnknown: true, abortEarly: false });
|
||||
|
||||
if (validatorError) {
|
||||
throw new BadRequestException(validatorError.message, { cause: validatorError });
|
||||
throw new ValidationError(validatorError.message);
|
||||
}
|
||||
|
||||
const awsProperties = {
|
||||
|
|
|
|||
|
|
@ -3,11 +3,9 @@ import { ConfigModule } from '@nestjs/config';
|
|||
|
||||
import { ActionHandlers } from './app.constants';
|
||||
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 localConfig from './config/local.config';
|
||||
import { IAMHandlers } from './iam/iam.constants';
|
||||
import { IamModule } from './iam/iam.module';
|
||||
import { KMSHandlers } from './kms/kms.constants';
|
||||
import { KmsModule } from './kms/kms.module';
|
||||
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 { SqsHandlers } from './sqs/sqs.constants';
|
||||
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({
|
||||
imports: [
|
||||
|
|
@ -23,12 +24,13 @@ import { SqsModule } from './sqs/sqs.module';
|
|||
load: [localConfig],
|
||||
isGlobal: true,
|
||||
}),
|
||||
IamModule,
|
||||
PrismaModule,
|
||||
AwsSharedEntitiesModule,
|
||||
KmsModule,
|
||||
SecretsManagerModule,
|
||||
SnsModule,
|
||||
SqsModule,
|
||||
AwsSharedEntitiesModule,
|
||||
StsModule,
|
||||
],
|
||||
controllers: [
|
||||
AppController,
|
||||
|
|
@ -39,11 +41,11 @@ import { SqsModule } from './sqs/sqs.module';
|
|||
provide: ActionHandlers,
|
||||
useFactory: (...args) => args.reduce((m, hs) => ({ ...m, ...hs }), {}),
|
||||
inject: [
|
||||
KMSHandlers,
|
||||
SecretsManagerHandlers,
|
||||
SnsHandlers,
|
||||
SqsHandlers,
|
||||
SecretsManagerHandlers,
|
||||
KMSHandlers,
|
||||
IAMHandlers,
|
||||
StsHandlers,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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 { Action } from '../action.enum';
|
||||
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 { KmsService } from './kms.service';
|
||||
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||
|
||||
type QueryParams = {
|
||||
GrantTokens?: string[];
|
||||
KeyId: string;
|
||||
}
|
||||
|
||||
|
|
@ -17,8 +16,6 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
|||
|
||||
constructor(
|
||||
private readonly kmsService: KmsService,
|
||||
@InjectRepository(KmsKey)
|
||||
private readonly keyRepo: Repository<KmsKey>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
|
@ -27,6 +24,7 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
|||
action = Action.KmsDescribeKey;
|
||||
validator = Joi.object<QueryParams, true>({
|
||||
KeyId: Joi.string().required(),
|
||||
GrantTokens: Joi.array().items(Joi.string()),
|
||||
});
|
||||
|
||||
protected async handle({ KeyId }: QueryParams, awsProperties: AwsProperties) {
|
||||
|
|
@ -38,16 +36,17 @@ export class DescribeKeyHandler extends AbstractActionHandler<QueryParams> {
|
|||
identifier: KeyId,
|
||||
};
|
||||
const [ type, pk ] = searchable.identifier.split('/');
|
||||
const keyId: Promise<string> = type === 'key' ?
|
||||
Promise.resolve(pk) :
|
||||
this.kmsService.findKeyIdFromAlias(pk, searchable);
|
||||
const keyId = await (type === 'key' ? Promise.resolve(pk) : this.kmsService.findKeyIdFromAlias(pk, searchable));
|
||||
|
||||
if (!keyId) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
const keyRecord = await this.keyRepo.findOne({ where: {
|
||||
id: await keyId,
|
||||
region: searchable.region,
|
||||
accountId: searchable.accountId,
|
||||
}});
|
||||
const keyRecord = await this.kmsService.findOneById(keyId);
|
||||
|
||||
if (!keyRecord) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
return {
|
||||
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' })
|
||||
export class KmsKeyAlias extends BaseEntity {
|
||||
// name: string;
|
||||
// targetKeyId: string;
|
||||
// accountId: string;
|
||||
// region: string;
|
||||
|
||||
@PrimaryColumn()
|
||||
name: string;
|
||||
|
||||
@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}`;
|
||||
}
|
||||
// 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 KeyUsage = 'SIGN_VERIFY' | 'ENCRYPT_DECRYPT' | 'GENERATE_VERIFY_MAC';
|
||||
|
||||
@Entity({ name: 'kms_key'})
|
||||
export class KmsKey extends BaseEntity {
|
||||
export class KmsKey implements PrismaKmsKey {
|
||||
|
||||
@PrimaryColumn()
|
||||
id: string;
|
||||
|
||||
@Column({ name: 'usage' })
|
||||
usage: KeyUsage;
|
||||
|
||||
@Column({ name: 'description' })
|
||||
description: string;
|
||||
|
||||
@Column({ name: 'key_spec' })
|
||||
keySpec: KeySpec;
|
||||
|
||||
@Column({ name: 'key' })
|
||||
key: string;
|
||||
|
||||
@Column({ name: 'account_id', nullable: false })
|
||||
accountId: string;
|
||||
|
||||
@Column({ name: 'region', nullable: false })
|
||||
region: string;
|
||||
createdAt: Date;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: string;
|
||||
constructor(p: PrismaKmsKey) {
|
||||
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() {
|
||||
return `arn:aws:kms:${this.region}:${this.accountId}:key/${this.id}`;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
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 { 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 { 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 = [
|
||||
CreateAliasHandler,
|
||||
DescribeKeyHandler,
|
||||
GetPublicKeyHandler,
|
||||
]
|
||||
|
||||
const actions = [
|
||||
|
|
@ -74,8 +69,8 @@ const actions = [
|
|||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([KmsKey, KmsKeyAlias]),
|
||||
AwsSharedEntitiesModule,
|
||||
PrismaModule,
|
||||
],
|
||||
providers: [
|
||||
...handlers,
|
||||
|
|
|
|||
|
|
@ -1,22 +1,30 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../_prisma/prisma.service';
|
||||
import { ArnParts } from '../util/breakdown-arn';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { KmsKeyAlias } from './kms-key-alias.entity';
|
||||
import { Repository } from 'typeorm';
|
||||
import { KmsKey } from './kms-key.entity';
|
||||
|
||||
@Injectable()
|
||||
export class KmsService {
|
||||
constructor(
|
||||
@InjectRepository(KmsKeyAlias)
|
||||
private readonly aliasRepo: Repository<KmsKeyAlias>,
|
||||
private readonly prismaService: PrismaService,
|
||||
) {}
|
||||
|
||||
async findKeyIdFromAlias(alias: string, arn: ArnParts): Promise<string> {
|
||||
const record = await this.aliasRepo.findOne({ where: {
|
||||
async findOneById(id: string): Promise<KmsKey | null> {
|
||||
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,
|
||||
accountId: arn.accountId,
|
||||
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 { CommonConfig } from './config/common-config.interface';
|
||||
import { AwsExceptionFilter } from './_context/exception.filter';
|
||||
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
(async () => {
|
||||
|
||||
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'}));
|
||||
|
||||
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 { PrismaService } from '../_prisma/prisma.service';
|
||||
import { ArnUtil } from '../util/arn-util.static';
|
||||
import { NotFoundException } from '../aws-shared-entities/aws-exceptions';
|
||||
|
||||
type QueryParams = {
|
||||
SubscriptionArn: string;
|
||||
|
|
@ -34,7 +35,7 @@ export class UnsubscribeHandler extends AbstractActionHandler<QueryParams> {
|
|||
const subscription = await this.prismaService.snsTopicSubscription.findFirst({ where: { id } });
|
||||
|
||||
if (!subscription) {
|
||||
return;
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
const arn = ArnUtil.fromTopicSub(subscription);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { SqsQueue } from './sqs-queue.entity';
|
|||
|
||||
type QueryParams = {
|
||||
QueueUrl: string;
|
||||
}
|
||||
} & Record<string, string>;
|
||||
|
||||
@Injectable()
|
||||
export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams> {
|
||||
|
|
@ -34,7 +34,7 @@ export class DeleteMessageBatchHandler extends AbstractActionHandler<QueryParams
|
|||
for (const header of Object.keys(params)) {
|
||||
if (header.includes('DeleteMessageBatchRequestEntry') && header.includes('ReceiptHandle')) {
|
||||
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) {
|
||||
|
||||
const [accountId, name] = SqsQueue.tryGetAccountIdAndNameFromPathOrArn(QueueUrl);
|
||||
await this.sqsQueueEntryService.deleteMessage(accountId, name, ReceiptHandle);
|
||||
await this.sqsQueueEntryService.deleteMessage(ReceiptHandle);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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';
|
||||
|
|
@ -14,9 +14,9 @@ import { PurgeQueueHandler } from './purge-queue.handler';
|
|||
import { ReceiveMessageHandler } from './receive-message.handler';
|
||||
import { SetQueueAttributesHandler } from './set-queue-attributes.handler';
|
||||
import { SqsQueueEntryService } from './sqs-queue-entry.service';
|
||||
import { SqsQueue } from './sqs-queue.entity';
|
||||
import { SqsHandlers } from './sqs.constants';
|
||||
import { DeleteMessageBatchHandler } from './delete-message-batch.handler';
|
||||
import { PrismaModule } from '../_prisma/prisma.module';
|
||||
|
||||
const handlers = [
|
||||
CreateQueueHandler,
|
||||
|
|
@ -55,8 +55,8 @@ const actions = [
|
|||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([SqsQueue]),
|
||||
AwsSharedEntitiesModule,
|
||||
PrismaModule,
|
||||
],
|
||||
providers: [
|
||||
...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 = {
|
||||
service: string;
|
||||
region: string;
|
||||
|
|
@ -7,7 +9,7 @@ export type ArnParts = {
|
|||
|
||||
export const breakdownArn = (arn: string): ArnParts => {
|
||||
if (!arn.startsWith('arn')) {
|
||||
throw new Error('Invalid arn');
|
||||
throw new InvalidArnException('Invalid arn');
|
||||
}
|
||||
|
||||
const [_arn, _aws, service, region, accountId, ...identifierData] = arn.split(':');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "dist", "**/*spec.ts"]
|
||||
"exclude": ["node_modules", "dist", "src/iam"]
|
||||
}
|
||||
Loading…
Reference in New Issue