Merge pull request #3487 from Infisical/ENG-2633
feat(secret-sync): Hashicorp Vault App Connection & Secret Sync
@@ -1862,6 +1862,13 @@ export const AppConnections = {
|
|||||||
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
instanceUrl: "The Windmill instance URL to connect with (defaults to https://app.windmill.dev).",
|
||||||
accessToken: "The access token to use to connect with Windmill."
|
accessToken: "The access token to use to connect with Windmill."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
instanceUrl: "The Hashicrop Vault instance URL to connect with.",
|
||||||
|
namespace: "The Hashicrop Vault namespace to connect with.",
|
||||||
|
accessToken: "The access token used to connect with Hashicorp Vault.",
|
||||||
|
roleId: "The Role ID used to connect with Hashicorp Vault.",
|
||||||
|
secretId: "The Secret ID used to connect with Hashicorp Vault."
|
||||||
|
},
|
||||||
LDAP: {
|
LDAP: {
|
||||||
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
provider: "The type of LDAP provider. Determines provider-specific behaviors.",
|
||||||
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
url: "The LDAP/LDAPS URL to connect to (e.g., 'ldap://domain-or-ip:389' or 'ldaps://domain-or-ip:636').",
|
||||||
@@ -2019,6 +2026,10 @@ export const SecretSyncs = {
|
|||||||
workspace: "The Windmill workspace to sync secrets to.",
|
workspace: "The Windmill workspace to sync secrets to.",
|
||||||
path: "The Windmill workspace path to sync secrets to."
|
path: "The Windmill workspace path to sync secrets to."
|
||||||
},
|
},
|
||||||
|
HC_VAULT: {
|
||||||
|
mount: "The Hashicorp Vault Secrets Engine Mount to sync secrets to.",
|
||||||
|
path: "The Hashicorp Vault path to sync secrets to."
|
||||||
|
},
|
||||||
TEAMCITY: {
|
TEAMCITY: {
|
||||||
project: "The TeamCity project to sync secrets to.",
|
project: "The TeamCity project to sync secrets to.",
|
||||||
buildConfig: "The TeamCity build configuration to sync secrets to."
|
buildConfig: "The TeamCity build configuration to sync secrets to."
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ import {
|
|||||||
} from "@app/services/app-connection/databricks";
|
} from "@app/services/app-connection/databricks";
|
||||||
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
||||||
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
||||||
|
import {
|
||||||
|
HCVaultConnectionListItemSchema,
|
||||||
|
SanitizedHCVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/hc-vault";
|
||||||
import {
|
import {
|
||||||
HumanitecConnectionListItemSchema,
|
HumanitecConnectionListItemSchema,
|
||||||
SanitizedHumanitecConnectionSchema
|
SanitizedHumanitecConnectionSchema
|
||||||
@@ -68,6 +72,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedMsSqlConnectionSchema.options,
|
...SanitizedMsSqlConnectionSchema.options,
|
||||||
...SanitizedCamundaConnectionSchema.options,
|
...SanitizedCamundaConnectionSchema.options,
|
||||||
...SanitizedAuth0ConnectionSchema.options,
|
...SanitizedAuth0ConnectionSchema.options,
|
||||||
|
...SanitizedHCVaultConnectionSchema.options,
|
||||||
...SanitizedAzureClientSecretsConnectionSchema.options,
|
...SanitizedAzureClientSecretsConnectionSchema.options,
|
||||||
...SanitizedWindmillConnectionSchema.options,
|
...SanitizedWindmillConnectionSchema.options,
|
||||||
...SanitizedLdapConnectionSchema.options,
|
...SanitizedLdapConnectionSchema.options,
|
||||||
@@ -88,6 +93,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
MsSqlConnectionListItemSchema,
|
MsSqlConnectionListItemSchema,
|
||||||
CamundaConnectionListItemSchema,
|
CamundaConnectionListItemSchema,
|
||||||
Auth0ConnectionListItemSchema,
|
Auth0ConnectionListItemSchema,
|
||||||
|
HCVaultConnectionListItemSchema,
|
||||||
AzureClientSecretsConnectionListItemSchema,
|
AzureClientSecretsConnectionListItemSchema,
|
||||||
WindmillConnectionListItemSchema,
|
WindmillConnectionListItemSchema,
|
||||||
LdapConnectionListItemSchema,
|
LdapConnectionListItemSchema,
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateHCVaultConnectionSchema,
|
||||||
|
SanitizedHCVaultConnectionSchema,
|
||||||
|
UpdateHCVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/hc-vault";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerHCVaultConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.HCVault,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedHCVaultConnectionSchema,
|
||||||
|
createSchema: CreateHCVaultConnectionSchema,
|
||||||
|
updateSchema: UpdateHCVaultConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The following endpoints are for internal Infisical App use only and not part of the public API
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/mounts`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string().array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const mounts = await server.services.appConnection.hcvault.listMounts(connectionId, req.permission);
|
||||||
|
return mounts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
|
|||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
|
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
|
||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
||||||
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
import { registerLdapConnectionRouter } from "./ldap-connection-router";
|
||||||
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
|
||||||
@@ -37,6 +38,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
[AppConnection.Camunda]: registerCamundaConnectionRouter,
|
||||||
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
[AppConnection.Windmill]: registerWindmillConnectionRouter,
|
||||||
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
|
||||||
|
[AppConnection.HCVault]: registerHCVaultConnectionRouter,
|
||||||
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
[AppConnection.LDAP]: registerLdapConnectionRouter,
|
||||||
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
[AppConnection.TeamCity]: registerTeamCityConnectionRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
CreateHCVaultSyncSchema,
|
||||||
|
HCVaultSyncSchema,
|
||||||
|
UpdateHCVaultSyncSchema
|
||||||
|
} from "@app/services/secret-sync/hc-vault";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerHCVaultSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.HCVault,
|
||||||
|
server,
|
||||||
|
responseSchema: HCVaultSyncSchema,
|
||||||
|
createSchema: CreateHCVaultSyncSchema,
|
||||||
|
updateSchema: UpdateHCVaultSyncSchema
|
||||||
|
});
|
||||||
@@ -8,6 +8,7 @@ import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
|||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-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";
|
||||||
|
import { registerHCVaultSyncRouter } from "./hc-vault-sync-router";
|
||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
||||||
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
import { registerTeamCitySyncRouter } from "./teamcity-sync-router";
|
||||||
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router";
|
||||||
@@ -29,5 +30,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
[SecretSync.Camunda]: registerCamundaSyncRouter,
|
||||||
[SecretSync.Vercel]: registerVercelSyncRouter,
|
[SecretSync.Vercel]: registerVercelSyncRouter,
|
||||||
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
[SecretSync.Windmill]: registerWindmillSyncRouter,
|
||||||
|
[SecretSync.HCVault]: registerHCVaultSyncRouter,
|
||||||
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
[SecretSync.TeamCity]: registerTeamCitySyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secr
|
|||||||
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
||||||
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";
|
||||||
|
import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault";
|
||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
||||||
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity";
|
||||||
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud";
|
||||||
@@ -41,6 +42,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
CamundaSyncSchema,
|
CamundaSyncSchema,
|
||||||
VercelSyncSchema,
|
VercelSyncSchema,
|
||||||
WindmillSyncSchema,
|
WindmillSyncSchema,
|
||||||
|
HCVaultSyncSchema,
|
||||||
TeamCitySyncSchema
|
TeamCitySyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
CamundaSyncListItemSchema,
|
CamundaSyncListItemSchema,
|
||||||
VercelSyncListItemSchema,
|
VercelSyncListItemSchema,
|
||||||
WindmillSyncListItemSchema,
|
WindmillSyncListItemSchema,
|
||||||
|
HCVaultSyncListItemSchema,
|
||||||
TeamCitySyncListItemSchema
|
TeamCitySyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export enum AppConnection {
|
|||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
Auth0 = "auth0",
|
Auth0 = "auth0",
|
||||||
|
HCVault = "hashicorp-vault",
|
||||||
LDAP = "ldap",
|
LDAP = "ldap",
|
||||||
TeamCity = "teamcity"
|
TeamCity = "teamcity"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ import {
|
|||||||
} from "./databricks";
|
} from "./databricks";
|
||||||
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
|
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
|
||||||
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
|
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
|
||||||
|
import {
|
||||||
|
getHCVaultConnectionListItem,
|
||||||
|
HCVaultConnectionMethod,
|
||||||
|
validateHCVaultConnectionCredentials
|
||||||
|
} from "./hc-vault";
|
||||||
import {
|
import {
|
||||||
getHumanitecConnectionListItem,
|
getHumanitecConnectionListItem,
|
||||||
HumanitecConnectionMethod,
|
HumanitecConnectionMethod,
|
||||||
@@ -84,6 +89,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getAzureClientSecretsConnectionListItem(),
|
getAzureClientSecretsConnectionListItem(),
|
||||||
getWindmillConnectionListItem(),
|
getWindmillConnectionListItem(),
|
||||||
getAuth0ConnectionListItem(),
|
getAuth0ConnectionListItem(),
|
||||||
|
getHCVaultConnectionListItem(),
|
||||||
getLdapConnectionListItem(),
|
getLdapConnectionListItem(),
|
||||||
getTeamCityConnectionListItem()
|
getTeamCityConnectionListItem()
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
@@ -152,6 +158,7 @@ export const validateAppConnectionCredentials = async (
|
|||||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
|
[AppConnection.HCVault]: validateHCVaultConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
[AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||||
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
|
[AppConnection.TeamCity]: validateTeamCityConnectionCredentials as TAppConnectionCredentialsValidator
|
||||||
};
|
};
|
||||||
@@ -186,10 +193,13 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
return "Username & Password";
|
return "Username & Password";
|
||||||
case WindmillConnectionMethod.AccessToken:
|
case WindmillConnectionMethod.AccessToken:
|
||||||
|
case HCVaultConnectionMethod.AccessToken:
|
||||||
case TeamCityConnectionMethod.AccessToken:
|
case TeamCityConnectionMethod.AccessToken:
|
||||||
return "Access Token";
|
return "Access Token";
|
||||||
case Auth0ConnectionMethod.ClientCredentials:
|
case Auth0ConnectionMethod.ClientCredentials:
|
||||||
return "Client Credentials";
|
return "Client Credentials";
|
||||||
|
case HCVaultConnectionMethod.AppRole:
|
||||||
|
return "App Role";
|
||||||
case LdapConnectionMethod.SimpleBind:
|
case LdapConnectionMethod.SimpleBind:
|
||||||
return "Simple Bind";
|
return "Simple Bind";
|
||||||
default:
|
default:
|
||||||
@@ -238,6 +248,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
|||||||
[AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported,
|
[AppConnection.AzureClientSecrets]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||||
|
[AppConnection.HCVault]: platformManagedCredentialsNotSupported,
|
||||||
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
[AppConnection.LDAP]: platformManagedCredentialsNotSupported, // we could support this in the future
|
||||||
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
|
[AppConnection.TeamCity]: platformManagedCredentialsNotSupported
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.Camunda]: "Camunda",
|
[AppConnection.Camunda]: "Camunda",
|
||||||
[AppConnection.Windmill]: "Windmill",
|
[AppConnection.Windmill]: "Windmill",
|
||||||
[AppConnection.Auth0]: "Auth0",
|
[AppConnection.Auth0]: "Auth0",
|
||||||
|
[AppConnection.HCVault]: "Hashicorp Vault",
|
||||||
[AppConnection.LDAP]: "LDAP",
|
[AppConnection.LDAP]: "LDAP",
|
||||||
[AppConnection.TeamCity]: "TeamCity"
|
[AppConnection.TeamCity]: "TeamCity"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
|||||||
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||||
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
||||||
import { githubConnectionService } from "./github/github-connection-service";
|
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 { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||||
@@ -81,6 +83,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
[AppConnection.AzureClientSecrets]: ValidateAzureClientSecretsConnectionCredentialsSchema,
|
||||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||||
|
[AppConnection.HCVault]: ValidateHCVaultConnectionCredentialsSchema,
|
||||||
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
[AppConnection.LDAP]: ValidateLdapConnectionCredentialsSchema,
|
||||||
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
|
[AppConnection.TeamCity]: ValidateTeamCityConnectionCredentialsSchema
|
||||||
};
|
};
|
||||||
@@ -459,6 +462,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
vercel: vercelConnectionService(connectAppConnectionById),
|
vercel: vercelConnectionService(connectAppConnectionById),
|
||||||
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
azureClientSecrets: azureClientSecretsConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
auth0: auth0ConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
|
hcvault: hcVaultConnectionService(connectAppConnectionById),
|
||||||
windmill: windmillConnectionService(connectAppConnectionById),
|
windmill: windmillConnectionService(connectAppConnectionById),
|
||||||
teamcity: teamcityConnectionService(connectAppConnectionById)
|
teamcity: teamcityConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ import {
|
|||||||
TGitHubConnectionInput,
|
TGitHubConnectionInput,
|
||||||
TValidateGitHubConnectionCredentialsSchema
|
TValidateGitHubConnectionCredentialsSchema
|
||||||
} from "./github";
|
} from "./github";
|
||||||
|
import {
|
||||||
|
THCVaultConnection,
|
||||||
|
THCVaultConnectionConfig,
|
||||||
|
THCVaultConnectionInput,
|
||||||
|
TValidateHCVaultConnectionCredentialsSchema
|
||||||
|
} from "./hc-vault";
|
||||||
import {
|
import {
|
||||||
THumanitecConnection,
|
THumanitecConnection,
|
||||||
THumanitecConnectionConfig,
|
THumanitecConnectionConfig,
|
||||||
@@ -116,6 +122,7 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TAzureClientSecretsConnection
|
| TAzureClientSecretsConnection
|
||||||
| TWindmillConnection
|
| TWindmillConnection
|
||||||
| TAuth0Connection
|
| TAuth0Connection
|
||||||
|
| THCVaultConnection
|
||||||
| TLdapConnection
|
| TLdapConnection
|
||||||
| TTeamCityConnection
|
| TTeamCityConnection
|
||||||
);
|
);
|
||||||
@@ -140,6 +147,7 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TAzureClientSecretsConnectionInput
|
| TAzureClientSecretsConnectionInput
|
||||||
| TWindmillConnectionInput
|
| TWindmillConnectionInput
|
||||||
| TAuth0ConnectionInput
|
| TAuth0ConnectionInput
|
||||||
|
| THCVaultConnectionInput
|
||||||
| TLdapConnectionInput
|
| TLdapConnectionInput
|
||||||
| TTeamCityConnectionInput
|
| TTeamCityConnectionInput
|
||||||
);
|
);
|
||||||
@@ -170,6 +178,7 @@ export type TAppConnectionConfig =
|
|||||||
| TVercelConnectionConfig
|
| TVercelConnectionConfig
|
||||||
| TWindmillConnectionConfig
|
| TWindmillConnectionConfig
|
||||||
| TAuth0ConnectionConfig
|
| TAuth0ConnectionConfig
|
||||||
|
| THCVaultConnectionConfig
|
||||||
| TLdapConnectionConfig
|
| TLdapConnectionConfig
|
||||||
| TTeamCityConnectionConfig;
|
| TTeamCityConnectionConfig;
|
||||||
|
|
||||||
@@ -189,6 +198,7 @@ export type TValidateAppConnectionCredentialsSchema =
|
|||||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||||
| TValidateWindmillConnectionCredentialsSchema
|
| TValidateWindmillConnectionCredentialsSchema
|
||||||
| TValidateAuth0ConnectionCredentialsSchema
|
| TValidateAuth0ConnectionCredentialsSchema
|
||||||
|
| TValidateHCVaultConnectionCredentialsSchema
|
||||||
| TValidateLdapConnectionCredentialsSchema
|
| TValidateLdapConnectionCredentialsSchema
|
||||||
| TValidateTeamCityConnectionCredentialsSchema;
|
| 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",
|
Camunda = "camunda",
|
||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
|
HCVault = "hashicorp-vault",
|
||||||
TeamCity = "teamcity"
|
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 { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||||
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
||||||
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
|
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 { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec";
|
||||||
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns";
|
||||||
import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity";
|
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.Camunda]: CAMUNDA_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
|
[SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION,
|
[SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.HCVault]: HC_VAULT_SYNC_LIST_OPTION,
|
||||||
[SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION
|
[SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,6 +144,8 @@ export const SecretSyncFns = {
|
|||||||
return VercelSyncFns.syncSecrets(secretSync, secretMap);
|
return VercelSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
|
return WindmillSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.HCVault:
|
||||||
|
return HCVaultSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
|
return TeamCitySyncFns.syncSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
@@ -203,6 +207,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
secretMap = await WindmillSyncFns.getSecrets(secretSync);
|
secretMap = await WindmillSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.HCVault:
|
||||||
|
secretMap = await HCVaultSyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
secretMap = await TeamCitySyncFns.getSecrets(secretSync);
|
secretMap = await TeamCitySyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
@@ -259,6 +266,8 @@ export const SecretSyncFns = {
|
|||||||
return VercelSyncFns.removeSecrets(secretSync, secretMap);
|
return VercelSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
|
return WindmillSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
|
case SecretSync.HCVault:
|
||||||
|
return HCVaultSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
|
return TeamCitySyncFns.removeSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.Camunda]: "Camunda",
|
[SecretSync.Camunda]: "Camunda",
|
||||||
[SecretSync.Vercel]: "Vercel",
|
[SecretSync.Vercel]: "Vercel",
|
||||||
[SecretSync.Windmill]: "Windmill",
|
[SecretSync.Windmill]: "Windmill",
|
||||||
|
[SecretSync.HCVault]: "Hashicorp Vault",
|
||||||
[SecretSync.TeamCity]: "TeamCity"
|
[SecretSync.TeamCity]: "TeamCity"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -30,5 +31,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Camunda]: AppConnection.Camunda,
|
[SecretSync.Camunda]: AppConnection.Camunda,
|
||||||
[SecretSync.Vercel]: AppConnection.Vercel,
|
[SecretSync.Vercel]: AppConnection.Vercel,
|
||||||
[SecretSync.Windmill]: AppConnection.Windmill,
|
[SecretSync.Windmill]: AppConnection.Windmill,
|
||||||
|
[SecretSync.HCVault]: AppConnection.HCVault,
|
||||||
[SecretSync.TeamCity]: AppConnection.TeamCity
|
[SecretSync.TeamCity]: AppConnection.TeamCity
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ import {
|
|||||||
TAzureKeyVaultSyncWithCredentials
|
TAzureKeyVaultSyncWithCredentials
|
||||||
} from "./azure-key-vault";
|
} from "./azure-key-vault";
|
||||||
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
||||||
|
import {
|
||||||
|
THCVaultSync,
|
||||||
|
THCVaultSyncInput,
|
||||||
|
THCVaultSyncListItem,
|
||||||
|
THCVaultSyncWithCredentials
|
||||||
|
} from "./hc-vault/hc-vault-sync-types";
|
||||||
import {
|
import {
|
||||||
THumanitecSync,
|
THumanitecSync,
|
||||||
THumanitecSyncInput,
|
THumanitecSyncInput,
|
||||||
@@ -88,6 +94,7 @@ export type TSecretSync =
|
|||||||
| TCamundaSync
|
| TCamundaSync
|
||||||
| TVercelSync
|
| TVercelSync
|
||||||
| TWindmillSync
|
| TWindmillSync
|
||||||
|
| THCVaultSync
|
||||||
| TTeamCitySync;
|
| TTeamCitySync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
@@ -103,6 +110,7 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TCamundaSyncWithCredentials
|
| TCamundaSyncWithCredentials
|
||||||
| TVercelSyncWithCredentials
|
| TVercelSyncWithCredentials
|
||||||
| TWindmillSyncWithCredentials
|
| TWindmillSyncWithCredentials
|
||||||
|
| THCVaultSyncWithCredentials
|
||||||
| TTeamCitySyncWithCredentials;
|
| TTeamCitySyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
@@ -118,6 +126,7 @@ export type TSecretSyncInput =
|
|||||||
| TCamundaSyncInput
|
| TCamundaSyncInput
|
||||||
| TVercelSyncInput
|
| TVercelSyncInput
|
||||||
| TWindmillSyncInput
|
| TWindmillSyncInput
|
||||||
|
| THCVaultSyncInput
|
||||||
| TTeamCitySyncInput;
|
| TTeamCitySyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
@@ -133,6 +142,7 @@ export type TSecretSyncListItem =
|
|||||||
| TCamundaSyncListItem
|
| TCamundaSyncListItem
|
||||||
| TVercelSyncListItem
|
| TVercelSyncListItem
|
||||||
| TWindmillSyncListItem
|
| TWindmillSyncListItem
|
||||||
|
| THCVaultSyncListItem
|
||||||
| TTeamCitySyncListItem;
|
| TTeamCitySyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { ProjectType, SecretsV2Schema, SecretType, TableName, TSecretsV2, TSecre
|
|||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
|
import { generateCacheKeyFromData } from "@app/lib/crypto/cache";
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import {
|
import {
|
||||||
buildFindFilter,
|
buildFindFilter,
|
||||||
@@ -22,7 +23,6 @@ import type {
|
|||||||
TFindSecretsByFolderIdsFilter,
|
TFindSecretsByFolderIdsFilter,
|
||||||
TGetSecretsDTO
|
TGetSecretsDTO
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { applyJitter } from "@app/lib/dates";
|
|
||||||
|
|
||||||
export const SecretServiceCacheKeys = {
|
export const SecretServiceCacheKeys = {
|
||||||
get productKey() {
|
get productKey() {
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Available"
|
||||||
|
openapi: "GET /api/v1/app-connections/hashicorp-vault/available"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/app-connections/hashicorp-vault"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Hashicorp Vault Connections](/integrations/app-connections/hashicorp-vault) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/app-connections/hashicorp-vault/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/app-connections/hashicorp-vault/{connectionId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/app-connections/hashicorp-vault/connection-name/{connectionName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/app-connections/hashicorp-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/app-connections/hashicorp-vault/{connectionId}"
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Check out the configuration docs for [Hashicorp Vault Connections](/integrations/app-connections/hashicorp-vault) to learn how to obtain the required credentials.
|
||||||
|
</Note>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/hashicorp-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/hashicorp-vault/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/hashicorp-vault/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/hashicorp-vault/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Import Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/hashicorp-vault/{syncId}/import-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/hashicorp-vault"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/hashicorp-vault/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/hashicorp-vault/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/hashicorp-vault/{syncId}"
|
||||||
|
---
|
||||||
|
After Width: | Height: | Size: 400 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 495 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 643 KiB |
|
After Width: | Height: | Size: 726 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 399 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 608 KiB |
|
Before Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 770 KiB |
|
Before Width: | Height: | Size: 868 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 641 KiB |
|
Before Width: | Height: | Size: 753 KiB |
|
Before Width: | Height: | Size: 545 KiB |
|
Before Width: | Height: | Size: 599 KiB |
|
After Width: | Height: | Size: 687 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 655 KiB |
|
After Width: | Height: | Size: 634 KiB |
|
After Width: | Height: | Size: 656 KiB |
|
After Width: | Height: | Size: 662 KiB |
|
After Width: | Height: | Size: 626 KiB |
@@ -0,0 +1,214 @@
|
|||||||
|
---
|
||||||
|
title: "Hashicorp Vault Connection"
|
||||||
|
description: "Learn how to configure a Hashicorp Vault Connection for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Infisical is compatible with Vault Self-hosted, HCP Vault Dedicated, and HCP Vault Enterprise deployments. Please note that HCP Generic Secrets are currently not supported.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
Infisical supports two methods for connecting to Hashicorp Vault.
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="App Role (Recommended)">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to Vault Access">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Enable New Method">
|
||||||
|
In the **Authentication Methods** tab, click on **Enable new method**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select AppRole">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Enable Method">
|
||||||
|
You may change the name of the method, but we suggest keeping it as `approle`.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Navigate to Vault Policies">
|
||||||
|
From the home page, navigate to **Policies**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Create ACL Policy">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Create Policy">
|
||||||
|
You may name your policy whatever you want, but remember the name as it will be used in future steps.
|
||||||
|
|
||||||
|
Depending on your use case, you may have different policy configurations:
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Secret Sync">
|
||||||
|
```hcl
|
||||||
|
path "demo_mount/data/*" {
|
||||||
|
capabilities = [ "create", "read", "update" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
path "sys/mounts" {
|
||||||
|
capabilities = ["read"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **demo_mount**: The name of the target secrets engine (e.g., 'secret', 'kv').
|
||||||
|
- **data/\***: The path within the secrets engine used for storing secrets. The wildcard (*) grants access to all secrets within this mount point.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Make sure to replace the policy path with the specific path where you intend to sync your secrets. For better security and control, it's recommended to use a more granular path instead of a wildcard (*). You can also specify a path that doesn’t yet exist—Infisical will automatically create it for you during the sync process.
|
||||||
|
</Note>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Run Shell Commands">
|
||||||
|
**Open Vault Shell**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Note>
|
||||||
|
If you used custom approle or policy names in previous steps, you'll need to customize the following commands.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
**Create Infisical Role**
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vault write auth/approle/role/infisical token_policies="infisical-policy" token_ttl=30s token_max_ttl=2m
|
||||||
|
```
|
||||||
|
|
||||||
|
**Read RoleID**
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vault read auth/approle/role/infisical/role-id
|
||||||
|
```
|
||||||
|
|
||||||
|
**Generate New SecretID**
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
vault write -force auth/approle/role/infisical/secret-id
|
||||||
|
```
|
||||||
|
|
||||||
|
Your shell output should look similar to the image below. Save the RoleID and SecretID values for later steps.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Access Token">
|
||||||
|
## Get a Hashicorp Vault Access Token
|
||||||
|
|
||||||
|
Open your profile dropdown and click **Copy token**. This token will be used in later steps.
|
||||||
|
|
||||||
|

|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Getting Vault Instance URL
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Self Hosted">
|
||||||
|
For self-hosted instances, locate and copy your vault's base URL (for example: `https://vault.example.com`).
|
||||||
|
|
||||||
|
Save the URL for later steps.
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Hashicorp Cloud Platform">
|
||||||
|
On HCP instances, you may need to navigate to **Cluster Overview** to see your cluster URL. Save this value for later steps.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Cluster Overview is found in the HCP dashboard, not in your cluster's web UI.
|
||||||
|
</Note>
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Setup Vault Connection in Infisical
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
In your Infisical dashboard, go to **Organization Settings** and select the **App Connections** tab.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Add Connection">
|
||||||
|
Click the **+ Add Connection** button and select the **Hashicorp Vault Connection** option.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Connection">
|
||||||
|
Configure your Vault Connection using the Instance URL and credentials from the steps above. **Depending on if you chose to authenticate with an Access Token or AppRole, you may need to input different information.**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="App Role">
|
||||||
|
- **Name**: The name of the connection being created. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description to provide details about this connection.
|
||||||
|
- **Instance URL**: The URL of your Hashicorp Vault instance.
|
||||||
|
- **Namespace (optional)**: The namespace within your vault. Self-hosted and enterprise clusters may not use namespaces.
|
||||||
|
- **Role ID**: The Role ID generated in the steps above.
|
||||||
|
- **Secret ID**: The Secret ID generated in the steps above.
|
||||||
|
</Tab>
|
||||||
|
<Tab title="Access Token">
|
||||||
|
- **Name**: The name of the connection being created. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description to provide details about this connection.
|
||||||
|
- **Instance URL**: The URL of your Hashicorp Vault instance.
|
||||||
|
- **Namespace (optional)**: The namespace within your vault. Self-hosted and enterprise clusters may not use namespaces.
|
||||||
|
- **Access Token**: The Access Token generated in the steps above.
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</Step>
|
||||||
|
<Step title="Connection Created">
|
||||||
|
Your Vault Connection is now available for use.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create a Vault Connection, make an API request to the [Create Hashicorp Vault
|
||||||
|
Connection](/api-reference/endpoints/app-connections/hashicorp-vault/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/app-connections/hashicorp-vault \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-vault-connection",
|
||||||
|
"method": "app-role",
|
||||||
|
"credentials": {
|
||||||
|
"instanceUrl": "https://vault.example.com",
|
||||||
|
"roleId": "4797c4fa-7794-71f0-c8b1-7c87759df5bf",
|
||||||
|
"secretId": "ad24df93-19c8-c865-9997-6b8513253d3a"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"appConnection": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-vault-connection",
|
||||||
|
"version": 1,
|
||||||
|
"orgId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"createdAt": "2025-04-01T05:31:56Z",
|
||||||
|
"updatedAt": "2025-04-01T05:31:56Z",
|
||||||
|
"app": "hashicorp-vault",
|
||||||
|
"method": "app-role",
|
||||||
|
"credentials": {
|
||||||
|
"instanceUrl": "https://vault.example.com",
|
||||||
|
"roleId": "4797c4fa-7794-71f0-c8b1-7c87759df5bf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -4,158 +4,5 @@ description: "How to sync secrets from Infisical to HashiCorp Vault"
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Infisical connects to Vault via the AppRole auth method.
|
The Hashicorp Vault Native Integration will be deprecated in 2026. Please migrate to our new [Hashicorp Vault Sync](../secret-syncs/hashicorp-vault).
|
||||||
|
|
||||||
Currently, each Infisical project can only point and sync secrets to one Vault cluster / namespace
|
|
||||||
but with unlimited integrations to different paths within it.
|
|
||||||
|
|
||||||
This tutorial makes use of Vault's UI but, in principle, instructions can executed via
|
|
||||||
Vault CLI or API call.
|
|
||||||
|
|
||||||
Lastly, you should note that we provide a simple use-case and, in practice, you should adapt and extend it to your own Vault use-case and follow best practices, for instance when defining fine-grained ACL policies.
|
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
Prerequisites:
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
- Have experience with [HashiCorp Vault](https://www.vaultproject.io/).
|
|
||||||
|
|
||||||
## Navigate to your project's integrations tab
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Prepare Vault
|
|
||||||
|
|
||||||
This section mirrors the latter parts of the [Vault quickstart](https://developer.hashicorp.com/vault/tutorials/cloud/getting-started-intro) provided by HashiCorp and uses sample names/values for demonstration.
|
|
||||||
|
|
||||||
To begin, navigate to the cluster / namespace that you want to sync secrets to in Vault; we'll use the default `admin` namespace (in practice, we recommend creating a namespace and not using the default `admin` namespace).
|
|
||||||
|
|
||||||
### Enable KV Secrets Engine
|
|
||||||
|
|
||||||
In Secrets, enable a KV Secrets Engine at a path for Infisical to sync secrets to; we'll use the path `kv`.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
### Enable the AppRole auth method
|
|
||||||
|
|
||||||
In Access > Auth Methods, enable the AppRole auth method.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
### Create an ACL Policy
|
|
||||||
|
|
||||||
Now in Policies, create a new ACL policy scoped to the path(s) you wish Infisical to be able to sync secrets to.
|
|
||||||
|
|
||||||
We'll call the policy `test` and have it grant access to the `dev` path in the KV Secrets Engine where we will be syncing secrets to from Infisical.
|
|
||||||
|
|
||||||
```console
|
|
||||||
path "kv/data/dev" {
|
|
||||||
capabilities = [ "create", "read", "update" ]
|
|
||||||
}
|
|
||||||
|
|
||||||
path "sys/namespaces/*" {
|
|
||||||
capabilities = [ "create", "read", "update", "delete", "list" ]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
<Note>
|
|
||||||
`kv` comes from the path of the KV Secrets Engine that we enabled and `dev` is the chosen path within it
|
|
||||||
that we want to sync secrets to.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
### Create a role with the policy attached
|
|
||||||
|
|
||||||
We now create a `infisical` role with the generated token's time-to-live (TTL) set to 1 hour and can be renewed for up to 4 hours from the time of its creation.
|
|
||||||
|
|
||||||
1. Click the Vault CLI shell icon (`>_`) to open a command shell in the browser.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
2. Copy the command below.
|
|
||||||
|
|
||||||
```console
|
|
||||||
vault write auth/approle/role/infisical token_policies="test" token_ttl=1h token_max_ttl=4h
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Paste the command into the command shell in the browser and press the enter button.
|
|
||||||
|
|
||||||
### Generate a RoleID and SecretID
|
|
||||||
|
|
||||||
Finally, we need to generate a **RoleID** and **SecretID** (like a username and password) that Infisical can use
|
|
||||||
to authenticate with Vault.
|
|
||||||
|
|
||||||
1. Click the Vault CLI shell icon (>_) again to open a command shell.
|
|
||||||
|
|
||||||
2. Read the RoleID.
|
|
||||||
|
|
||||||
```console
|
|
||||||
vault read auth/approle/role/infisical/role-id
|
|
||||||
```
|
|
||||||
|
|
||||||
Example output:
|
|
||||||
|
|
||||||
```console
|
|
||||||
Key Value
|
|
||||||
role_id b6ccdcca-183b-ce9c-6b98-b556b9a0edb9
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Generate a new SecretID of the `infisical` role.
|
|
||||||
|
|
||||||
```console
|
|
||||||
vault write -force auth/approle/role/infisical/secret-id
|
|
||||||
```
|
|
||||||
|
|
||||||
Example output:
|
|
||||||
|
|
||||||
|
|
||||||
```console
|
|
||||||
Key Value
|
|
||||||
secret_id 735a47cc-7a98-77cc-0128-12b1e96a4157
|
|
||||||
secret_id_accessor 3ab305d1-1eab-df4b-4079-ef7135635c49
|
|
||||||
...snip...
|
|
||||||
```
|
|
||||||
|
|
||||||
Great. We're now ready to connect Infisical to Vault!
|
|
||||||
|
|
||||||
## Enter your Vault instance and authentication details
|
|
||||||
|
|
||||||
Back in Infisical, press on the HashiCorp Vault tile and input your Vault instance and `infisical` role RoleID and SecretID.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
For additional details on each field:
|
|
||||||
|
|
||||||
- Vault Cluster URL: The address of your cluster, either HCP or self-hosted.
|
|
||||||
|
|
||||||
If using HCP, you can copy your Cluster URL in the Cluster Overview:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- Vault Namespace: The Vault namespace you wish to connect to.
|
|
||||||
- Vault RoleID: The RoleID previously created for the `infisical` role.
|
|
||||||
- Vault SecretID: The SecretID previously created for the `infisical` role.
|
|
||||||
|
|
||||||
## Start integration
|
|
||||||
|
|
||||||
Select which Infisical environment secrets you want to sync to Vault.
|
|
||||||
|
|
||||||
For additional details on each field:
|
|
||||||
|
|
||||||
- Vault KV Secrets Engine Path: the path at which you enabled the intended KV Secrets Engine; in this demonstration, we used `kv`.
|
|
||||||
- Vault Secret(s) Path: the path in the KV Secrets Engine that you wish to sync secrets to.
|
|
||||||
|
|
||||||
Press create integration to start syncing secrets to Vault.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
title: "Hashicorp Vault Sync"
|
||||||
|
description: "Learn how to configure a Hashicorp Vault Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
|
||||||
|
- Create a [Hashicorp Vault Connection](/integrations/app-connections/hashicorp-vault)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Add Sync">
|
||||||
|
Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select Hashicorp Vault">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Source">
|
||||||
|
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>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Destination">
|
||||||
|
Configure the **Destination** to where secrets should be deployed.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Hashicorp Vault Connection**: The Vault Connection to authenticate with.
|
||||||
|
- **Secrets Engine Mount**: The secrets engine to sync secrets with (e.g., 'secret', 'kv').
|
||||||
|
- **Path**: The specific path within the secrets engine where secrets will be stored.
|
||||||
|
|
||||||
|
After configuring these parameters, click the **Next** button to continue to the Sync Options step.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
If the **path** you provide does not exist in Vault, it will be created.
|
||||||
|
</Note>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Sync Options">
|
||||||
|
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 Hashicorp Vault when keys conflict.
|
||||||
|
- **Import Secrets (Prioritize Hashicorp Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Hashicorp Vault 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.
|
||||||
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure Details">
|
||||||
|
Configure the **Details** of your Hashicorp Vault Sync, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
</Step>
|
||||||
|
<Step title="Review Configuration">
|
||||||
|
Review your Hashicorp Vault Sync configuration, then click **Create Sync**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Sync Created">
|
||||||
|
If enabled, your Hashicorp Vault Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create an **Hashicorp Vault Sync**, make an API request to the [Create Hashicorp Vault Sync](/api-reference/endpoints/secret-syncs/hashicorp-vault/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/hashicorp-vault \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-vault-sync",
|
||||||
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"description": "an example sync",
|
||||||
|
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"environment": "dev",
|
||||||
|
"secretPath": "/",
|
||||||
|
"isEnabled": true,
|
||||||
|
"syncOptions": {
|
||||||
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
|
},
|
||||||
|
"destinationConfig": {
|
||||||
|
"mount": "secret",
|
||||||
|
"path": "dev/nested"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-vault-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": "hashicorp-vault",
|
||||||
|
"name": "my-vault-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": "/"
|
||||||
|
},
|
||||||
|
"destination": "hashicorp-vault",
|
||||||
|
"destinationConfig": {
|
||||||
|
"mount": "secret",
|
||||||
|
"path": "dev/nested"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -438,6 +438,7 @@
|
|||||||
"integrations/app-connections/databricks",
|
"integrations/app-connections/databricks",
|
||||||
"integrations/app-connections/gcp",
|
"integrations/app-connections/gcp",
|
||||||
"integrations/app-connections/github",
|
"integrations/app-connections/github",
|
||||||
|
"integrations/app-connections/hashicorp-vault",
|
||||||
"integrations/app-connections/humanitec",
|
"integrations/app-connections/humanitec",
|
||||||
"integrations/app-connections/ldap",
|
"integrations/app-connections/ldap",
|
||||||
"integrations/app-connections/mssql",
|
"integrations/app-connections/mssql",
|
||||||
@@ -465,6 +466,7 @@
|
|||||||
"integrations/secret-syncs/databricks",
|
"integrations/secret-syncs/databricks",
|
||||||
"integrations/secret-syncs/gcp-secret-manager",
|
"integrations/secret-syncs/gcp-secret-manager",
|
||||||
"integrations/secret-syncs/github",
|
"integrations/secret-syncs/github",
|
||||||
|
"integrations/secret-syncs/hashicorp-vault",
|
||||||
"integrations/secret-syncs/humanitec",
|
"integrations/secret-syncs/humanitec",
|
||||||
"integrations/secret-syncs/teamcity",
|
"integrations/secret-syncs/teamcity",
|
||||||
"integrations/secret-syncs/terraform-cloud",
|
"integrations/secret-syncs/terraform-cloud",
|
||||||
@@ -1068,6 +1070,18 @@
|
|||||||
"api-reference/endpoints/app-connections/github/delete"
|
"api-reference/endpoints/app-connections/github/delete"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Hashicorp Vault",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/list",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/available",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/get-by-id",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/get-by-name",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/create",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/update",
|
||||||
|
"api-reference/endpoints/app-connections/hashicorp-vault/delete"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Humanitec",
|
"group": "Humanitec",
|
||||||
"pages": [
|
"pages": [
|
||||||
@@ -1280,6 +1294,20 @@
|
|||||||
"api-reference/endpoints/secret-syncs/github/remove-secrets"
|
"api-reference/endpoints/secret-syncs/github/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Hashicorp Vault",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/import-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/hashicorp-vault/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Humanitec",
|
"group": "Humanitec",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
|
import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
|
||||||
|
import { useHCVaultConnectionListMounts } from "@app/hooks/api/appConnections/hc-vault";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const HCVaultSyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.HCVault }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
|
||||||
|
const { data: mounts, isLoading: isMountsLoading } = useHCVaultConnectionListMounts(
|
||||||
|
connectionId,
|
||||||
|
{
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.mount", "");
|
||||||
|
setValue("destinationConfig.path", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.mount"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Secrets Engine Mount"
|
||||||
|
helperText={
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-md"
|
||||||
|
content="Ensure the Secrets Engine mount exists and that your App Role / Access Token has permission to access it. Infisical only supports version 2 KV Secrets Engine mounts. If you're using Hashicorp Cloud Platform, ensure that you correctly defined your 'namespace' when creating the App Connection."
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span>Don't see the mount you're looking for?</span>{" "}
|
||||||
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
menuPlacement="top"
|
||||||
|
isLoading={isMountsLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
onChange={(option) =>
|
||||||
|
onChange((option as SingleValue<{ value: string }>)?.value ?? null)
|
||||||
|
}
|
||||||
|
options={mounts?.map((v) => ({ label: v, value: v }))}
|
||||||
|
placeholder="Select a Secrets Engine Mount..."
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.path"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
tooltipClassName="max-w-sm"
|
||||||
|
tooltipText="The Secrets Engine mount path where secrets should be synced to. If the path does not exist, it will be created."
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Path"
|
||||||
|
>
|
||||||
|
<Input value={value} onChange={onChange} placeholder="dev/example" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import { CamundaSyncFields } from "./CamundaSyncFields";
|
|||||||
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
import { DatabricksSyncFields } from "./DatabricksSyncFields";
|
||||||
import { GcpSyncFields } from "./GcpSyncFields";
|
import { GcpSyncFields } from "./GcpSyncFields";
|
||||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||||
|
import { HCVaultSyncFields } from "./HCVaultSyncFields";
|
||||||
import { HumanitecSyncFields } from "./HumanitecSyncFields";
|
import { HumanitecSyncFields } from "./HumanitecSyncFields";
|
||||||
import { TeamCitySyncFields } from "./TeamCitySyncFields";
|
import { TeamCitySyncFields } from "./TeamCitySyncFields";
|
||||||
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
|
import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields";
|
||||||
@@ -47,6 +48,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <VercelSyncFields />;
|
return <VercelSyncFields />;
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
return <WindmillSyncFields />;
|
return <WindmillSyncFields />;
|
||||||
|
case SecretSync.HCVault:
|
||||||
|
return <HCVaultSyncFields />;
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
return <TeamCitySyncFields />;
|
return <TeamCitySyncFields />;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.Camunda:
|
case SecretSync.Camunda:
|
||||||
case SecretSync.Vercel:
|
case SecretSync.Vercel:
|
||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
|
case SecretSync.HCVault:
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
AdditionalSyncOptionsFieldsComponent = null;
|
AdditionalSyncOptionsFieldsComponent = null;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const HCVaultSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.HCVault }>();
|
||||||
|
const mount = watch("destinationConfig.mount");
|
||||||
|
const path = watch("destinationConfig.path");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GenericFieldLabel label="Secrets Engine Mount">{mount}</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Path">{path}</GenericFieldLabel>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -21,6 +21,7 @@ import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
|||||||
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields";
|
||||||
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
|
||||||
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
||||||
|
import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields";
|
||||||
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
||||||
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
||||||
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
||||||
@@ -89,6 +90,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.Windmill:
|
case SecretSync.Windmill:
|
||||||
DestinationFieldsComponent = <WindmillSyncReviewFields />;
|
DestinationFieldsComponent = <WindmillSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.HCVault:
|
||||||
|
DestinationFieldsComponent = <HCVaultSyncReviewFields />;
|
||||||
|
break;
|
||||||
case SecretSync.TeamCity:
|
case SecretSync.TeamCity:
|
||||||
DestinationFieldsComponent = <TeamCitySyncReviewFields />;
|
DestinationFieldsComponent = <TeamCitySyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const HCVaultSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||||
|
z.object({
|
||||||
|
destination: z.literal(SecretSync.HCVault),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
mount: z.string().trim().min(1, "Secrets Engine Mount required"),
|
||||||
|
path: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "Path required")
|
||||||
|
.transform((val) => val.trim().replace(/^\/+|\/+$/g, "")) // removes leading/trailing slashes
|
||||||
|
.refine((val) => /^([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."
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -8,6 +8,7 @@ import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema"
|
|||||||
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema";
|
||||||
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
||||||
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
|
||||||
|
import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema";
|
||||||
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
|
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
|
||||||
import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema";
|
import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema";
|
||||||
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
|
import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema";
|
||||||
@@ -27,6 +28,7 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
|||||||
CamundaSyncDestinationSchema,
|
CamundaSyncDestinationSchema,
|
||||||
VercelSyncDestinationSchema,
|
VercelSyncDestinationSchema,
|
||||||
WindmillSyncDestinationSchema,
|
WindmillSyncDestinationSchema,
|
||||||
|
HCVaultSyncDestinationSchema,
|
||||||
TeamCitySyncDestinationSchema
|
TeamCitySyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
DatabricksConnectionMethod,
|
DatabricksConnectionMethod,
|
||||||
GcpConnectionMethod,
|
GcpConnectionMethod,
|
||||||
GitHubConnectionMethod,
|
GitHubConnectionMethod,
|
||||||
|
HCVaultConnectionMethod,
|
||||||
HumanitecConnectionMethod,
|
HumanitecConnectionMethod,
|
||||||
LdapConnectionMethod,
|
LdapConnectionMethod,
|
||||||
MsSqlConnectionMethod,
|
MsSqlConnectionMethod,
|
||||||
@@ -58,6 +59,7 @@ export const APP_CONNECTION_MAP: Record<
|
|||||||
[AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" },
|
[AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" },
|
||||||
[AppConnection.Windmill]: { name: "Windmill", image: "Windmill.png" },
|
[AppConnection.Windmill]: { name: "Windmill", image: "Windmill.png" },
|
||||||
[AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 },
|
[AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 },
|
||||||
|
[AppConnection.HCVault]: { name: "Hashicorp Vault", image: "Vault.png", size: 65 },
|
||||||
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
|
[AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 },
|
||||||
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }
|
[AppConnection.TeamCity]: { name: "TeamCity", image: "TeamCity.png" }
|
||||||
};
|
};
|
||||||
@@ -88,11 +90,14 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
case PostgresConnectionMethod.UsernameAndPassword:
|
case PostgresConnectionMethod.UsernameAndPassword:
|
||||||
case MsSqlConnectionMethod.UsernameAndPassword:
|
case MsSqlConnectionMethod.UsernameAndPassword:
|
||||||
return { name: "Username & Password", icon: faLock };
|
return { name: "Username & Password", icon: faLock };
|
||||||
|
case HCVaultConnectionMethod.AccessToken:
|
||||||
case TeamCityConnectionMethod.AccessToken:
|
case TeamCityConnectionMethod.AccessToken:
|
||||||
case WindmillConnectionMethod.AccessToken:
|
case WindmillConnectionMethod.AccessToken:
|
||||||
return { name: "Access Token", icon: faKey };
|
return { name: "Access Token", icon: faKey };
|
||||||
case Auth0ConnectionMethod.ClientCredentials:
|
case Auth0ConnectionMethod.ClientCredentials:
|
||||||
return { name: "Client Credentials", icon: faServer };
|
return { name: "Client Credentials", icon: faServer };
|
||||||
|
case HCVaultConnectionMethod.AppRole:
|
||||||
|
return { name: "App Role", icon: faUser };
|
||||||
case LdapConnectionMethod.SimpleBind:
|
case LdapConnectionMethod.SimpleBind:
|
||||||
return { name: "Simple Bind", icon: faLink };
|
return { name: "Simple Bind", icon: faLink };
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -40,6 +40,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
|||||||
name: "Windmill",
|
name: "Windmill",
|
||||||
image: "Windmill.png"
|
image: "Windmill.png"
|
||||||
},
|
},
|
||||||
|
[SecretSync.HCVault]: {
|
||||||
|
name: "Hashicorp Vault",
|
||||||
|
image: "Vault.png"
|
||||||
|
},
|
||||||
[SecretSync.TeamCity]: {
|
[SecretSync.TeamCity]: {
|
||||||
name: "TeamCity",
|
name: "TeamCity",
|
||||||
image: "TeamCity.png"
|
image: "TeamCity.png"
|
||||||
@@ -59,6 +63,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Camunda]: AppConnection.Camunda,
|
[SecretSync.Camunda]: AppConnection.Camunda,
|
||||||
[SecretSync.Vercel]: AppConnection.Vercel,
|
[SecretSync.Vercel]: AppConnection.Vercel,
|
||||||
[SecretSync.Windmill]: AppConnection.Windmill,
|
[SecretSync.Windmill]: AppConnection.Windmill,
|
||||||
|
[SecretSync.HCVault]: AppConnection.HCVault,
|
||||||
[SecretSync.TeamCity]: AppConnection.TeamCity
|
[SecretSync.TeamCity]: AppConnection.TeamCity
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export enum AppConnection {
|
|||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
Auth0 = "auth0",
|
Auth0 = "auth0",
|
||||||
|
HCVault = "hashicorp-vault",
|
||||||
LDAP = "ldap",
|
LDAP = "ldap",
|
||||||
TeamCity = "teamcity"
|
TeamCity = "teamcity"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./queries";
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { appConnectionKeys } from "../queries";
|
||||||
|
|
||||||
|
const hcVaultConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "hcvault"] as const,
|
||||||
|
listMounts: (connectionId: string) =>
|
||||||
|
[...hcVaultConnectionKeys.all, "mounts", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHCVaultConnectionListMounts = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
string[],
|
||||||
|
unknown,
|
||||||
|
string[],
|
||||||
|
ReturnType<typeof hcVaultConnectionKeys.listMounts>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: hcVaultConnectionKeys.listMounts(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<string[]>(
|
||||||
|
`/api/v1/app-connections/hashicorp-vault/${connectionId}/mounts`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -72,6 +72,10 @@ export type TAuth0ConnectionOption = TAppConnectionOptionBase & {
|
|||||||
app: AppConnection.Auth0;
|
app: AppConnection.Auth0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type THCVaultConnectionOption = TAppConnectionOptionBase & {
|
||||||
|
app: AppConnection.HCVault;
|
||||||
|
};
|
||||||
|
|
||||||
export type TLdapConnectionOption = TAppConnectionOptionBase & {
|
export type TLdapConnectionOption = TAppConnectionOptionBase & {
|
||||||
app: AppConnection.LDAP;
|
app: AppConnection.LDAP;
|
||||||
};
|
};
|
||||||
@@ -96,6 +100,7 @@ export type TAppConnectionOption =
|
|||||||
| TCamundaConnectionOption
|
| TCamundaConnectionOption
|
||||||
| TWindmillConnectionOption
|
| TWindmillConnectionOption
|
||||||
| TAuth0ConnectionOption
|
| TAuth0ConnectionOption
|
||||||
|
| THCVaultConnectionOption
|
||||||
| TTeamCityConnectionOption;
|
| TTeamCityConnectionOption;
|
||||||
|
|
||||||
export type TAppConnectionOptionMap = {
|
export type TAppConnectionOptionMap = {
|
||||||
@@ -114,6 +119,7 @@ export type TAppConnectionOptionMap = {
|
|||||||
[AppConnection.Camunda]: TCamundaConnectionOption;
|
[AppConnection.Camunda]: TCamundaConnectionOption;
|
||||||
[AppConnection.Windmill]: TWindmillConnectionOption;
|
[AppConnection.Windmill]: TWindmillConnectionOption;
|
||||||
[AppConnection.Auth0]: TAuth0ConnectionOption;
|
[AppConnection.Auth0]: TAuth0ConnectionOption;
|
||||||
|
[AppConnection.HCVault]: THCVaultConnectionOption;
|
||||||
[AppConnection.LDAP]: TLdapConnectionOption;
|
[AppConnection.LDAP]: TLdapConnectionOption;
|
||||||
[AppConnection.TeamCity]: TTeamCityConnectionOption;
|
[AppConnection.TeamCity]: TTeamCityConnectionOption;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||||
|
|
||||||
|
export enum HCVaultConnectionMethod {
|
||||||
|
AccessToken = "access-token",
|
||||||
|
AppRole = "app-role"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type THCVaultConnection = TRootAppConnection & { app: AppConnection.HCVault } & (
|
||||||
|
| {
|
||||||
|
method: HCVaultConnectionMethod.AccessToken;
|
||||||
|
credentials: {
|
||||||
|
instanceUrl: string;
|
||||||
|
namespace?: string;
|
||||||
|
accessToken: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
method: HCVaultConnectionMethod.AppRole;
|
||||||
|
credentials: {
|
||||||
|
instanceUrl: string;
|
||||||
|
namespace?: string;
|
||||||
|
roleId: string;
|
||||||
|
secretId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -9,6 +9,7 @@ import { TCamundaConnection } from "./camunda-connection";
|
|||||||
import { TDatabricksConnection } from "./databricks-connection";
|
import { TDatabricksConnection } from "./databricks-connection";
|
||||||
import { TGcpConnection } from "./gcp-connection";
|
import { TGcpConnection } from "./gcp-connection";
|
||||||
import { TGitHubConnection } from "./github-connection";
|
import { TGitHubConnection } from "./github-connection";
|
||||||
|
import { THCVaultConnection } from "./hc-vault-connection";
|
||||||
import { THumanitecConnection } from "./humanitec-connection";
|
import { THumanitecConnection } from "./humanitec-connection";
|
||||||
import { TLdapConnection } from "./ldap-connection";
|
import { TLdapConnection } from "./ldap-connection";
|
||||||
import { TMsSqlConnection } from "./mssql-connection";
|
import { TMsSqlConnection } from "./mssql-connection";
|
||||||
@@ -27,6 +28,7 @@ export * from "./camunda-connection";
|
|||||||
export * from "./databricks-connection";
|
export * from "./databricks-connection";
|
||||||
export * from "./gcp-connection";
|
export * from "./gcp-connection";
|
||||||
export * from "./github-connection";
|
export * from "./github-connection";
|
||||||
|
export * from "./hc-vault-connection";
|
||||||
export * from "./humanitec-connection";
|
export * from "./humanitec-connection";
|
||||||
export * from "./ldap-connection";
|
export * from "./ldap-connection";
|
||||||
export * from "./mssql-connection";
|
export * from "./mssql-connection";
|
||||||
@@ -52,6 +54,7 @@ export type TAppConnection =
|
|||||||
| TCamundaConnection
|
| TCamundaConnection
|
||||||
| TWindmillConnection
|
| TWindmillConnection
|
||||||
| TAuth0Connection
|
| TAuth0Connection
|
||||||
|
| THCVaultConnection
|
||||||
| TLdapConnection
|
| TLdapConnection
|
||||||
| TTeamCityConnection;
|
| TTeamCityConnection;
|
||||||
|
|
||||||
@@ -96,6 +99,7 @@ export type TAppConnectionMap = {
|
|||||||
[AppConnection.Camunda]: TCamundaConnection;
|
[AppConnection.Camunda]: TCamundaConnection;
|
||||||
[AppConnection.Windmill]: TWindmillConnection;
|
[AppConnection.Windmill]: TWindmillConnection;
|
||||||
[AppConnection.Auth0]: TAuth0Connection;
|
[AppConnection.Auth0]: TAuth0Connection;
|
||||||
|
[AppConnection.HCVault]: THCVaultConnection;
|
||||||
[AppConnection.LDAP]: TLdapConnection;
|
[AppConnection.LDAP]: TLdapConnection;
|
||||||
[AppConnection.TeamCity]: TTeamCityConnection;
|
[AppConnection.TeamCity]: TTeamCityConnection;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export enum SecretSync {
|
|||||||
Camunda = "camunda",
|
Camunda = "camunda",
|
||||||
Vercel = "vercel",
|
Vercel = "vercel",
|
||||||
Windmill = "windmill",
|
Windmill = "windmill",
|
||||||
|
HCVault = "hashicorp-vault",
|
||||||
TeamCity = "teamcity"
|
TeamCity = "teamcity"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||