mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 14:26:38 +00:00
Support for namespaces (for HCP)
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
<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;
|
||||||
};
|
};
|
||||||
|
|||||||
+21
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user