feat: added external kms router but not connected with the server yet

This commit is contained in:
=
2024-07-10 12:48:35 +05:30
parent 7ca7a95070
commit 2e7baf8c89
8 changed files with 1605 additions and 1 deletions
@@ -0,0 +1,102 @@
import { CreateKeyCommand, DecryptCommand, DescribeKeyCommand, EncryptCommand, KMSClient } from "@aws-sdk/client-kms";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { randomUUID } from "crypto";
import { ExternalKmsAwsSchema, KmsAwsCredentialType, TExternalKmsAwsSchema, TExternalKmsProviderFns } from "./model";
const getAwsKmsClient = async (providerInputs: TExternalKmsAwsSchema) => {
if (providerInputs.credential.type === KmsAwsCredentialType.AssumeRole) {
const awsCredential = providerInputs.credential.data;
const stsClient = new STSClient({
region: providerInputs.awsRegion
});
const command = new AssumeRoleCommand({
RoleArn: awsCredential.assumeRoleArn,
RoleSessionName: `infisical-kms-${randomUUID()}`,
DurationSeconds: 900, // 15mins
ExternalId: awsCredential.externalId
});
const response = await stsClient.send(command);
if (!response.Credentials?.AccessKeyId || !response.Credentials?.SecretAccessKey)
throw new Error("Failed to assume role");
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretAccessKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration
}
});
return kmsClient;
}
const awsCredential = providerInputs.credential.data;
const kmsClient = new KMSClient({
region: providerInputs.awsRegion,
credentials: {
accessKeyId: awsCredential.accessKey,
secretAccessKey: awsCredential.secretKey
}
});
return kmsClient;
};
type AwsKmsProviderArgs = {
inputs: unknown;
};
type TAwsKmsProviderFactoryReturn = TExternalKmsProviderFns & {
generateInputKmsKey: () => Promise<TExternalKmsAwsSchema>;
};
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
const providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
const awsClient = await getAwsKmsClient(providerInputs);
const generateInputKmsKey = async () => {
if (providerInputs.kmsKeyId) return providerInputs;
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
const kmsKey = await awsClient.send(command);
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
return { ...providerInputs, kmsKeyId: kmsKey.KeyMetadata?.KeyId };
};
const validateConnection = async () => {
const command = new DescribeKeyCommand({
KeyId: providerInputs.kmsKeyId
});
const isConnected = await awsClient.send(command).then(() => true);
return isConnected;
};
const encrypt = async (data: Buffer) => {
const command = new EncryptCommand({
KeyId: providerInputs.kmsKeyId,
Plaintext: data
});
const encryptionCommand = await awsClient.send(command);
if (!encryptionCommand.CiphertextBlob) throw new Error("encryption failed");
return { encryptedBlob: Buffer.from(encryptionCommand.CiphertextBlob) };
};
const decrypt = async (encryptedBlob: Buffer) => {
const command = new DecryptCommand({
KeyId: providerInputs.kmsKeyId,
CiphertextBlob: encryptedBlob
});
const decryptionCommand = await awsClient.send(command);
if (!decryptionCommand.Plaintext) throw new Error("decryption failed");
return { data: Buffer.from(decryptionCommand.Plaintext) };
};
return {
generateInputKmsKey,
validateConnection,
encrypt,
decrypt
};
};
@@ -0,0 +1,61 @@
import { z } from "zod";
export enum KmsProviders {
Aws = "aws"
}
export enum KmsAwsCredentialType {
AssumeRole = "assume-role",
AccessKey = "access-key"
}
export const ExternalKmsAwsSchema = z.object({
credential: z
.discriminatedUnion("type", [
z.object({
type: z.literal(KmsAwsCredentialType.AccessKey),
data: z.object({
accessKey: z.string().trim().min(1).describe("AWS user account access key"),
secretKey: z.string().trim().min(1).describe("AWS user account secret key")
})
}),
z.object({
type: z.literal(KmsAwsCredentialType.AssumeRole),
data: z.object({
assumeRoleArn: z.string().trim().min(1).describe("AWS user role to be assumed by infisical"),
externalId: z
.string()
.trim()
.min(1)
.optional()
.describe("AWS assume role external id for furthur security in authentication")
})
})
])
.describe("AWS credential information to connect"),
awsRegion: z.string().min(1).trim().describe("AWS region to connect"),
kmsKeyId: z
.string()
.trim()
.optional()
.describe("A pre existing AWS KMS key id to be used for encryption. If not provided a kms key will be generated.")
});
export type TExternalKmsAwsSchema = z.infer<typeof ExternalKmsAwsSchema>;
// The root schema of the JSON
export const ExternalKmsInputSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema })
]);
export type TExternalKmsInputSchema = z.infer<typeof ExternalKmsInputSchema>;
export const ExternalKmsInputUpdateSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(KmsProviders.Aws), inputs: ExternalKmsAwsSchema.partial() })
]);
export type TExternalKmsInputUpdateSchema = z.infer<typeof ExternalKmsInputUpdateSchema>;
// generic function shared by all provider
export type TExternalKmsProviderFns = {
validateConnection: () => Promise<boolean>;
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
};