mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(server): added aws iam secret rotation function
This commit is contained in:
846
backend/package-lock.json
generated
846
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -70,6 +70,7 @@
|
|||||||
"vitest": "^1.2.2"
|
"vitest": "^1.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@aws-sdk/client-iam": "^3.525.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
"@aws-sdk/client-secrets-manager": "^3.504.0",
|
||||||
"@casl/ability": "^6.5.0",
|
"@casl/ability": "^6.5.0",
|
||||||
"@fastify/cookie": "^9.3.1",
|
"@fastify/cookie": "^9.3.1",
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
CreateAccessKeyCommand,
|
||||||
|
DeleteAccessKeyCommand,
|
||||||
|
GetAccessKeyLastUsedCommand,
|
||||||
|
IAMClient
|
||||||
|
} from "@aws-sdk/client-iam";
|
||||||
|
|
||||||
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
import { SecretKeyEncoding, SecretType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import {
|
import {
|
||||||
@@ -18,7 +25,12 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
|||||||
|
|
||||||
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "../secret-rotation-dal";
|
||||||
import { rotationTemplates } from "../templates";
|
import { rotationTemplates } from "../templates";
|
||||||
import { TDbProviderClients, TProviderFunctionTypes, TSecretRotationProviderTemplate } from "../templates/types";
|
import {
|
||||||
|
TAwsProviderSystems,
|
||||||
|
TDbProviderClients,
|
||||||
|
TProviderFunctionTypes,
|
||||||
|
TSecretRotationProviderTemplate
|
||||||
|
} from "../templates/types";
|
||||||
import {
|
import {
|
||||||
getDbSetQuery,
|
getDbSetQuery,
|
||||||
secretRotationDbFn,
|
secretRotationDbFn,
|
||||||
@@ -127,7 +139,10 @@ export const secretRotationQueueFactory = ({
|
|||||||
internal: {}
|
internal: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
// when its a database we keep cycling the variables accordingly
|
/* Rotation Function For Database
|
||||||
|
* A database like sql cannot have multiple password for a user
|
||||||
|
* thus we ask users to create two users with required permission and then we keep cycling between these two db users
|
||||||
|
*/
|
||||||
if (provider.template.type === TProviderFunctionTypes.DB) {
|
if (provider.template.type === TProviderFunctionTypes.DB) {
|
||||||
const lastCred = variables.creds.at(-1);
|
const lastCred = variables.creds.at(-1);
|
||||||
if (lastCred && variables.creds.length === 1) {
|
if (lastCred && variables.creds.length === 1) {
|
||||||
@@ -170,6 +185,65 @@ export const secretRotationQueueFactory = ({
|
|||||||
if (variables.creds.length === 2) variables.creds.pop();
|
if (variables.creds.length === 2) variables.creds.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rotation Function For AWS Services
|
||||||
|
* Due to complexity in AWS Authorization hashing signature process we keep it as seperate entity instead of http template mode
|
||||||
|
* We first delete old key before creating a new one because aws iam has a quota limit of 2 keys
|
||||||
|
* */
|
||||||
|
if (provider.template.type === TProviderFunctionTypes.AWS) {
|
||||||
|
if (provider.template.client === TAwsProviderSystems.IAM) {
|
||||||
|
const client = new IAMClient({
|
||||||
|
region: newCredential.inputs.manager_user_aws_region as string,
|
||||||
|
credentials: {
|
||||||
|
accessKeyId: newCredential.inputs.manager_user_access_key as string,
|
||||||
|
secretAccessKey: newCredential.inputs.manager_user_secret_key as string
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const iamUserName = newCredential.inputs.iam_username as string;
|
||||||
|
|
||||||
|
if (variables.creds.length === 2) {
|
||||||
|
const deleteCycleCredential = variables.creds.pop();
|
||||||
|
if (deleteCycleCredential) {
|
||||||
|
const deletedIamAccessKey = await client.send(
|
||||||
|
new DeleteAccessKeyCommand({
|
||||||
|
UserName: iamUserName,
|
||||||
|
AccessKeyId: deleteCycleCredential.outputs.iam_user_access_key as string
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!deletedIamAccessKey?.$metadata?.httpStatusCode ||
|
||||||
|
deletedIamAccessKey?.$metadata?.httpStatusCode > 300
|
||||||
|
) {
|
||||||
|
throw new DisableRotationErrors({
|
||||||
|
message: "Failed to delete aws iam access key. Check managed iam user policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIamAccessKey = await client.send(new CreateAccessKeyCommand({ UserName: iamUserName }));
|
||||||
|
if (!newIamAccessKey.AccessKey)
|
||||||
|
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||||
|
|
||||||
|
// test
|
||||||
|
const testAccessKey = await client.send(
|
||||||
|
new GetAccessKeyLastUsedCommand({ AccessKeyId: newIamAccessKey.AccessKey.AccessKeyId })
|
||||||
|
);
|
||||||
|
if (testAccessKey?.UserName !== iamUserName)
|
||||||
|
throw new DisableRotationErrors({ message: "Failed to create access key. Check managed iam user policy" });
|
||||||
|
|
||||||
|
newCredential.outputs.iam_user_access_key = newIamAccessKey.AccessKey.AccessKeyId;
|
||||||
|
newCredential.outputs.iam_user_secret_key = newIamAccessKey.AccessKey.SecretAccessKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotation function of HTTP infisical template
|
||||||
|
* This is a generic http based template system for rotation
|
||||||
|
* we use this for sendgrid and for custom secret rotation
|
||||||
|
* This will ensure user provided rotation is easier to make
|
||||||
|
* */
|
||||||
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
if (provider.template.type === TProviderFunctionTypes.HTTP) {
|
||||||
if (provider.template.functions.set?.pre) {
|
if (provider.template.functions.set?.pre) {
|
||||||
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
|
||||||
@@ -185,6 +259,9 @@ export const secretRotationQueueFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// insert the new variables to start
|
||||||
|
// encrypt the data - save it
|
||||||
variables.creds.unshift({
|
variables.creds.unshift({
|
||||||
outputs: newCredential.outputs,
|
outputs: newCredential.outputs,
|
||||||
internal: newCredential.internal
|
internal: newCredential.internal
|
||||||
@@ -200,6 +277,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
key
|
key
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
// map the final values to output keys in the board
|
||||||
await secretRotationDAL.transaction(async (tx) => {
|
await secretRotationDAL.transaction(async (tx) => {
|
||||||
await secretRotationDAL.updateById(
|
await secretRotationDAL.updateById(
|
||||||
rotationId,
|
rotationId,
|
||||||
|
|||||||
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
21
backend/src/ee/services/secret-rotation/templates/aws-iam.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { TAwsProviderSystems, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const AWS_IAM_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.AWS as const,
|
||||||
|
client: TAwsProviderSystems.IAM,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
manager_user_access_key: { type: "string" as const },
|
||||||
|
manager_user_secret_key: { type: "string" as const },
|
||||||
|
manager_user_aws_region: { type: "string" as const },
|
||||||
|
iam_username: { type: "string" as const }
|
||||||
|
},
|
||||||
|
required: ["manage_user_access_key", "manage_user_secret_key", "iam_username"],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
iam_user_access_key: { type: "string" },
|
||||||
|
iam_user_secret_key: { type: "string" }
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AWS_IAM_TEMPLATE } from "./aws-iam";
|
||||||
import { MYSQL_TEMPLATE } from "./mysql";
|
import { MYSQL_TEMPLATE } from "./mysql";
|
||||||
import { POSTGRES_TEMPLATE } from "./postgres";
|
import { POSTGRES_TEMPLATE } from "./postgres";
|
||||||
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
||||||
@@ -24,5 +25,12 @@ export const rotationTemplates: TSecretRotationProviderTemplate[] = [
|
|||||||
image: "mysql.png",
|
image: "mysql.png",
|
||||||
description: "Rotate MySQL@7/MariaDB user credentials",
|
description: "Rotate MySQL@7/MariaDB user credentials",
|
||||||
template: MYSQL_TEMPLATE
|
template: MYSQL_TEMPLATE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-iam",
|
||||||
|
title: "AWS IAM",
|
||||||
|
image: "aws-iam.svg",
|
||||||
|
description: "Rotate AWS IAM User credentials",
|
||||||
|
template: AWS_IAM_TEMPLATE
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum TProviderFunctionTypes {
|
export enum TProviderFunctionTypes {
|
||||||
HTTP = "http",
|
HTTP = "http",
|
||||||
DB = "database"
|
DB = "database",
|
||||||
|
AWS = "aws"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TDbProviderClients {
|
export enum TDbProviderClients {
|
||||||
@@ -10,6 +11,10 @@ export enum TDbProviderClients {
|
|||||||
MySql = "mysql"
|
MySql = "mysql"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TAwsProviderSystems {
|
||||||
|
IAM = "iam"
|
||||||
|
}
|
||||||
|
|
||||||
export enum TAssignOp {
|
export enum TAssignOp {
|
||||||
Direct = "direct",
|
Direct = "direct",
|
||||||
JmesPath = "jmesopath"
|
JmesPath = "jmesopath"
|
||||||
@@ -42,7 +47,7 @@ export type TSecretRotationProviderTemplate = {
|
|||||||
title: string;
|
title: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
template: THttpProviderTemplate | TDbProviderTemplate;
|
template: THttpProviderTemplate | TDbProviderTemplate | TAwsProviderTemplate;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type THttpProviderTemplate = {
|
export type THttpProviderTemplate = {
|
||||||
@@ -70,3 +75,14 @@ export type TDbProviderTemplate = {
|
|||||||
};
|
};
|
||||||
outputs: Record<string, unknown>;
|
outputs: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TAwsProviderTemplate = {
|
||||||
|
type: TProviderFunctionTypes.AWS;
|
||||||
|
client: TAwsProviderSystems;
|
||||||
|
inputs: {
|
||||||
|
type: "object";
|
||||||
|
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
|
||||||
|
required?: string[];
|
||||||
|
};
|
||||||
|
outputs: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user