Merge pull request #3487 from Infisical/ENG-2633

feat(secret-sync): Hashicorp Vault App Connection & Secret Sync
This commit is contained in:
x032205
2025-05-01 20:31:18 -04:00
committed by GitHub
111 changed files with 1693 additions and 157 deletions
@@ -14,6 +14,7 @@ export enum AppConnection {
Camunda = "camunda",
Windmill = "windmill",
Auth0 = "auth0",
HCVault = "hashicorp-vault",
LDAP = "ldap",
TeamCity = "teamcity"
}
@@ -41,6 +41,11 @@ import {
} from "./databricks";
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
import {
getHCVaultConnectionListItem,
HCVaultConnectionMethod,
validateHCVaultConnectionCredentials
} from "./hc-vault";
import {
getHumanitecConnectionListItem,
HumanitecConnectionMethod,
@@ -84,6 +89,7 @@ export const listAppConnectionOptions = () => {
getAzureClientSecretsConnectionListItem(),
getWindmillConnectionListItem(),
getAuth0ConnectionListItem(),
getHCVaultConnectionListItem(),
getLdapConnectionListItem(),
getTeamCityConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
@@ -152,6 +158,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.HCVault]: validateHCVaultConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
};
@@ -186,10 +193,13 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case MsSqlConnectionMethod.UsernameAndPassword:
return "Username & Password";
case WindmillConnectionMethod.AccessToken:
case HCVaultConnectionMethod.AccessToken:
case TeamCityConnectionMethod.AccessToken:
return "Access Token";
case Auth0ConnectionMethod.ClientCredentials:
return "Client Credentials";
case HCVaultConnectionMethod.AppRole:
return "App Role";
case LdapConnectionMethod.SimpleBind:
return "Simple Bind";
default:
@@ -238,6 +248,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported,
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
};
@@ -16,6 +16,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Camunda]: "Camunda",
[AppConnection.Windmill]: "Windmill",
[AppConnection.Auth0]: "Auth0",
[AppConnection.HCVault]: "Hashicorp Vault",
[AppConnection.LDAP]: "LDAP",
[AppConnection.TeamCity]: "TeamCity"
};
@@ -43,6 +43,8 @@ import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
import { gcpConnectionService } from "./gcp/gcp-connection-service";
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
import { githubConnectionService } from "./github/github-connection-service";
import { ValidateHCVaultConnectionCredentialsSchema } from "./hc-vault";
import { hcVaultConnectionService } from "./hc-vault/hc-vault-connection-service";
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
@@ -81,6 +83,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
};
@@ -459,6 +462,7 @@ export const appConnectionServiceFactory = ({
vercel: vercelConnectionService(connectAppConnectionById),
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
hcvault: hcVaultConnectionService(connectAppConnectionById),
windmill: windmillConnectionService(connectAppConnectionById),
teamcity: teamcityConnectionService(connectAppConnectionById)
};
@@ -57,6 +57,12 @@ import {
TGitHubConnectionInput,
TValidateGitHubConnectionCredentialsSchema
} from "./github";
import {
THCVaultConnection,
THCVaultConnectionConfig,
THCVaultConnectionInput,
TValidateHCVaultConnectionCredentialsSchema
} from "./hc-vault";
import {
THumanitecConnection,
THumanitecConnectionConfig,
@@ -116,6 +122,7 @@ export type TAppConnection = { id: string } & (
| TAzureClientSecretsConnection
| TWindmillConnection
| TAuth0Connection
| THCVaultConnection
| TLdapConnection
| TTeamCityConnection
);
@@ -140,6 +147,7 @@ export type TAppConnectionInput = { id: string } & (
| TAzureClientSecretsConnectionInput
| TWindmillConnectionInput
| TAuth0ConnectionInput
| THCVaultConnectionInput
| TLdapConnectionInput
| TTeamCityConnectionInput
);
@@ -170,6 +178,7 @@ export type TAppConnectionConfig =
| TVercelConnectionConfig
| TWindmillConnectionConfig
| TAuth0ConnectionConfig
| THCVaultConnectionConfig
| TLdapConnectionConfig
| TTeamCityConnectionConfig;
@@ -189,6 +198,7 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateTerraformCloudConnectionCredentialsSchema
| TValidateWindmillConnectionCredentialsSchema
| TValidateAuth0ConnectionCredentialsSchema
| TValidateHCVaultConnectionCredentialsSchema
| TValidateLdapConnectionCredentialsSchema
| TValidateTeamCityConnectionCredentialsSchema;
@@ -0,0 +1,4 @@
export enum HCVaultConnectionMethod {
AccessToken = "access-token",
AppRole = "app-role"
}
@@ -0,0 +1,119 @@
import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { HCVaultConnectionMethod } from "./hc-vault-connection-enums";
import {
THCVaultConnection,
THCVaultConnectionConfig,
THCVaultMountResponse,
TValidateHCVaultConnectionCredentials
} from "./hc-vault-connection-types";
export const getHCVaultInstanceUrl = async (config: THCVaultConnectionConfig) => {
const instanceUrl = removeTrailingSlash(config.credentials.instanceUrl);
await blockLocalAndPrivateIpAddresses(instanceUrl);
return instanceUrl;
};
export const getHCVaultConnectionListItem = () => ({
name: "HCVault" as const,
app: AppConnection.HCVault as const,
methods: Object.values(HCVaultConnectionMethod) as [
HCVaultConnectionMethod.AccessToken,
HCVaultConnectionMethod.AppRole
]
});
type TokenRespData = {
auth: {
client_token: string;
};
};
export const getHCVaultAccessToken = async (connection: TValidateHCVaultConnectionCredentials) => {
// Return access token directly if not using AppRole method
if (connection.method !== HCVaultConnectionMethod.AppRole) {
return connection.credentials.accessToken;
}
// Generate temporary token for AppRole method
try {
const { instanceUrl, roleId, secretId } = connection.credentials;
const tokenResp = await request.post<TokenRespData>(
`${removeTrailingSlash(instanceUrl)}/v1/auth/approle/login`,
{ role_id: roleId, secret_id: secretId },
{
headers: {
"Content-Type": "application/json",
...(connection.credentials.namespace ? { "X-Vault-Namespace": connection.credentials.namespace } : {})
}
}
);
if (tokenResp.status !== 200) {
throw new BadRequestError({
message: `Unable to validate credentials: Hashicorp Vault responded with a status code of ${tokenResp.status} (${tokenResp.statusText}). Verify credentials and try again.`
});
}
return tokenResp.data.auth.client_token;
} catch (e: unknown) {
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
};
export const validateHCVaultConnectionCredentials = async (config: THCVaultConnectionConfig) => {
const instanceUrl = await getHCVaultInstanceUrl(config);
try {
const accessToken = await getHCVaultAccessToken(config);
// Verify token
await request.get(`${instanceUrl}/v1/auth/token/lookup-self`, {
headers: { "X-Vault-Token": accessToken }
});
return config.credentials;
} catch (error: unknown) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
};
export const listHCVaultMounts = async (appConnection: THCVaultConnection) => {
const instanceUrl = await getHCVaultInstanceUrl(appConnection);
const accessToken = await getHCVaultAccessToken(appConnection);
const { data } = await request.get<THCVaultMountResponse>(`${instanceUrl}/v1/sys/mounts`, {
headers: {
"X-Vault-Token": accessToken,
...(appConnection.credentials.namespace ? { "X-Vault-Namespace": appConnection.credentials.namespace } : {})
}
});
const mounts: string[] = [];
// Filter for "kv" version 2 type only
Object.entries(data.data).forEach(([path, mount]) => {
if (mount.type === "kv" && mount.options?.version === "2") {
mounts.push(path);
}
});
return mounts;
};
@@ -0,0 +1,100 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { HCVaultConnectionMethod } from "./hc-vault-connection-enums";
const InstanceUrlSchema = z
.string()
.trim()
.min(1, "Instance URL required")
.url("Invalid Instance URL")
.describe(AppConnections.CREDENTIALS.HC_VAULT.instanceUrl);
const NamespaceSchema = z.string().trim().optional().describe(AppConnections.CREDENTIALS.HC_VAULT.namespace);
export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({
instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
accessToken: z
.string()
.trim()
.min(1, "Access Token required")
.describe(AppConnections.CREDENTIALS.HC_VAULT.accessToken)
});
export const HCVaultConnectionAppRoleCredentialsSchema = z.object({
instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
roleId: z.string().trim().min(1, "Role ID required").describe(AppConnections.CREDENTIALS.HC_VAULT.roleId),
secretId: z.string().trim().min(1, "Secret ID required").describe(AppConnections.CREDENTIALS.HC_VAULT.secretId)
});
const BaseHCVaultConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.HCVault) });
export const HCVaultConnectionSchema = z.intersection(
BaseHCVaultConnectionSchema,
z.discriminatedUnion("method", [
z.object({
method: z.literal(HCVaultConnectionMethod.AccessToken),
credentials: HCVaultConnectionAccessTokenCredentialsSchema
}),
z.object({
method: z.literal(HCVaultConnectionMethod.AppRole),
credentials: HCVaultConnectionAppRoleCredentialsSchema
})
])
);
export const SanitizedHCVaultConnectionSchema = z.discriminatedUnion("method", [
BaseHCVaultConnectionSchema.extend({
method: z.literal(HCVaultConnectionMethod.AccessToken),
credentials: HCVaultConnectionAccessTokenCredentialsSchema.pick({})
}),
BaseHCVaultConnectionSchema.extend({
method: z.literal(HCVaultConnectionMethod.AppRole),
credentials: HCVaultConnectionAppRoleCredentialsSchema.pick({})
})
]);
export const ValidateHCVaultConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(HCVaultConnectionMethod.AccessToken)
.describe(AppConnections.CREATE(AppConnection.HCVault).method),
credentials: HCVaultConnectionAccessTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.HCVault).credentials
)
}),
z.object({
method: z.literal(HCVaultConnectionMethod.AppRole).describe(AppConnections.CREATE(AppConnection.HCVault).method),
credentials: HCVaultConnectionAppRoleCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.HCVault).credentials
)
})
]);
export const CreateHCVaultConnectionSchema = ValidateHCVaultConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.HCVault)
);
export const UpdateHCVaultConnectionSchema = z
.object({
credentials: z
.union([HCVaultConnectionAccessTokenCredentialsSchema, HCVaultConnectionAppRoleCredentialsSchema])
.optional()
.describe(AppConnections.UPDATE(AppConnection.HCVault).credentials)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.HCVault));
export const HCVaultConnectionListItemSchema = z.object({
name: z.literal("HCVault"),
app: z.literal(AppConnection.HCVault),
methods: z.nativeEnum(HCVaultConnectionMethod).array()
});
@@ -0,0 +1,30 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listHCVaultMounts } from "./hc-vault-connection-fns";
import { THCVaultConnection } from "./hc-vault-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<THCVaultConnection>;
export const hcVaultConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listMounts = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.HCVault, connectionId, actor);
try {
const mounts = await listHCVaultMounts(appConnection);
return mounts;
} catch (error) {
logger.error(error, "Failed to establish connection with Hashicorp Vault");
return [];
}
};
return {
listMounts
};
};
@@ -0,0 +1,35 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateHCVaultConnectionSchema,
HCVaultConnectionSchema,
ValidateHCVaultConnectionCredentialsSchema
} from "./hc-vault-connection-schemas";
export type THCVaultConnection = z.infer<typeof HCVaultConnectionSchema>;
export type THCVaultConnectionInput = z.infer<typeof CreateHCVaultConnectionSchema> & {
app: AppConnection.HCVault;
};
export type TValidateHCVaultConnectionCredentialsSchema = typeof ValidateHCVaultConnectionCredentialsSchema;
export type TValidateHCVaultConnectionCredentials = z.infer<typeof ValidateHCVaultConnectionCredentialsSchema>;
export type THCVaultConnectionConfig = DiscriminativePick<THCVaultConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type THCVaultMountResponse = {
data: {
[key: string]: {
options: {
version?: string | null;
} | null;
type: string; // We're only interested in "kv" types
};
};
};
@@ -0,0 +1,4 @@
export * from "./hc-vault-connection-enums";
export * from "./hc-vault-connection-fns";
export * from "./hc-vault-connection-schemas";
export * from "./hc-vault-connection-types";
@@ -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 HC_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Hashicorp Vault",
destination: SecretSync.HCVault,
connection: AppConnection.HCVault,
canImportSecrets: true
};
@@ -0,0 +1,161 @@
import { isAxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { removeTrailingSlash } from "@app/lib/fn";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { getHCVaultAccessToken, getHCVaultInstanceUrl } from "@app/services/app-connection/hc-vault";
import {
THCVaultListVariables,
THCVaultListVariablesResponse,
THCVaultSyncWithCredentials,
TPostHCVaultVariable
} from "@app/services/secret-sync/hc-vault/hc-vault-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
const listHCVaultVariables = async ({ instanceUrl, namespace, mount, accessToken, path }: THCVaultListVariables) => {
await blockLocalAndPrivateIpAddresses(instanceUrl);
try {
const { data } = await request.get<THCVaultListVariablesResponse>(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{
headers: {
"X-Vault-Token": accessToken,
...(namespace ? { "X-Vault-Namespace": namespace } : {})
}
}
);
return data.data.data;
} catch (error: unknown) {
// Returning an empty set when a path isn't found allows that path to be created by a later POST request
if (isAxiosError(error) && error.response?.status === 404) {
return {};
}
throw error;
}
};
// Hashicorp Vault updates all variables in one batch. This is to respect their versioning
const updateHCVaultVariables = async ({
path,
instanceUrl,
namespace,
accessToken,
mount,
data
}: TPostHCVaultVariable) => {
await blockLocalAndPrivateIpAddresses(instanceUrl);
return request.post(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{
data
},
{
headers: {
"X-Vault-Token": accessToken,
...(namespace ? { "X-Vault-Namespace": namespace } : {}),
"Content-Type": "application/json"
}
}
);
};
export const HCVaultSyncFns = {
syncSecrets: async (secretSync: THCVaultSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
destinationConfig: { mount, path },
syncOptions: { disableSecretDeletion }
} = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({
instanceUrl,
accessToken,
namespace,
mount,
path
});
let tainted = false;
for (const entry of Object.entries(secretMap)) {
const [key, { value }] = entry;
if (value !== variables[key]) {
variables[key] = value;
tainted = true;
}
}
if (disableSecretDeletion) return;
for await (const [key] of Object.entries(variables)) {
if (!(key in secretMap)) {
delete variables[key];
tainted = true;
}
}
// Only update variables if there was a change detected
if (!tainted) return;
try {
await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) {
throw new SecretSyncError({
error
});
}
},
removeSecrets: async (secretSync: THCVaultSyncWithCredentials, secretMap: TSecretMap) => {
const {
connection,
destinationConfig: { mount, path }
} = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({ instanceUrl, namespace, accessToken, mount, path });
for await (const [key] of Object.entries(variables)) {
if (key in secretMap) {
delete variables[key];
}
}
try {
await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) {
throw new SecretSyncError({
error
});
}
},
getSecrets: async (secretSync: THCVaultSyncWithCredentials) => {
const {
connection,
destinationConfig: { mount, path }
} = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({
instanceUrl,
namespace,
accessToken,
mount,
path
});
return Object.fromEntries(Object.entries(variables).map(([key, value]) => [key, { value }]));
}
};
@@ -0,0 +1,58 @@
import RE2 from "re2";
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 HCVaultSyncDestinationConfigSchema = z.object({
mount: z
.string()
.trim()
.min(1, "Secrets Engine Mount required")
.describe(SecretSyncs.DESTINATION_CONFIG.HC_VAULT.mount),
path: z
.string()
.trim()
.min(1, "Path required")
.transform((val) => val.replace(/^\/+|\/+$/g, "")) // removes leading/trailing slashes
.refine((val) => new RE2("^([a-zA-Z0-9._-]+/)*[a-zA-Z0-9._-]+$").test(val), {
message:
"Invalid Vault path format. Use alphanumerics, dots, dashes, underscores, and single slashes between segments."
})
.describe(SecretSyncs.DESTINATION_CONFIG.HC_VAULT.path)
});
const HCVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
export const HCVaultSyncSchema = BaseSecretSyncSchema(SecretSync.HCVault, HCVaultSyncOptionsConfig).extend({
destination: z.literal(SecretSync.HCVault),
destinationConfig: HCVaultSyncDestinationConfigSchema
});
export const CreateHCVaultSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.HCVault,
HCVaultSyncOptionsConfig
).extend({
destinationConfig: HCVaultSyncDestinationConfigSchema
});
export const UpdateHCVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.HCVault,
HCVaultSyncOptionsConfig
).extend({
destinationConfig: HCVaultSyncDestinationConfigSchema.optional()
});
export const HCVaultSyncListItemSchema = z.object({
name: z.literal("Hashicorp Vault"),
connection: z.literal(AppConnection.HCVault),
destination: z.literal(SecretSync.HCVault),
canImportSecrets: z.literal(true)
});
@@ -0,0 +1,39 @@
import { z } from "zod";
import { THCVaultConnection } from "@app/services/app-connection/hc-vault";
import { CreateHCVaultSyncSchema, HCVaultSyncListItemSchema, HCVaultSyncSchema } from "./hc-vault-sync-schemas";
export type THCVaultSync = z.infer<typeof HCVaultSyncSchema>;
export type THCVaultSyncInput = z.infer<typeof CreateHCVaultSyncSchema>;
export type THCVaultSyncListItem = z.infer<typeof HCVaultSyncListItemSchema>;
export type THCVaultSyncWithCredentials = THCVaultSync & {
connection: THCVaultConnection;
};
export type THCVaultListVariablesResponse = {
data: {
data: {
[key: string]: string;
};
};
};
export type THCVaultListVariables = {
accessToken: string;
instanceUrl: string;
namespace?: string;
mount: string;
path: string;
};
export type TPostHCVaultVariable = THCVaultListVariables & {
data: {
[key: string]: string;
};
};
export type TDeleteHCVaultVariable = THCVaultListVariables;
@@ -0,0 +1,4 @@
export * from "./hc-vault-sync-constants";
export * from "./hc-vault-sync-fns";
export * from "./hc-vault-sync-schemas";
export * from "./hc-vault-sync-types";
@@ -11,6 +11,7 @@ export enum SecretSync {
Camunda = "camunda",
Vercel = "vercel",
Windmill = "windmill",
HCVault = "hashicorp-vault",
TeamCity = "teamcity"
}
@@ -25,6 +25,7 @@ import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./az
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault";
import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
@@ -45,6 +46,7 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.Camunda]: CAMUNDA_SYNC_LIST_OPTION,
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION,
[SecretSync.HCVault]: HC_VAULT_SYNC_LIST_OPTION,
[SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION
};
@@ -142,6 +144,8 @@ export const SecretSyncFns = {
return VercelSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.Windmill:
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.HCVault:
return HCVaultSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.TeamCity:
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
default:
@@ -203,6 +207,9 @@ export const SecretSyncFns = {
case SecretSync.Windmill:
secretMap = await WindmillSyncFns.getSecrets(secretSync);
break;
case SecretSync.HCVault:
secretMap = await HCVaultSyncFns.getSecrets(secretSync);
break;
case SecretSync.TeamCity:
secretMap = await TeamCitySyncFns.getSecrets(secretSync);
break;
@@ -259,6 +266,8 @@ export const SecretSyncFns = {
return VercelSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.Windmill:
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.HCVault:
return HCVaultSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.TeamCity:
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
default:
@@ -14,6 +14,7 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.Camunda]: "Camunda",
[SecretSync.Vercel]: "Vercel",
[SecretSync.Windmill]: "Windmill",
[SecretSync.HCVault]: "Hashicorp Vault",
[SecretSync.TeamCity]: "TeamCity"
};
@@ -30,5 +31,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.Camunda]: AppConnection.Camunda,
[SecretSync.Vercel]: AppConnection.Vercel,
[SecretSync.Windmill]: AppConnection.Windmill,
[SecretSync.HCVault]: AppConnection.HCVault,
[SecretSync.TeamCity]: AppConnection.TeamCity
};
@@ -55,6 +55,12 @@ import {
TAzureKeyVaultSyncWithCredentials
} from "./azure-key-vault";
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
import {
THCVaultSync,
THCVaultSyncInput,
THCVaultSyncListItem,
THCVaultSyncWithCredentials
} from "./hc-vault/hc-vault-sync-types";
import {
THumanitecSync,
THumanitecSyncInput,
@@ -88,6 +94,7 @@ export type TSecretSync =
| TCamundaSync
| TVercelSync
| TWindmillSync
| THCVaultSync
| TTeamCitySync;
export type TSecretSyncWithCredentials =
@@ -103,6 +110,7 @@ export type TSecretSyncWithCredentials =
| TCamundaSyncWithCredentials
| TVercelSyncWithCredentials
| TWindmillSyncWithCredentials
| THCVaultSyncWithCredentials
| TTeamCitySyncWithCredentials;
export type TSecretSyncInput =
@@ -118,6 +126,7 @@ export type TSecretSyncInput =
| TCamundaSyncInput
| TVercelSyncInput
| TWindmillSyncInput
| THCVaultSyncInput
| TTeamCitySyncInput;
export type TSecretSyncListItem =
@@ -133,6 +142,7 @@ export type TSecretSyncListItem =
| TCamundaSyncListItem
| TVercelSyncListItem
| TWindmillSyncListItem
| THCVaultSyncListItem
| TTeamCitySyncListItem;
export type TSyncOptionsConfig = {
@@ -7,6 +7,7 @@ import { ProjectType, SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecre
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
import { applyJitter } from "@app/lib/dates";
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
import {
buildFindFilter,
@@ -22,7 +23,6 @@ import type {
TFindSecretsByFolderIdsFilter,
TGetSecretsDTO
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
import { applyJitter } from "@app/lib/dates";
export const SecretServiceCacheKeys = {
get productKey() {