mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 00:30:56 +00:00
feat(app-connections): Hashicorp Vault App Connection
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
export enum HCVaultConnectionMethod {
|
||||
AccessToken = "access-token",
|
||||
AppRole = "app-role"
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
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 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" } }
|
||||
);
|
||||
|
||||
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 = removeTrailingSlash(config.credentials.instanceUrl);
|
||||
|
||||
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 = removeTrailingSlash(appConnection.credentials.instanceUrl);
|
||||
const accessToken = await getHCVaultAccessToken(appConnection);
|
||||
|
||||
const { data } = await request.get<THCVaultMountResponse>(`${instanceUrl}/v1/sys/mounts`, {
|
||||
headers: { "X-Vault-Token": accessToken }
|
||||
});
|
||||
|
||||
const mounts: string[] = [];
|
||||
|
||||
// Filter for "kv" type only
|
||||
Object.entries(data.data).forEach(([path, mount]) => {
|
||||
if (mount.type === "kv") {
|
||||
mounts.push(path);
|
||||
}
|
||||
});
|
||||
|
||||
return mounts;
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
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);
|
||||
|
||||
export const HCVaultConnectionAccessTokenCredentialsSchema = z.object({
|
||||
instanceUrl: InstanceUrlSchema,
|
||||
accessToken: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Access Token required")
|
||||
.describe(AppConnections.CREDENTIALS.HC_VAULT.accessToken)
|
||||
});
|
||||
|
||||
export const HCVaultConnectionAppRoleCredentialsSchema = z.object({
|
||||
instanceUrl: InstanceUrlSchema,
|
||||
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: HCVaultConnectionAccessTokenCredentialsSchema.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,28 @@
|
||||
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) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
listMounts
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
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]: {
|
||||
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";
|
||||
Reference in New Issue
Block a user