Merge pull request #3086 from Infisical/aws-secrets-manager-sync
Feature: AWS Secrets Manager Sync
@@ -1719,6 +1719,12 @@ export const SecretSyncs = {
|
|||||||
REGION: "The AWS region to sync secrets to.",
|
REGION: "The AWS region to sync secrets to.",
|
||||||
PATH: "The Parameter Store path to sync secrets to."
|
PATH: "The Parameter Store path to sync secrets to."
|
||||||
},
|
},
|
||||||
|
AWS_SECRETS_MANAGER: {
|
||||||
|
REGION: "The AWS region to sync secrets to.",
|
||||||
|
MAPPING_BEHAVIOR:
|
||||||
|
"How secrets from Infisical should be mapped to AWS Secrets Manager; one-to-one or many-to-one.",
|
||||||
|
SECRET_NAME: "The secret name in AWS Secrets Manager to sync to when using mapping behavior many-to-one."
|
||||||
|
},
|
||||||
GITHUB: {
|
GITHUB: {
|
||||||
ORG: "The name of the GitHub organization.",
|
ORG: "The name of the GitHub organization.",
|
||||||
OWNER: "The name of the GitHub account owner of the repository.",
|
OWNER: "The name of the GitHub account owner of the repository.",
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
AwsSecretsManagerSyncSchema,
|
||||||
|
CreateAwsSecretsManagerSyncSchema,
|
||||||
|
UpdateAwsSecretsManagerSyncSchema
|
||||||
|
} from "@app/services/secret-sync/aws-secrets-manager";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerAwsSecretsManagerSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.AWSSecretsManager,
|
||||||
|
server,
|
||||||
|
responseSchema: AwsSecretsManagerSyncSchema,
|
||||||
|
createSchema: CreateAwsSecretsManagerSyncSchema,
|
||||||
|
updateSchema: UpdateAwsSecretsManagerSyncSchema
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
||||||
|
import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-router";
|
||||||
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ export * from "./secret-sync-router";
|
|||||||
|
|
||||||
export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: FastifyZodProvider) => Promise<void>> = {
|
export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: FastifyZodProvider) => Promise<void>> = {
|
||||||
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
||||||
|
[SecretSync.AWSSecretsManager]: registerAwsSecretsManagerSyncRouter,
|
||||||
[SecretSync.GitHub]: registerGitHubSyncRouter,
|
[SecretSync.GitHub]: registerGitHubSyncRouter,
|
||||||
[SecretSync.GCPSecretManager]: registerGcpSyncRouter
|
[SecretSync.GCPSecretManager]: registerGcpSyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,17 +9,23 @@ import {
|
|||||||
AwsParameterStoreSyncListItemSchema,
|
AwsParameterStoreSyncListItemSchema,
|
||||||
AwsParameterStoreSyncSchema
|
AwsParameterStoreSyncSchema
|
||||||
} from "@app/services/secret-sync/aws-parameter-store";
|
} from "@app/services/secret-sync/aws-parameter-store";
|
||||||
|
import {
|
||||||
|
AwsSecretsManagerSyncListItemSchema,
|
||||||
|
AwsSecretsManagerSyncSchema
|
||||||
|
} from "@app/services/secret-sync/aws-secrets-manager";
|
||||||
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
|
|
||||||
const SecretSyncSchema = z.discriminatedUnion("destination", [
|
const SecretSyncSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncSchema,
|
AwsParameterStoreSyncSchema,
|
||||||
|
AwsSecretsManagerSyncSchema,
|
||||||
GitHubSyncSchema,
|
GitHubSyncSchema,
|
||||||
GcpSyncSchema
|
GcpSyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncListItemSchema,
|
AwsParameterStoreSyncListItemSchema,
|
||||||
|
AwsSecretsManagerSyncListItemSchema,
|
||||||
GitHubSyncListItemSchema,
|
GitHubSyncListItemSchema,
|
||||||
GcpSyncListItemSchema
|
GcpSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ const getParametersByPath = async (ssm: AWS.SSM, path: string): Promise<TAWSPara
|
|||||||
attempt += 1;
|
attempt += 1;
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await sleep();
|
await sleep();
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const AWS_SECRETS_MANAGER_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "AWS Secrets Manager",
|
||||||
|
destination: SecretSync.AWSSecretsManager,
|
||||||
|
connection: AppConnection.AWS,
|
||||||
|
canImportSecrets: true
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export enum AwsSecretsManagerSyncMappingBehavior {
|
||||||
|
OneToOne = "one-to-one",
|
||||||
|
ManyToOne = "many-to-one"
|
||||||
|
}
|
||||||
@@ -0,0 +1,352 @@
|
|||||||
|
import {
|
||||||
|
BatchGetSecretValueCommand,
|
||||||
|
CreateSecretCommand,
|
||||||
|
CreateSecretCommandInput,
|
||||||
|
DeleteSecretCommand,
|
||||||
|
DeleteSecretResponse,
|
||||||
|
ListSecretsCommand,
|
||||||
|
SecretsManagerClient,
|
||||||
|
UpdateSecretCommand,
|
||||||
|
UpdateSecretCommandInput
|
||||||
|
} from "@aws-sdk/client-secrets-manager";
|
||||||
|
import { AWSError } from "aws-sdk";
|
||||||
|
import { CreateSecretResponse, SecretListEntry, SecretValueEntry } from "aws-sdk/clients/secretsmanager";
|
||||||
|
|
||||||
|
import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns";
|
||||||
|
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { TAwsSecretsManagerSyncWithCredentials } from "./aws-secrets-manager-sync-types";
|
||||||
|
|
||||||
|
type TAwsSecretsRecord = Record<string, SecretListEntry>;
|
||||||
|
type TAwsSecretValuesRecord = Record<string, SecretValueEntry>;
|
||||||
|
|
||||||
|
const MAX_RETRIES = 5;
|
||||||
|
const BATCH_SIZE = 20;
|
||||||
|
|
||||||
|
const getSecretsManagerClient = async (secretSync: TAwsSecretsManagerSyncWithCredentials) => {
|
||||||
|
const { destinationConfig, connection } = secretSync;
|
||||||
|
|
||||||
|
const config = await getAwsConnectionConfig(connection, destinationConfig.region);
|
||||||
|
|
||||||
|
const secretsManagerClient = new SecretsManagerClient({
|
||||||
|
region: config.region,
|
||||||
|
credentials: config.credentials!
|
||||||
|
});
|
||||||
|
|
||||||
|
return secretsManagerClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sleep = async () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSecretsRecord = async (client: SecretsManagerClient): Promise<TAwsSecretsRecord> => {
|
||||||
|
const awsSecretsRecord: TAwsSecretsRecord = {};
|
||||||
|
let hasNext = true;
|
||||||
|
let nextToken: string | undefined;
|
||||||
|
let attempt = 0;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const output = await client.send(new ListSecretsCommand({ NextToken: nextToken }));
|
||||||
|
|
||||||
|
attempt = 0;
|
||||||
|
|
||||||
|
if (output.SecretList) {
|
||||||
|
output.SecretList.forEach((secretEntry) => {
|
||||||
|
if (secretEntry.Name) {
|
||||||
|
awsSecretsRecord[secretEntry.Name] = secretEntry;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext = Boolean(output.NextToken);
|
||||||
|
nextToken = output.NextToken;
|
||||||
|
} catch (e) {
|
||||||
|
if ((e as AWSError).code === "ThrottlingException" && attempt < MAX_RETRIES) {
|
||||||
|
attempt += 1;
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sleep();
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return awsSecretsRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSecretValuesRecord = async (
|
||||||
|
client: SecretsManagerClient,
|
||||||
|
awsSecretsRecord: TAwsSecretsRecord
|
||||||
|
): Promise<TAwsSecretValuesRecord> => {
|
||||||
|
const awsSecretValuesRecord: TAwsSecretValuesRecord = {};
|
||||||
|
let attempt = 0;
|
||||||
|
|
||||||
|
const secretIdList = Object.keys(awsSecretsRecord);
|
||||||
|
|
||||||
|
for (let i = 0; i < secretIdList.length; i += BATCH_SIZE) {
|
||||||
|
const batchSecretIds = secretIdList.slice(i, i + BATCH_SIZE);
|
||||||
|
let hasNext = true;
|
||||||
|
let nextToken: string | undefined;
|
||||||
|
|
||||||
|
while (hasNext) {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const output = await client.send(
|
||||||
|
new BatchGetSecretValueCommand({
|
||||||
|
SecretIdList: batchSecretIds,
|
||||||
|
NextToken: nextToken
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
attempt = 0;
|
||||||
|
|
||||||
|
if (output.SecretValues) {
|
||||||
|
output.SecretValues.forEach((secretValueEntry) => {
|
||||||
|
if (secretValueEntry.Name) {
|
||||||
|
awsSecretValuesRecord[secretValueEntry.Name] = secretValueEntry;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
hasNext = Boolean(output.NextToken);
|
||||||
|
nextToken = output.NextToken;
|
||||||
|
} catch (e) {
|
||||||
|
if ((e as AWSError).code === "ThrottlingException" && attempt < MAX_RETRIES) {
|
||||||
|
attempt += 1;
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await sleep();
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return awsSecretValuesRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSecret = async (
|
||||||
|
client: SecretsManagerClient,
|
||||||
|
input: CreateSecretCommandInput,
|
||||||
|
attempt = 0
|
||||||
|
): Promise<CreateSecretResponse> => {
|
||||||
|
try {
|
||||||
|
return await client.send(new CreateSecretCommand(input));
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as AWSError).code === "ThrottlingException" && attempt < MAX_RETRIES) {
|
||||||
|
await sleep();
|
||||||
|
|
||||||
|
// retry
|
||||||
|
return createSecret(client, input, attempt + 1);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSecret = async (
|
||||||
|
client: SecretsManagerClient,
|
||||||
|
input: UpdateSecretCommandInput,
|
||||||
|
attempt = 0
|
||||||
|
): Promise<CreateSecretResponse> => {
|
||||||
|
try {
|
||||||
|
return await client.send(new UpdateSecretCommand(input));
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as AWSError).code === "ThrottlingException" && attempt < MAX_RETRIES) {
|
||||||
|
await sleep();
|
||||||
|
|
||||||
|
// retry
|
||||||
|
return updateSecret(client, input, attempt + 1);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSecret = async (
|
||||||
|
client: SecretsManagerClient,
|
||||||
|
secretKey: string,
|
||||||
|
attempt = 0
|
||||||
|
): Promise<DeleteSecretResponse> => {
|
||||||
|
try {
|
||||||
|
return await client.send(new DeleteSecretCommand({ SecretId: secretKey, ForceDeleteWithoutRecovery: true }));
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as AWSError).code === "ThrottlingException" && attempt < MAX_RETRIES) {
|
||||||
|
await sleep();
|
||||||
|
|
||||||
|
// retry
|
||||||
|
return deleteSecret(client, secretKey, attempt + 1);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncFns = {
|
||||||
|
syncSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
const client = await getSecretsManagerClient(secretSync);
|
||||||
|
|
||||||
|
const awsSecretsRecord = await getSecretsRecord(client);
|
||||||
|
|
||||||
|
const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord);
|
||||||
|
|
||||||
|
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.OneToOne) {
|
||||||
|
for await (const entry of Object.entries(secretMap)) {
|
||||||
|
const [key, { value }] = entry;
|
||||||
|
|
||||||
|
// skip secrets that don't have a value set
|
||||||
|
if (!value) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (awsSecretsRecord[key]) {
|
||||||
|
// skip secrets that haven't changed
|
||||||
|
if (awsValuesRecord[key]?.SecretString === value) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateSecret(client, {
|
||||||
|
SecretId: key,
|
||||||
|
SecretString: value
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await createSecret(client, {
|
||||||
|
Name: key,
|
||||||
|
SecretString: value
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const secretKey of Object.keys(awsSecretsRecord)) {
|
||||||
|
if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
|
||||||
|
try {
|
||||||
|
await deleteSecret(client, secretKey);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Many-To-One Mapping
|
||||||
|
|
||||||
|
const secretValue = JSON.stringify(
|
||||||
|
Object.fromEntries(Object.entries(secretMap).map(([key, secretData]) => [key, secretData.value]))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (awsValuesRecord[destinationConfig.secretName]) {
|
||||||
|
await updateSecret(client, {
|
||||||
|
SecretId: destinationConfig.secretName,
|
||||||
|
SecretString: secretValue
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createSecret(client, {
|
||||||
|
Name: destinationConfig.secretName,
|
||||||
|
SecretString: secretValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const secretKey of Object.keys(awsSecretsRecord)) {
|
||||||
|
if (secretKey === destinationConfig.secretName) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteSecret(client, secretKey);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials): Promise<TSecretMap> => {
|
||||||
|
const client = await getSecretsManagerClient(secretSync);
|
||||||
|
|
||||||
|
const awsSecretsRecord = await getSecretsRecord(client);
|
||||||
|
const awsValuesRecord = await getSecretValuesRecord(client, awsSecretsRecord);
|
||||||
|
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.OneToOne) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.keys(awsSecretsRecord).map((key) => [key, { value: awsValuesRecord[key].SecretString ?? "" }])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many-To-One Mapping
|
||||||
|
|
||||||
|
const secretValueEntry = awsValuesRecord[destinationConfig.secretName];
|
||||||
|
|
||||||
|
if (!secretValueEntry) return {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsedValue = (secretValueEntry.SecretString ? JSON.parse(secretValueEntry.SecretString) : {}) as Record<
|
||||||
|
string,
|
||||||
|
string
|
||||||
|
>;
|
||||||
|
|
||||||
|
return Object.fromEntries(Object.entries(parsedValue).map(([key, value]) => [key, { value }]));
|
||||||
|
} catch {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
message:
|
||||||
|
"Failed to import secrets. Invalid format for Many-To-One mapping behavior: requires key/value configuration.",
|
||||||
|
shouldRetry: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSecrets: async (secretSync: TAwsSecretsManagerSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
const client = await getSecretsManagerClient(secretSync);
|
||||||
|
|
||||||
|
const awsSecretsRecord = await getSecretsRecord(client);
|
||||||
|
|
||||||
|
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.OneToOne) {
|
||||||
|
for await (const secretKey of Object.keys(awsSecretsRecord)) {
|
||||||
|
if (secretKey in secretMap) {
|
||||||
|
try {
|
||||||
|
await deleteSecret(client, secretKey);
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await deleteSecret(client, destinationConfig.secretName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretSyncs } from "@app/lib/api-docs";
|
||||||
|
import { AppConnection, AWSRegion } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { AwsSecretsManagerSyncMappingBehavior } from "@app/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
BaseSecretSyncSchema,
|
||||||
|
GenericCreateSecretSyncFieldsSchema,
|
||||||
|
GenericUpdateSecretSyncFieldsSchema
|
||||||
|
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||||
|
|
||||||
|
const AwsSecretsManagerSyncDestinationConfigSchema = z
|
||||||
|
.discriminatedUnion("mappingBehavior", [
|
||||||
|
z.object({
|
||||||
|
mappingBehavior: z
|
||||||
|
.literal(AwsSecretsManagerSyncMappingBehavior.OneToOne)
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.MAPPING_BEHAVIOR)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
mappingBehavior: z
|
||||||
|
.literal(AwsSecretsManagerSyncMappingBehavior.ManyToOne)
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.MAPPING_BEHAVIOR),
|
||||||
|
secretName: z
|
||||||
|
.string()
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9/_+=.@-]+$/,
|
||||||
|
"Secret name must contain only alphanumeric characters and the characters /_+=.@-"
|
||||||
|
)
|
||||||
|
.min(1, "Secret name is required")
|
||||||
|
.max(256, "Secret name cannot exceed 256 characters")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.SECRET_NAME)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.and(
|
||||||
|
z.object({
|
||||||
|
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.REGION)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncSchema = BaseSecretSyncSchema(SecretSync.AWSSecretsManager).extend({
|
||||||
|
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||||
|
destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateAwsSecretsManagerSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AWSSecretsManager
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateAwsSecretsManagerSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.AWSSecretsManager
|
||||||
|
).extend({
|
||||||
|
destinationConfig: AwsSecretsManagerSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("AWS Secrets Manager"),
|
||||||
|
connection: z.literal(AppConnection.AWS),
|
||||||
|
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||||
|
canImportSecrets: z.literal(true)
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TAwsConnection } from "@app/services/app-connection/aws";
|
||||||
|
|
||||||
|
import {
|
||||||
|
AwsSecretsManagerSyncListItemSchema,
|
||||||
|
AwsSecretsManagerSyncSchema,
|
||||||
|
CreateAwsSecretsManagerSyncSchema
|
||||||
|
} from "./aws-secrets-manager-sync-schemas";
|
||||||
|
|
||||||
|
export type TAwsSecretsManagerSync = z.infer<typeof AwsSecretsManagerSyncSchema>;
|
||||||
|
|
||||||
|
export type TAwsSecretsManagerSyncInput = z.infer<typeof CreateAwsSecretsManagerSyncSchema>;
|
||||||
|
|
||||||
|
export type TAwsSecretsManagerSyncListItem = z.infer<typeof AwsSecretsManagerSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TAwsSecretsManagerSyncWithCredentials = TAwsSecretsManagerSync & {
|
||||||
|
connection: TAwsConnection;
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./aws-secrets-manager-sync-constants";
|
||||||
|
export * from "./aws-secrets-manager-sync-fns";
|
||||||
|
export * from "./aws-secrets-manager-sync-schemas";
|
||||||
|
export * from "./aws-secrets-manager-sync-types";
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum SecretSync {
|
export enum SecretSync {
|
||||||
AWSParameterStore = "aws-parameter-store",
|
AWSParameterStore = "aws-parameter-store",
|
||||||
|
AWSSecretsManager = "aws-secrets-manager",
|
||||||
GitHub = "github",
|
GitHub = "github",
|
||||||
GCPSecretManager = "gcp-secret-manager"
|
GCPSecretManager = "gcp-secret-manager"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import {
|
|||||||
AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
||||||
AwsParameterStoreSyncFns
|
AwsParameterStoreSyncFns
|
||||||
} from "@app/services/secret-sync/aws-parameter-store";
|
} from "@app/services/secret-sync/aws-parameter-store";
|
||||||
|
import {
|
||||||
|
AWS_SECRETS_MANAGER_SYNC_LIST_OPTION,
|
||||||
|
AwsSecretsManagerSyncFns
|
||||||
|
} from "@app/services/secret-sync/aws-secrets-manager";
|
||||||
import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github";
|
import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github";
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
@@ -18,6 +22,7 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
|||||||
|
|
||||||
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
||||||
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.AWSSecretsManager]: AWS_SECRETS_MANAGER_SYNC_LIST_OPTION,
|
||||||
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
|
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
|
||||||
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION
|
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
@@ -73,6 +78,8 @@ export const SecretSyncFns = {
|
|||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return AwsParameterStoreSyncFns.syncSecrets(secretSync, secretMap);
|
return AwsParameterStoreSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
return AwsSecretsManagerSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
@@ -89,6 +96,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
secretMap = await AwsParameterStoreSyncFns.getSecrets(secretSync);
|
secretMap = await AwsParameterStoreSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
secretMap = await AwsSecretsManagerSyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
secretMap = await GithubSyncFns.getSecrets(secretSync);
|
secretMap = await GithubSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
@@ -110,6 +120,8 @@ export const SecretSyncFns = {
|
|||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return AwsParameterStoreSyncFns.removeSecrets(secretSync, secretMap);
|
return AwsParameterStoreSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
return AwsSecretsManagerSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
|||||||
|
|
||||||
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||||
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
||||||
|
[SecretSync.AWSSecretsManager]: "AWS Secrets Manager",
|
||||||
[SecretSync.GitHub]: "GitHub",
|
[SecretSync.GitHub]: "GitHub",
|
||||||
[SecretSync.GCPSecretManager]: "GCP Secret Manager"
|
[SecretSync.GCPSecretManager]: "GCP Secret Manager"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
||||||
|
[SecretSync.AWSSecretsManager]: AppConnection.AWS,
|
||||||
[SecretSync.GitHub]: AppConnection.GitHub,
|
[SecretSync.GitHub]: AppConnection.GitHub,
|
||||||
[SecretSync.GCPSecretManager]: AppConnection.GCP
|
[SecretSync.GCPSecretManager]: AppConnection.GCP
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ import { Job } from "bullmq";
|
|||||||
|
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { QueueJobs } from "@app/queue";
|
import { QueueJobs } from "@app/queue";
|
||||||
|
import {
|
||||||
|
TAwsSecretsManagerSync,
|
||||||
|
TAwsSecretsManagerSyncInput,
|
||||||
|
TAwsSecretsManagerSyncListItem,
|
||||||
|
TAwsSecretsManagerSyncWithCredentials
|
||||||
|
} from "@app/services/secret-sync/aws-secrets-manager";
|
||||||
import {
|
import {
|
||||||
TGitHubSync,
|
TGitHubSync,
|
||||||
TGitHubSyncInput,
|
TGitHubSyncInput,
|
||||||
@@ -19,16 +25,25 @@ import {
|
|||||||
} from "./aws-parameter-store";
|
} from "./aws-parameter-store";
|
||||||
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
||||||
|
|
||||||
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync | TGcpSync;
|
export type TSecretSync = TAwsParameterStoreSync | TAwsSecretsManagerSync | TGitHubSync | TGcpSync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
|
| TAwsSecretsManagerSyncWithCredentials
|
||||||
| TGitHubSyncWithCredentials
|
| TGitHubSyncWithCredentials
|
||||||
| TGcpSyncWithCredentials;
|
| TGcpSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput = TAwsParameterStoreSyncInput | TGitHubSyncInput | TGcpSyncInput;
|
export type TSecretSyncInput =
|
||||||
|
| TAwsParameterStoreSyncInput
|
||||||
|
| TAwsSecretsManagerSyncInput
|
||||||
|
| TGitHubSyncInput
|
||||||
|
| TGcpSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem = TAwsParameterStoreSyncListItem | TGitHubSyncListItem | TGcpSyncListItem;
|
export type TSecretSyncListItem =
|
||||||
|
| TAwsParameterStoreSyncListItem
|
||||||
|
| TAwsSecretsManagerSyncListItem
|
||||||
|
| TGitHubSyncListItem
|
||||||
|
| TGcpSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/aws-secrets-manager"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/aws-secrets-manager/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/aws-secrets-manager/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/aws-secrets-manager/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Import Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/aws-secrets-manager/{syncId}/import-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/aws-secrets-manager"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/aws-secrets-manager/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/aws-secrets-manager/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/aws-secrets-manager/{syncId}"
|
||||||
|
---
|
||||||
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 805 KiB |
|
After Width: | Height: | Size: 797 KiB |
|
After Width: | Height: | Size: 832 KiB |
|
After Width: | Height: | Size: 848 KiB |
|
After Width: | Height: | Size: 782 KiB |
|
After Width: | Height: | Size: 773 KiB |
@@ -39,7 +39,7 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
|
|||||||
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
||||||
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Parameter Store when keys conflict.
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Parameter Store when keys conflict.
|
||||||
- **Import Secrets (Prioritize Parameter Store)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Parameter Store over Infisical when keys conflict.
|
- **Import Secrets (Prioritize AWS Parameter Store)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Parameter Store over Infisical when keys conflict.
|
||||||
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
|
||||||
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
title: "AWS Secrets Manager Sync"
|
||||||
|
description: "Learn how to configure an AWS Secrets Manager Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
|
||||||
|
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Create an [AWS Connection](/integrations/app-connections/aws) with the required **Secret Sync** permissions
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
1. Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|

|
||||||
|
|
||||||
|
2. Select the **AWS Secrets Manager** option.
|
||||||
|

|
||||||
|
|
||||||
|
3. Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|

|
||||||
|
|
||||||
|
- **Environment**: The project environment to retrieve secrets from.
|
||||||
|
- **Secret Path**: The folder path to retrieve secrets from.
|
||||||
|
|
||||||
|
<Tip>
|
||||||
|
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
|
||||||
|
</Tip>
|
||||||
|
|
||||||
|
4. Configure the **Destination** to where secrets should be deployed, then click **Next**.
|
||||||
|

|
||||||
|
|
||||||
|
- **AWS Connection**: The AWS Connection to authenticate with.
|
||||||
|
- **Region**: The AWS region to deploy secrets to.
|
||||||
|
- **Mapping Behavior**: Specify how Infisical should map secrets to AWS Secrets Manager:
|
||||||
|
- **One-To-One**: Each Infisical secret will be mapped to a separate AWS Secrets Manager secret.
|
||||||
|
- **Many-To-One**: All Infisical secrets will be mapped to a single AWS Secrets Manager secret.
|
||||||
|
- **Secret Name**: Specifies the name of the AWS Secret to map secrets to if **Many-To-One** mapping behavior is selected.
|
||||||
|
|
||||||
|
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|

|
||||||
|
|
||||||
|
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
|
||||||
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
|
||||||
|
- **Import Secrets (Prioritize AWS Secrets Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
|
||||||
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
|
||||||
|
6. Configure the **Details** of your Secrets Manager Sync, then click **Next**.
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
|
||||||
|
7. Review your Secrets Manager Sync configuration, then click **Create Sync**.
|
||||||
|

|
||||||
|
|
||||||
|
8. If enabled, your Secrets Manager Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|

|
||||||
|
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create an **AWS Secrets Manager Sync**, make an API request to the [Create AWS
|
||||||
|
Secrets Manager Sync](/api-reference/endpoints/secret-syncs/aws-secrets-manager/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/aws-secrets-manager \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-secrets-manager-sync",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "an example sync",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/my-secrets",
|
||||||
|
"isEnabled": true,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
|
},
|
||||||
|
"destinationConfig": {
|
||||||
|
"region": "us-east-1",
|
||||||
|
"mappingBehavior": "one-to-one"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-secrets-manager-sync",
|
||||||
|
"description": "an example sync",
|
||||||
|
"isEnabled": true,
|
||||||
|
"version": 1,
|
||||||
|
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2023-11-07T05:31:56Z",
|
||||||
|
"updatedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"syncStatus": "succeeded",
|
||||||
|
"lastSyncJobId": "123",
|
||||||
|
"lastSyncMessage": null,
|
||||||
|
"lastSyncedAt": "2023-11-07T05:31:56Z",
|
||||||
|
"importStatus": null,
|
||||||
|
"lastImportJobId": null,
|
||||||
|
"lastImportMessage": null,
|
||||||
|
"lastImportedAt": null,
|
||||||
|
"removeStatus": null,
|
||||||
|
"lastRemoveJobId": null,
|
||||||
|
"lastRemoveMessage": null,
|
||||||
|
"lastRemovedAt": null,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
|
},
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"connection": {
|
||||||
|
"app": "aws",
|
||||||
|
"name": "my-aws-connection",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"slug": "dev",
|
||||||
|
"name": "Development",
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"path": "/my-secrets"
|
||||||
|
},
|
||||||
|
"destination": "aws-secrets-manager",
|
||||||
|
"destinationConfig": {
|
||||||
|
"region": "us-east-1",
|
||||||
|
"mappingBehavior": "one-to-one"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -406,6 +406,7 @@
|
|||||||
"group": "Syncs",
|
"group": "Syncs",
|
||||||
"pages": [
|
"pages": [
|
||||||
"integrations/secret-syncs/aws-parameter-store",
|
"integrations/secret-syncs/aws-parameter-store",
|
||||||
|
"integrations/secret-syncs/aws-secrets-manager",
|
||||||
"integrations/secret-syncs/github",
|
"integrations/secret-syncs/github",
|
||||||
"integrations/secret-syncs/gcp-secret-manager"
|
"integrations/secret-syncs/gcp-secret-manager"
|
||||||
]
|
]
|
||||||
@@ -864,6 +865,20 @@
|
|||||||
"api-reference/endpoints/secret-syncs/aws-parameter-store/remove-secrets"
|
"api-reference/endpoints/secret-syncs/aws-parameter-store/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "AWS Secrets Manager",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/import-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/aws-secrets-manager/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "GitHub",
|
"group": "GitHub",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@@ -1,30 +1,11 @@
|
|||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import { components, OptionProps, SingleValue } from "react-select";
|
|
||||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
import { Badge, FilterableSelect, FormControl, Input } from "@app/components/v2";
|
import { FormControl, Input } from "@app/components/v2";
|
||||||
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
import { TSecretSyncForm } from "../schemas";
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
import { AwsRegionSelect } from "./shared";
|
||||||
const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGIONS)[number]>) => {
|
|
||||||
return (
|
|
||||||
<components.Option isSelected={isSelected} {...props}>
|
|
||||||
<div className="flex flex-row items-center justify-between">
|
|
||||||
<p className="truncate">{children}</p>
|
|
||||||
<Badge variant="success" className="ml-1 mr-auto cursor-pointer">
|
|
||||||
{props.data.slug}
|
|
||||||
</Badge>
|
|
||||||
{isSelected && (
|
|
||||||
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</components.Option>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AwsParameterStoreSyncFields = () => {
|
export const AwsParameterStoreSyncFields = () => {
|
||||||
const { control } = useFormContext<
|
const { control } = useFormContext<
|
||||||
@@ -37,17 +18,7 @@ export const AwsParameterStoreSyncFields = () => {
|
|||||||
<Controller
|
<Controller
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Region">
|
<FormControl isError={Boolean(error)} errorText={error?.message} label="Region">
|
||||||
<FilterableSelect
|
<AwsRegionSelect value={value} onChange={onChange} />
|
||||||
value={AWS_REGIONS.find((region) => region.slug === value)}
|
|
||||||
onChange={(option) =>
|
|
||||||
onChange((option as SingleValue<(typeof AWS_REGIONS)[number]>)?.slug)
|
|
||||||
}
|
|
||||||
options={AWS_REGIONS}
|
|
||||||
placeholder="Select region..."
|
|
||||||
getOptionLabel={(option) => option.name}
|
|
||||||
getOptionValue={(option) => option.slug}
|
|
||||||
components={{ Option }}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
|
import { FormControl, Input, Select, SelectItem } from "@app/components/v2";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { AwsSecretsManagerSyncMappingBehavior } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
import { AwsRegionSelect } from "./shared";
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncFields = () => {
|
||||||
|
const { control, watch } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.AWSSecretsManager }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const mappingBehavior = watch("destinationConfig.mappingBehavior");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField />
|
||||||
|
<Controller
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message} label="Region">
|
||||||
|
<AwsRegionSelect value={value} onChange={onChange} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="destinationConfig.region"
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.mappingBehavior"
|
||||||
|
control={control}
|
||||||
|
defaultValue={AwsSecretsManagerSyncMappingBehavior.OneToOne}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipClassName="max-w-lg py-3"
|
||||||
|
tooltipText={
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<p>Specify how Infisical should map secrets to AWS Secrets Manager:</p>
|
||||||
|
<ul className="flex list-disc flex-col gap-3 pl-4">
|
||||||
|
<li>
|
||||||
|
<p className="text-mineshaft-300">
|
||||||
|
<span className="font-medium text-bunker-200">One-To-One</span>: Each
|
||||||
|
Infisical secret will be mapped to a separate AWS Secrets Manager secret.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p className="text-mineshaft-300">
|
||||||
|
<span className="font-medium text-bunker-200">Many-To-One</span>: All
|
||||||
|
Infisical secrets will be mapped to a single AWS Secrets Manager secret.
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Mapping Behavior"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
onValueChange={(val) => onChange(val)}
|
||||||
|
className="w-full border border-mineshaft-500 capitalize"
|
||||||
|
position="popper"
|
||||||
|
placeholder="Select an option..."
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
>
|
||||||
|
{Object.values(AwsSecretsManagerSyncMappingBehavior).map((behavior) => {
|
||||||
|
return (
|
||||||
|
<SelectItem className="capitalize" value={behavior} key={behavior}>
|
||||||
|
{behavior}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne && (
|
||||||
|
<Controller
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="AWS Secrets Manager Secret Name"
|
||||||
|
>
|
||||||
|
<Input value={value} onChange={onChange} placeholder="Secret name..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
control={control}
|
||||||
|
name="destinationConfig.secretName"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
|
|||||||
|
|
||||||
import { TSecretSyncForm } from "../schemas";
|
import { TSecretSyncForm } from "../schemas";
|
||||||
import { AwsParameterStoreSyncFields } from "./AwsParameterStoreSyncFields";
|
import { AwsParameterStoreSyncFields } from "./AwsParameterStoreSyncFields";
|
||||||
|
import { AwsSecretsManagerSyncFields } from "./AwsSecretsManagerSyncFields";
|
||||||
import { GcpSyncFields } from "./GcpSyncFields";
|
import { GcpSyncFields } from "./GcpSyncFields";
|
||||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||||
|
|
||||||
@@ -15,6 +16,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
switch (destination) {
|
switch (destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return <AwsParameterStoreSyncFields />;
|
return <AwsParameterStoreSyncFields />;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
return <AwsSecretsManagerSyncFields />;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return <GitHubSyncFields />;
|
return <GitHubSyncFields />;
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import { components, OptionProps, SingleValue } from "react-select";
|
||||||
|
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { Badge, FilterableSelect } from "@app/components/v2";
|
||||||
|
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||||
|
|
||||||
|
const Option = ({ isSelected, children, ...props }: OptionProps<(typeof AWS_REGIONS)[number]>) => {
|
||||||
|
return (
|
||||||
|
<components.Option isSelected={isSelected} {...props}>
|
||||||
|
<div className="flex flex-row items-center justify-between">
|
||||||
|
<p className="truncate">{children}</p>
|
||||||
|
<Badge variant="success" className="ml-1 mr-auto cursor-pointer">
|
||||||
|
{props.data.slug}
|
||||||
|
</Badge>
|
||||||
|
{isSelected && (
|
||||||
|
<FontAwesomeIcon className="ml-2 text-primary" icon={faCheckCircle} size="sm" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</components.Option>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
value: string;
|
||||||
|
onChange: (value: string | undefined) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsRegionSelect = ({ value, onChange }: Props) => {
|
||||||
|
return (
|
||||||
|
<FilterableSelect
|
||||||
|
value={AWS_REGIONS.find((region) => region.slug === value)}
|
||||||
|
onChange={(option) => onChange((option as SingleValue<(typeof AWS_REGIONS)[number]>)?.slug)}
|
||||||
|
options={AWS_REGIONS}
|
||||||
|
placeholder="Select region..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.slug}
|
||||||
|
components={{ Option }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./AwsRegionSelect";
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { Badge } from "@app/components/v2";
|
||||||
|
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { AwsSecretsManagerSyncMappingBehavior } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.AWSSecretsManager }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const [region, mappingBehavior, secretName] = watch([
|
||||||
|
"destinationConfig.region",
|
||||||
|
"destinationConfig.mappingBehavior",
|
||||||
|
"destinationConfig.secretName"
|
||||||
|
]);
|
||||||
|
|
||||||
|
const awsRegion = AWS_REGIONS.find((r) => r.slug === region);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncLabel label="Region">
|
||||||
|
{awsRegion?.name}
|
||||||
|
<Badge className="ml-1" variant="success">
|
||||||
|
{awsRegion?.slug}{" "}
|
||||||
|
</Badge>
|
||||||
|
</SecretSyncLabel>
|
||||||
|
<SecretSyncLabel className="capitalize" label="Mapping Behavior">
|
||||||
|
{mappingBehavior}
|
||||||
|
</SecretSyncLabel>
|
||||||
|
{mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne && (
|
||||||
|
<SecretSyncLabel label="Secret Name">{secretName}</SecretSyncLabel>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ import { useFormContext } from "react-hook-form";
|
|||||||
|
|
||||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { AwsSecretsManagerSyncReviewFields } from "@app/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields";
|
||||||
import { Badge } from "@app/components/v2";
|
import { Badge } from "@app/components/v2";
|
||||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
@@ -36,6 +37,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
DestinationFieldsComponent = <AwsParameterStoreSyncReviewFields />;
|
DestinationFieldsComponent = <AwsParameterStoreSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
DestinationFieldsComponent = <AwsSecretsManagerSyncReviewFields />;
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
DestinationFieldsComponent = <GitHubSyncReviewFields />;
|
DestinationFieldsComponent = <GitHubSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { AwsSecretsManagerSyncMappingBehavior } from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncDestinationSchema = z.object({
|
||||||
|
destination: z.literal(SecretSync.AWSSecretsManager),
|
||||||
|
destinationConfig: z
|
||||||
|
.discriminatedUnion("mappingBehavior", [
|
||||||
|
z.object({
|
||||||
|
mappingBehavior: z.literal(AwsSecretsManagerSyncMappingBehavior.OneToOne)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
mappingBehavior: z.literal(AwsSecretsManagerSyncMappingBehavior.ManyToOne),
|
||||||
|
secretName: z
|
||||||
|
.string()
|
||||||
|
.regex(
|
||||||
|
/^[a-zA-Z0-9/_+=.@-]+$/,
|
||||||
|
"Secret name must contain only alphanumeric characters and the characters /_+=.@-"
|
||||||
|
)
|
||||||
|
.min(1, "Secret name is required")
|
||||||
|
.max(256, "Secret name cannot exceed 256 characters")
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.and(
|
||||||
|
z.object({
|
||||||
|
region: z.string().min(1, "Region required")
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { AwsSecretsManagerSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/aws-secrets-manager-sync-destination-schema";
|
||||||
import { GitHubSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/github-sync-destination-schema";
|
import { GitHubSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/github-sync-destination-schema";
|
||||||
import { SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs";
|
import { SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs";
|
||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
@@ -32,6 +33,7 @@ const BaseSecretSyncSchema = z.object({
|
|||||||
|
|
||||||
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncDestinationSchema,
|
AwsParameterStoreSyncDestinationSchema,
|
||||||
|
AwsSecretsManagerSyncDestinationSchema,
|
||||||
GitHubSyncDestinationSchema,
|
GitHubSyncDestinationSchema,
|
||||||
GcpSyncDestinationSchema
|
GcpSyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import {
|
|||||||
} from "@app/hooks/api/secretSyncs";
|
} from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
|
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
|
||||||
[SecretSync.AWSParameterStore]: { name: "Parameter Store", image: "Amazon Web Services.png" },
|
[SecretSync.AWSParameterStore]: { name: "AWS Parameter Store", image: "Amazon Web Services.png" },
|
||||||
|
[SecretSync.AWSSecretsManager]: { name: "AWS Secrets Manager", image: "Amazon Web Services.png" },
|
||||||
[SecretSync.GitHub]: { name: "GitHub", image: "GitHub.png" },
|
[SecretSync.GitHub]: { name: "GitHub", image: "GitHub.png" },
|
||||||
[SecretSync.GCPSecretManager]: { name: "GCP Secret Manager", image: "Google Cloud Platform.png" }
|
[SecretSync.GCPSecretManager]: { name: "GCP Secret Manager", image: "Google Cloud Platform.png" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
||||||
|
[SecretSync.AWSSecretsManager]: AppConnection.AWS,
|
||||||
[SecretSync.GitHub]: AppConnection.GitHub,
|
[SecretSync.GitHub]: AppConnection.GitHub,
|
||||||
[SecretSync.GCPSecretManager]: AppConnection.GCP
|
[SecretSync.GCPSecretManager]: AppConnection.GCP
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum SecretSync {
|
export enum SecretSync {
|
||||||
AWSParameterStore = "aws-parameter-store",
|
AWSParameterStore = "aws-parameter-store",
|
||||||
|
AWSSecretsManager = "aws-secrets-manager",
|
||||||
GitHub = "github",
|
GitHub = "github",
|
||||||
GCPSecretManager = "gcp-secret-manager"
|
GCPSecretManager = "gcp-secret-manager"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export type TAwsSecretsManagerSync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.AWSSecretsManager;
|
||||||
|
destinationConfig:
|
||||||
|
| {
|
||||||
|
mappingBehavior: AwsSecretsManagerSyncMappingBehavior.OneToOne;
|
||||||
|
region: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
region: string;
|
||||||
|
mappingBehavior: AwsSecretsManagerSyncMappingBehavior.ManyToOne;
|
||||||
|
secretName: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.AWS;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export enum AwsSecretsManagerSyncMappingBehavior {
|
||||||
|
OneToOne = "one-to-one",
|
||||||
|
ManyToOne = "many-to-one"
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-par
|
|||||||
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
|
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
|
||||||
import { DiscriminativePick } from "@app/types";
|
import { DiscriminativePick } from "@app/types";
|
||||||
|
|
||||||
|
import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
|
||||||
import { TGcpSync } from "./gcp-sync";
|
import { TGcpSync } from "./gcp-sync";
|
||||||
|
|
||||||
export type TSecretSyncOption = {
|
export type TSecretSyncOption = {
|
||||||
@@ -11,7 +12,7 @@ export type TSecretSyncOption = {
|
|||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync | TGcpSync;
|
export type TSecretSync = TAwsParameterStoreSync | TAwsSecretsManagerSync | TGitHubSync | TGcpSync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const IntegrationsListPage = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
|
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
|
||||||
<div className="mb-1 flex items-center text-sm">
|
<div className="mb-1 flex items-center text-sm">
|
||||||
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1 text-primary" />
|
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
|
||||||
Integrations Update
|
Integrations Update
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-2 mt-1 text-sm text-bunker-300">
|
<p className="mb-2 mt-1 text-sm text-bunker-300">
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
AwsSecretsManagerSyncMappingBehavior,
|
||||||
|
TAwsSecretsManagerSync
|
||||||
|
} from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell, SecretSyncTableCellProps } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TAwsSecretsManagerSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
let additionalProps: Pick<
|
||||||
|
SecretSyncTableCellProps,
|
||||||
|
"additionalTooltipContent" | "infoBadge" | "secondaryClassName"
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
if (destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne) {
|
||||||
|
additionalProps = {
|
||||||
|
infoBadge: "secondary",
|
||||||
|
additionalTooltipContent: (
|
||||||
|
<div className="mt-4">
|
||||||
|
<span className="text-xs text-bunker-300">Secret Name:</span>
|
||||||
|
<p className="text-sm">{destinationConfig.secretName}</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SecretSyncTableCell
|
||||||
|
{...additionalProps}
|
||||||
|
secondaryClassName="capitalize"
|
||||||
|
primaryText={primaryText}
|
||||||
|
secondaryText={secondaryText}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
import { AwsParameterStoreSyncDestinationCol } from "./AwsParameterStoreSyncDestinationCol";
|
import { AwsParameterStoreSyncDestinationCol } from "./AwsParameterStoreSyncDestinationCol";
|
||||||
|
import { AwsSecretsManagerSyncDestinationCol } from "./AwsSecretsManagerSyncDestinationCol";
|
||||||
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
||||||
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return <AwsParameterStoreSyncDestinationCol secretSync={secretSync} />;
|
return <AwsParameterStoreSyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
return <AwsSecretsManagerSyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return <GitHubSyncDestinationCol secretSync={secretSync} />;
|
return <GitHubSyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ export const SecretSyncTableCell = ({
|
|||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<p className="text-sm">{primaryText}</p>
|
<p className="text-sm">{primaryText}</p>
|
||||||
{secondaryText && <p className="text-xs leading-3 text-bunker-300">{secondaryText}</p>}
|
{secondaryText && (
|
||||||
|
<p className={twMerge("text-xs leading-3 text-bunker-300", secondaryClassName)}>
|
||||||
|
{secondaryText}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
{additionalTooltipContent}
|
{additionalTooltipContent}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
primaryText = destinationConfig.path;
|
primaryText = destinationConfig.path;
|
||||||
secondaryText = destinationConfig.region;
|
secondaryText = destinationConfig.region;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
primaryText = destinationConfig.region;
|
||||||
|
secondaryText = destinationConfig.mappingBehavior;
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
switch (destinationConfig.scope) {
|
switch (destinationConfig.scope) {
|
||||||
case GitHubSyncScope.Organization:
|
case GitHubSyncScope.Organization:
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
|
import { Badge } from "@app/components/v2";
|
||||||
|
import { AWS_REGIONS } from "@app/helpers/appConnections";
|
||||||
|
import {
|
||||||
|
AwsSecretsManagerSyncMappingBehavior,
|
||||||
|
TAwsSecretsManagerSync
|
||||||
|
} from "@app/hooks/api/secretSyncs/types/aws-secrets-manager-sync";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TAwsSecretsManagerSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AwsSecretsManagerSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
|
const awsRegion = AWS_REGIONS.find((r) => r.slug === destinationConfig.region);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncLabel label="Region">
|
||||||
|
{awsRegion?.name}
|
||||||
|
<Badge className="ml-1" variant="success">
|
||||||
|
{awsRegion?.slug}{" "}
|
||||||
|
</Badge>
|
||||||
|
</SecretSyncLabel>
|
||||||
|
<SecretSyncLabel label="Mapping Behavior" className="capitalize">
|
||||||
|
{destinationConfig.mappingBehavior}
|
||||||
|
</SecretSyncLabel>
|
||||||
|
{destinationConfig.mappingBehavior === AwsSecretsManagerSyncMappingBehavior.ManyToOne && (
|
||||||
|
<SecretSyncLabel label="Secret Name">{destinationConfig.secretName}</SecretSyncLabel>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -10,6 +10,7 @@ import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissi
|
|||||||
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
|
||||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
||||||
|
import { AwsSecretsManagerSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsSecretsManagerSyncDestinationSection";
|
||||||
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
||||||
|
|
||||||
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
||||||
@@ -29,6 +30,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
DestinationComponents = <AwsParameterStoreSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <AwsParameterStoreSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.AWSSecretsManager:
|
||||||
|
DestinationComponents = <AwsSecretsManagerSyncDestinationSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
|||||||