mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 08:35:52 +00:00
feat: chef secret-sync
This commit is contained in:
@@ -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 CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||
name: "Chef",
|
||||
destination: SecretSync.Chef,
|
||||
connection: AppConnection.Chef,
|
||||
canImportSecrets: true
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
import { getChefDataBagItem, updateChefDataBagItem } from "@app/services/app-connection/chef";
|
||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { ChefSecret, TChefDataBagItemContent, TChefSyncWithCredentials, TGetChefSecrets } from "./chef-sync-types";
|
||||
|
||||
const getChefSecretsRaw = async ({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
}: TGetChefSecrets): Promise<TChefDataBagItemContent> => {
|
||||
const dataBagItem = await getChefDataBagItem({
|
||||
serverUrl,
|
||||
userName,
|
||||
privateKey,
|
||||
orgName,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
});
|
||||
|
||||
// Ensure the data bag item has an id field
|
||||
if (!dataBagItem.id) {
|
||||
dataBagItem.id = dataBagItemName;
|
||||
}
|
||||
|
||||
return dataBagItem;
|
||||
};
|
||||
|
||||
const getChefSecrets = async (secretSync: TChefSyncWithCredentials): Promise<ChefSecret[]> => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { dataBagName, dataBagItemName }
|
||||
} = secretSync;
|
||||
|
||||
const { serverUrl, userName, privateKey, orgName } = connection.credentials;
|
||||
|
||||
const dataBagItem = await getChefSecretsRaw({
|
||||
serverUrl,
|
||||
orgName,
|
||||
userName,
|
||||
privateKey,
|
||||
dataBagName,
|
||||
dataBagItemName
|
||||
});
|
||||
|
||||
// Convert data bag item to key-value pairs
|
||||
// Exclude the 'id' field as it's metadata
|
||||
const secrets: ChefSecret[] = [];
|
||||
Object.entries(dataBagItem).forEach(([key, value]) => {
|
||||
if (key !== "id" && value !== null && value !== undefined) {
|
||||
secrets.push({ key, value: String(value) });
|
||||
}
|
||||
});
|
||||
|
||||
return secrets;
|
||||
};
|
||||
|
||||
const updateChefSecrets = async (
|
||||
secretSync: TChefSyncWithCredentials,
|
||||
secrets: Record<string, string | number | boolean>
|
||||
) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { dataBagName, dataBagItemName }
|
||||
} = secretSync;
|
||||
|
||||
const { serverUrl, userName, privateKey, orgName } = connection.credentials;
|
||||
|
||||
// Chef data bag items must have an 'id' field
|
||||
const dataBagItemContent: TChefDataBagItemContent = {
|
||||
id: dataBagItemName,
|
||||
...secrets
|
||||
};
|
||||
|
||||
await updateChefDataBagItem({
|
||||
serverUrl,
|
||||
orgName,
|
||||
userName,
|
||||
privateKey,
|
||||
dataBagName,
|
||||
dataBagItemName,
|
||||
data: dataBagItemContent
|
||||
});
|
||||
};
|
||||
|
||||
export const ChefSyncFns = {
|
||||
async syncSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const {
|
||||
environment,
|
||||
syncOptions: { disableSecretDeletion, keySchema }
|
||||
} = secretSync;
|
||||
|
||||
const secrets = await getChefSecrets(secretSync);
|
||||
|
||||
// Create a map of the existing secrets
|
||||
const updatedSecretsMap = new Map(secrets.map((secret) => [secret.key, secret.value]));
|
||||
|
||||
// Add/update new secrets
|
||||
for (const [key, { value }] of Object.entries(secretMap)) {
|
||||
updatedSecretsMap.set(key, value);
|
||||
}
|
||||
|
||||
// Delete secrets if not disabled
|
||||
if (!disableSecretDeletion) {
|
||||
secrets.forEach((secret) => {
|
||||
if (!matchesSchema(secret.key, environment?.slug || "", keySchema)) return;
|
||||
|
||||
if (!secretMap[secret.key]) {
|
||||
updatedSecretsMap.delete(secret.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Convert map to object for Chef API
|
||||
const updatedSecrets = Object.fromEntries(updatedSecretsMap.entries());
|
||||
|
||||
await updateChefSecrets(secretSync, updatedSecrets);
|
||||
},
|
||||
|
||||
async getSecrets(secretSync: TChefSyncWithCredentials): Promise<TSecretMap> {
|
||||
const secrets = await getChefSecrets(secretSync);
|
||||
return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }]));
|
||||
},
|
||||
|
||||
async removeSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) {
|
||||
const existingSecrets = await getChefSecrets(secretSync);
|
||||
|
||||
const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key));
|
||||
|
||||
// If nothing changed, return early
|
||||
if (newSecrets.length === existingSecrets.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert to object for Chef API
|
||||
const updatedSecrets = Object.fromEntries(newSecrets.map((secret) => [secret.key, secret.value]));
|
||||
|
||||
await updateChefSecrets(secretSync, updatedSecrets);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretSyncs } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import {
|
||||
BaseSecretSyncSchema,
|
||||
GenericCreateSecretSyncFieldsSchema,
|
||||
GenericUpdateSecretSyncFieldsSchema
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const ChefSyncDestinationConfigSchema = z.object({
|
||||
dataBagName: z
|
||||
.string()
|
||||
.min(1, "Data Bag Name is required")
|
||||
.max(256, "Data Bag Name cannot exceed 256 characters")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagName),
|
||||
dataBagItemName: z
|
||||
.string()
|
||||
.min(1, "Data Bag Item Name is required")
|
||||
.max(256, "Data Bag Item Name cannot exceed 256 characters")
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagItemName)
|
||||
});
|
||||
|
||||
const ChefSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destination: z.literal(SecretSync.Chef),
|
||||
destinationConfig: ChefSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const CreateChefSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destinationConfig: ChefSyncDestinationConfigSchema
|
||||
});
|
||||
|
||||
export const UpdateChefSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({
|
||||
destinationConfig: ChefSyncDestinationConfigSchema.optional()
|
||||
});
|
||||
|
||||
export const ChefSyncListItemSchema = z.object({
|
||||
name: z.literal("Chef"),
|
||||
connection: z.literal(AppConnection.Chef),
|
||||
destination: z.literal(SecretSync.Chef),
|
||||
canImportSecrets: z.literal(true)
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import z from "zod";
|
||||
|
||||
import { TChefConnection } from "@app/services/app-connection/chef";
|
||||
|
||||
import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas";
|
||||
|
||||
export type TChefSyncListItem = z.infer<typeof ChefSyncListItemSchema>;
|
||||
|
||||
export type TChefSync = z.infer<typeof ChefSyncSchema>;
|
||||
|
||||
export type TChefSyncInput = z.infer<typeof CreateChefSyncSchema>;
|
||||
|
||||
export type TChefSyncWithCredentials = TChefSync & {
|
||||
connection: TChefConnection;
|
||||
};
|
||||
|
||||
export type TGetChefSecrets = {
|
||||
serverUrl?: string;
|
||||
userName: string;
|
||||
privateKey: string;
|
||||
orgName: string;
|
||||
dataBagName: string;
|
||||
dataBagItemName: string;
|
||||
};
|
||||
|
||||
export type TChefDataBagItemContent = {
|
||||
id: string;
|
||||
[key: string]: string | number | boolean | null;
|
||||
};
|
||||
|
||||
export type ChefSecret = {
|
||||
key: string;
|
||||
value: string;
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./chef-sync-constants";
|
||||
export * from "./chef-sync-fns";
|
||||
export * from "./chef-sync-schemas";
|
||||
export * from "./chef-sync-types";
|
||||
@@ -29,7 +29,8 @@ export enum SecretSync {
|
||||
DigitalOceanAppPlatform = "digital-ocean-app-platform",
|
||||
Netlify = "netlify",
|
||||
Bitbucket = "bitbucket",
|
||||
LaravelForge = "laravel-forge"
|
||||
LaravelForge = "laravel-forge",
|
||||
Chef = "chef"
|
||||
}
|
||||
|
||||
export enum SecretSyncInitialSyncBehavior {
|
||||
|
||||
@@ -34,6 +34,7 @@ import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket";
|
||||
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
||||
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
||||
import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "./chef";
|
||||
import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants";
|
||||
import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns";
|
||||
import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers";
|
||||
@@ -49,8 +50,7 @@ import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
|
||||
import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku";
|
||||
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
|
||||
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
||||
import { LARAVEL_FORGE_SYNC_LIST_OPTION } from "./laravel-forge";
|
||||
import { LaravelForgeSyncFns } from "./laravel-forge/laravel-forge-sync-fns";
|
||||
import { LARAVEL_FORGE_SYNC_LIST_OPTION, LaravelForgeSyncFns } from "./laravel-forge";
|
||||
import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify";
|
||||
import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants";
|
||||
import { RailwaySyncFns } from "./railway/railway-sync-fns";
|
||||
@@ -94,7 +94,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
||||
[SecretSync.DigitalOceanAppPlatform]: DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION,
|
||||
[SecretSync.Netlify]: NETLIFY_SYNC_LIST_OPTION,
|
||||
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION,
|
||||
[SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION
|
||||
[SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION,
|
||||
[SecretSync.Chef]: CHEF_SYNC_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretSyncOptions = () => {
|
||||
@@ -282,6 +283,8 @@ export const SecretSyncFns = {
|
||||
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.LaravelForge:
|
||||
return LaravelForgeSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Chef:
|
||||
return ChefSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
@@ -401,6 +404,9 @@ export const SecretSyncFns = {
|
||||
case SecretSync.LaravelForge:
|
||||
secretMap = await LaravelForgeSyncFns.getSecrets(secretSync);
|
||||
break;
|
||||
case SecretSync.Chef:
|
||||
secretMap = await ChefSyncFns.getSecrets(secretSync);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
@@ -496,6 +502,8 @@ export const SecretSyncFns = {
|
||||
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.LaravelForge:
|
||||
return LaravelForgeSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
case SecretSync.Chef:
|
||||
return ChefSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||
|
||||
@@ -33,7 +33,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||
[SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform",
|
||||
[SecretSync.Netlify]: "Netlify",
|
||||
[SecretSync.Bitbucket]: "Bitbucket",
|
||||
[SecretSync.LaravelForge]: "Laravel Forge"
|
||||
[SecretSync.LaravelForge]: "Laravel Forge",
|
||||
[SecretSync.Chef]: "Chef"
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
@@ -67,7 +68,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean,
|
||||
[SecretSync.Netlify]: AppConnection.Netlify,
|
||||
[SecretSync.Bitbucket]: AppConnection.Bitbucket,
|
||||
[SecretSync.LaravelForge]: AppConnection.LaravelForge
|
||||
[SecretSync.LaravelForge]: AppConnection.LaravelForge,
|
||||
[SecretSync.Chef]: AppConnection.Chef
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
@@ -101,7 +103,8 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
[SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Netlify]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular
|
||||
[SecretSync.LaravelForge]: SecretSyncPlanType.Regular,
|
||||
[SecretSync.Chef]: SecretSyncPlanType.Regular
|
||||
};
|
||||
|
||||
export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
@@ -144,7 +147,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
[SecretSync.DigitalOceanAppPlatform]: ["appName"],
|
||||
[SecretSync.Netlify]: ["accountName", "siteName"],
|
||||
[SecretSync.Bitbucket]: [],
|
||||
[SecretSync.LaravelForge]: []
|
||||
[SecretSync.LaravelForge]: [],
|
||||
[SecretSync.Chef]: []
|
||||
};
|
||||
|
||||
const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true;
|
||||
@@ -204,5 +208,6 @@ export const DESTINATION_DUPLICATE_CHECK_MAP: Record<SecretSync, DestinationDupl
|
||||
[SecretSync.DigitalOceanAppPlatform]: defaultDuplicateCheck,
|
||||
[SecretSync.Netlify]: defaultDuplicateCheck,
|
||||
[SecretSync.Bitbucket]: defaultDuplicateCheck,
|
||||
[SecretSync.LaravelForge]: defaultDuplicateCheck
|
||||
[SecretSync.LaravelForge]: defaultDuplicateCheck,
|
||||
[SecretSync.Chef]: defaultDuplicateCheck
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
TCamundaSyncListItem,
|
||||
TCamundaSyncWithCredentials
|
||||
} from "@app/services/secret-sync/camunda";
|
||||
import { TChefSync, TChefSyncInput, TChefSyncListItem, TChefSyncWithCredentials } from "@app/services/secret-sync/chef";
|
||||
import {
|
||||
TDatabricksSync,
|
||||
TDatabricksSyncInput,
|
||||
@@ -163,6 +164,7 @@ export type TSecretSync =
|
||||
| TGitHubSync
|
||||
| TGcpSync
|
||||
| TAzureKeyVaultSync
|
||||
| TChefSync
|
||||
| TAzureAppConfigurationSync
|
||||
| TAzureDevOpsSync
|
||||
| TDatabricksSync
|
||||
@@ -195,6 +197,7 @@ export type TSecretSyncWithCredentials =
|
||||
| TGitHubSyncWithCredentials
|
||||
| TGcpSyncWithCredentials
|
||||
| TAzureKeyVaultSyncWithCredentials
|
||||
| TChefSyncWithCredentials
|
||||
| TAzureAppConfigurationSyncWithCredentials
|
||||
| TAzureDevOpsSyncWithCredentials
|
||||
| TDatabricksSyncWithCredentials
|
||||
@@ -228,6 +231,7 @@ export type TSecretSyncInput =
|
||||
| TGitHubSyncInput
|
||||
| TGcpSyncInput
|
||||
| TAzureKeyVaultSyncInput
|
||||
| TChefSyncInput
|
||||
| TAzureAppConfigurationSyncInput
|
||||
| TAzureDevOpsSyncInput
|
||||
| TDatabricksSyncInput
|
||||
@@ -261,6 +265,7 @@ export type TSecretSyncListItem =
|
||||
| TGitHubSyncListItem
|
||||
| TGcpSyncListItem
|
||||
| TAzureKeyVaultSyncListItem
|
||||
| TChefSyncListItem
|
||||
| TAzureAppConfigurationSyncListItem
|
||||
| TAzureDevOpsSyncListItem
|
||||
| TDatabricksSyncListItem
|
||||
|
||||
Reference in New Issue
Block a user