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

View File

@@ -1860,6 +1860,7 @@ export const AppConnections = {
},
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."

View File

@@ -49,7 +49,12 @@ export const getHCVaultAccessToken = async (connection: TValidateHCVaultConnecti
const tokenResp = await request.post<TokenRespData>(
`${removeTrailingSlash(instanceUrl)}/v1/auth/approle/login`,
{ 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) {
@@ -95,7 +100,10 @@ export const listHCVaultMounts = async (appConnection: THCVaultConnection) => {
const accessToken = await getHCVaultAccessToken(appConnection);
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[] = [];

View File

@@ -17,8 +17,11 @@ const InstanceUrlSchema = z
.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()
@@ -28,6 +31,7 @@ export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({
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)
});

View File

@@ -10,12 +10,13 @@ import {
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
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>(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{
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
const updateHCVaultVariables = async ({ path, instanceUrl, accessToken, mount, data }: TPostHCVaultVariable) =>
const updateHCVaultVariables = async ({
path,
instanceUrl,
namespace,
accessToken,
mount,
data
}: TPostHCVaultVariable) =>
request.post(
`${instanceUrl}/v1/${removeTrailingSlash(mount)}/data/${path}`,
{
@@ -33,6 +41,7 @@ const updateHCVaultVariables = async ({ path, instanceUrl, accessToken, mount, d
{
headers: {
"X-Vault-Token": accessToken,
...(namespace ? { "X-Vault-Namespace": namespace } : {}),
"Content-Type": "application/json"
}
}
@@ -46,10 +55,17 @@ export const HCVaultSyncFns = {
syncOptions: { disableSecretDeletion }
} = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(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;
for (const entry of Object.entries(secretMap)) {
@@ -73,7 +89,7 @@ export const HCVaultSyncFns = {
if (!tainted) return;
try {
await updateHCVaultVariables({ accessToken, instanceUrl, mount, path, data: variables });
await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) {
throw new SecretSyncError({
error
@@ -86,10 +102,11 @@ export const HCVaultSyncFns = {
destinationConfig: { mount, path }
} = secretSync;
const { namespace } = connection.credentials;
const accessToken = await getHCVaultAccessToken(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)) {
if (key in secretMap) {
@@ -98,7 +115,7 @@ export const HCVaultSyncFns = {
}
try {
await updateHCVaultVariables({ accessToken, instanceUrl, mount, path, data: variables });
await updateHCVaultVariables({ accessToken, instanceUrl, namespace, mount, path, data: variables });
} catch (error) {
throw new SecretSyncError({
error
@@ -111,11 +128,13 @@ export const HCVaultSyncFns = {
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

View File

@@ -25,6 +25,7 @@ export type THCVaultListVariablesResponse = {
export type THCVaultListVariables = {
accessToken: string;
instanceUrl: string;
namespace?: string;
mount: 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

View File

@@ -4,7 +4,7 @@ description: "Learn how to configure a Hashicorp Vault Connection for Infisical.
---
<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>
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.
</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.
![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>
</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.
- **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 (if using Access Token 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).

View File

@@ -11,6 +11,7 @@ export type THCVaultConnection = TRootAppConnection & { app: AppConnection.HCVau
method: HCVaultConnectionMethod.AccessToken;
credentials: {
instanceUrl: string;
namespace?: string;
accessToken: string;
};
}
@@ -18,6 +19,7 @@ export type THCVaultConnection = TRootAppConnection & { app: AppConnection.HCVau
method: HCVaultConnectionMethod.AppRole;
credentials: {
instanceUrl: string;
namespace?: string;
roleId: string;
secretId: string;
};

View File

@@ -35,11 +35,14 @@ const InstanceUrlSchema = z
.min(1, "Instance URL required")
.url("Invalid Instance URL");
const NamespaceSchema = z.string().trim().optional();
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(HCVaultConnectionMethod.AccessToken),
credentials: z.object({
instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
accessToken: z.string().trim().min(1, "Access Token required")
})
}),
@@ -47,6 +50,7 @@ const formSchema = z.discriminatedUnion("method", [
method: z.literal(HCVaultConnectionMethod.AppRole),
credentials: z.object({
instanceUrl: InstanceUrlSchema,
namespace: NamespaceSchema,
roleId: z.string().trim().min(1, "Role ID required"),
secretId: z.string().trim().min(1, "Secret ID required")
})
@@ -127,6 +131,23 @@ export const HCVaultConnectionForm = ({ appConnection, onSubmit }: Props) => {
</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 ? (
<Controller
name="credentials.accessToken"