Support for namespaces (for HCP)

This commit is contained in:
x
2025-04-28 18:34:33 -04:00
parent b6566943c6
commit e30d400afa
10 changed files with 72 additions and 11 deletions
+1
View File
@@ -1860,6 +1860,7 @@ export const AppConnections = {
}, },
HC_VAULT: { HC_VAULT: {
instanceUrl: "The Hashicrop Vault instance URL to connect with.", 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.", accessToken: "The access token used to connect with Hashicorp Vault.",
roleId: "The Role ID 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." secretId: "The Secret ID used to connect with Hashicorp Vault."
@@ -49,7 +49,12 @@ export const getHCVaultAccessToken = async (connection: TValidateHCVaultConnecti
const tokenResp = await request.post<TokenRespData>( const tokenResp = await request.post<TokenRespData>(
`${removeTrailingSlash(instanceUrl)}/v1/auth/approle/login`, `${removeTrailingSlash(instanceUrl)}/v1/auth/approle/login`,
{ role_id: roleId, secret_id: secretId }, { role_id: roleId, secret_id: secretId },
{ headers: { "Content-Type": "application/json" } } {
headers: {
"Content-Type": "application/json",
...(connection.credentials.namespace ? { "X-Vault-Namespace": connection.credentials.namespace } : {})
}
}
); );
if (tokenResp.status !== 200) { if (tokenResp.status !== 200) {
@@ -95,7 +100,10 @@ export const listHCVaultMounts = async (appConnection: THCVaultConnection) => {
const accessToken = await getHCVaultAccessToken(appConnection); const accessToken = await getHCVaultAccessToken(appConnection);
const { data } = await request.get<THCVaultMountResponse>(`${instanceUrl}/v1/sys/mounts`, { const { data } = await request.get<THCVaultMountResponse>(`${instanceUrl}/v1/sys/mounts`, {
headers: { "X-Vault-Token": accessToken } headers: {
"X-Vault-Token": accessToken,
...(appConnection.credentials.namespace ? { "X-Vault-Namespace": appConnection.credentials.namespace } : {})
}
}); });
const mounts: string[] = []; const mounts: string[] = [];
@@ -17,8 +17,11 @@ const InstanceUrlSchema = z
.url("Invalid Instance URL") .url("Invalid Instance URL")
.describe(AppConnections.CREDENTIALS.HC_VAULT.instanceUrl); .describe(AppConnections.CREDENTIALS.HC_VAULT.instanceUrl);
const NamespaceSchema = z.string().trim().optional().describe(AppConnections.CREDENTIALS.HC_VAULT.namespace);
export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({ export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({
instanceUrl: InstanceUrlSchema, instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
accessToken: z accessToken: z
.string() .string()
.trim() .trim()
@@ -28,6 +31,7 @@ export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({
export const HCVaultConnectionAppRoleCredentialsSchema = z.object({ export const HCVaultConnectionAppRoleCredentialsSchema = z.object({
instanceUrl: InstanceUrlSchema, instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
roleId: z.string().trim().min(1, "Role ID required").describe(AppConnections.CREDENTIALS.HC_VAULT.roleId), 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) secretId: z.string().trim().min(1, "Secret ID required").describe(AppConnections.CREDENTIALS.HC_VAULT.secretId)
}); });
@@ -10,12 +10,13 @@ import {
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
const listHCVaultVariables = async ({ instanceUrl, mount, accessToken, path }: THCVaultListVariables) => { const listHCVaultVariables = async ({ instanceUrl, namespace, mount, accessToken, path }: THCVaultListVariables) => {
const { data } = await request.get<THCVaultListVariablesResponse>( const { data } = await request.get<THCVaultListVariablesResponse>(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`, `${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{ {
headers: { headers: {
"X-Vault-Token": accessToken "X-Vault-Token": accessToken,
...(namespace ? { "X-Vault-Namespace": namespace } : {})
} }
} }
); );
@@ -24,7 +25,14 @@ const listHCVaultVariables = async ({ instanceUrl, mount, accessToken, path }: T
}; };
// Hashicorp Vault updates all variables in one batch. This is to respect their versioning // Hashicorp Vault updates all variables in one batch. This is to respect their versioning
const updateHCVaultVariables = async ({ path, instanceUrl, accessToken, mount, data }: TPostHCVaultVariable) => const updateHCVaultVariables = async ({
path,
instanceUrl,
namespace,
accessToken,
mount,
data
}: TPostHCVaultVariable) =>
request.post( request.post(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`, `${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{ {
@@ -33,6 +41,7 @@ const updateHCVaultVariables = async ({ path, instanceUrl, accessToken, mount, d
{ {
headers: { headers: {
"X-Vault-Token": accessToken, "X-Vault-Token": accessToken,
...(namespace ? { "X-Vault-Namespace": namespace } : {}),
"Content-Type": "application/json" "Content-Type": "application/json"
} }
} }
@@ -46,10 +55,17 @@ export const HCVaultSyncFns = {
syncOptions: { disableSecretDeletion } syncOptions: { disableSecretDeletion }
} = secretSync; } = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection); const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection); const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({ instanceUrl, accessToken, mount, path }); const variables = await listHCVaultVariables({
instanceUrl,
accessToken,
namespace,
mount,
path
});
let tainted = false; let tainted = false;
for (const entry of Object.entries(secretMap)) { for (const entry of Object.entries(secretMap)) {
@@ -73,7 +89,7 @@ export const HCVaultSyncFns = {
if (!tainted) return; if (!tainted) return;
try { try {
await updateHCVaultVariables({ accessToken, instanceUrl, mount, path, data: variables }); await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) { } catch (error) {
throw new SecretSyncError({ throw new SecretSyncError({
error error
@@ -86,10 +102,11 @@ export const HCVaultSyncFns = {
destinationConfig: { mount, path } destinationConfig: { mount, path }
} = secretSync; } = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection); const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection); const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({ instanceUrl, accessToken, mount, path }); const variables = await listHCVaultVariables({ instanceUrl, namespace, accessToken, mount, path });
for await (const [key] of Object.entries(variables)) { for await (const [key] of Object.entries(variables)) {
if (key in secretMap) { if (key in secretMap) {
@@ -98,7 +115,7 @@ export const HCVaultSyncFns = {
} }
try { try {
await updateHCVaultVariables({ accessToken, instanceUrl, mount, path, data: variables }); await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) { } catch (error) {
throw new SecretSyncError({ throw new SecretSyncError({
error error
@@ -111,11 +128,13 @@ export const HCVaultSyncFns = {
destinationConfig: { mount, path } destinationConfig: { mount, path }
} = secretSync; } = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(connection); const accessToken = await getHCVaultAccessToken(connection);
const instanceUrl = await getHCVaultInstanceUrl(connection); const instanceUrl = await getHCVaultInstanceUrl(connection);
const variables = await listHCVaultVariables({ const variables = await listHCVaultVariables({
instanceUrl, instanceUrl,
namespace,
accessToken, accessToken,
mount, mount,
path path
@@ -25,6 +25,7 @@ export type THCVaultListVariablesResponse = {
export type THCVaultListVariables = { export type THCVaultListVariables = {
accessToken: string; accessToken: string;
instanceUrl: string; instanceUrl: string;
namespace?: string;
mount: string; mount: string;
path: string; path: string;
}; };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 KiB

After

Width:  |  Height:  |  Size: 643 KiB

@@ -4,7 +4,7 @@ description: "Learn how to configure a Hashicorp Vault Connection for Infisical.
--- ---
<Note> <Note>
The Hashicorp Vault UI may vary based on whether you're self-hosting or using HCP, but the written directions should still be universal. 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> </Note>
Infisical supports two methods for connecting to Hashicorp Vault. Infisical supports two methods for connecting to Hashicorp Vault.
@@ -114,10 +114,14 @@ Infisical supports two methods for connecting to Hashicorp Vault.
Save this value for later steps. Save this value for later steps.
</Tab> </Tab>
<Tab title="HCP"> <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. On HCP instances, you may need to navigate to **Cluster Overview** to see your cluster URL. Save this value for later steps.
![Vault Cluster URLs](/images/app-connections/hashicorp-vault/vault-cluster-urls.png) ![Vault Cluster URLs](/images/app-connections/hashicorp-vault/vault-cluster-urls.png)
<Note>
Cluster Overview is found in the HCP dashboard, not in your cluster's web UI.
</Note>
</Tab> </Tab>
</Tabs> </Tabs>
@@ -144,6 +148,7 @@ Infisical supports two methods for connecting to Hashicorp Vault.
- **Name**: The name of the connection being created. Must be slug-friendly. - **Name**: The name of the connection being created. Must be slug-friendly.
- **Description**: An optional description to provide details about this connection. - **Description**: An optional description to provide details about this connection.
- **Instance URL**: The URL of your Hashicorp Vault instance. - **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 (if using Access Token authentication method). - **Access Token**: The Access Token generated in the steps above (if using Access Token authentication method).
- **Role ID**: The Role ID generated in the steps above (if using AppRole authentication method). - **Role ID**: The Role ID generated in the steps above (if using AppRole authentication method).
- **Secret ID**: The Secret ID generated in the steps above (if using AppRole authentication method). - **Secret ID**: The Secret ID generated in the steps above (if using AppRole authentication method).
@@ -11,6 +11,7 @@ export type THCVaultConnection = TRootAppConnection & { app: AppConnection.HCVau
method: HCVaultConnectionMethod.AccessToken; method: HCVaultConnectionMethod.AccessToken;
credentials: { credentials: {
instanceUrl: string; instanceUrl: string;
namespace?: string;
accessToken: string; accessToken: string;
}; };
} }
@@ -18,6 +19,7 @@ export type THCVaultConnection = TRootAppConnection & { app: AppConnection.HCVau
method: HCVaultConnectionMethod.AppRole; method: HCVaultConnectionMethod.AppRole;
credentials: { credentials: {
instanceUrl: string; instanceUrl: string;
namespace?: string;
roleId: string; roleId: string;
secretId: string; secretId: string;
}; };
@@ -35,11 +35,14 @@ const InstanceUrlSchema = z
.min(1, "Instance URL required") .min(1, "Instance URL required")
.url("Invalid Instance URL"); .url("Invalid Instance URL");
const NamespaceSchema = z.string().trim().optional();
const formSchema = z.discriminatedUnion("method", [ const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({ rootSchema.extend({
method: z.literal(HCVaultConnectionMethod.AccessToken), method: z.literal(HCVaultConnectionMethod.AccessToken),
credentials: z.object({ credentials: z.object({
instanceUrl: InstanceUrlSchema, instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
accessToken: z.string().trim().min(1, "Access Token required") accessToken: z.string().trim().min(1, "Access Token required")
}) })
}), }),
@@ -47,6 +50,7 @@ const formSchema = z.discriminatedUnion("method", [
method: z.literal(HCVaultConnectionMethod.AppRole), method: z.literal(HCVaultConnectionMethod.AppRole),
credentials: z.object({ credentials: z.object({
instanceUrl: InstanceUrlSchema, instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
roleId: z.string().trim().min(1, "Role ID required"), roleId: z.string().trim().min(1, "Role ID required"),
secretId: z.string().trim().min(1, "Secret ID required") secretId: z.string().trim().min(1, "Secret ID required")
}) })
@@ -127,6 +131,23 @@ export const HCVaultConnectionForm = ({ appConnection, onSubmit }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller
name="credentials.namespace"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Namespace"
isOptional
tooltipClassName="max-w-sm"
tooltipText="On self-hosted and enterprise clusters there may not be namespaces."
>
<Input {...field} placeholder="admin" />
</FormControl>
)}
/>
{selectedMethod === HCVaultConnectionMethod.AccessToken ? ( {selectedMethod === HCVaultConnectionMethod.AccessToken ? (
<Controller <Controller
name="credentials.accessToken" name="credentials.accessToken"