mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
General improvements to Humanitec integration
This commit is contained in:
@@ -1775,7 +1775,8 @@ export const SecretSyncs = {
|
||||
HUMANITEC: {
|
||||
app: "The ID of the Humanitec app to sync secrets to.",
|
||||
org: "The ID of the Humanitec org to sync secrets to.",
|
||||
env: "The ID of the Humanitec environment to sync secrets to."
|
||||
env: "The ID of the Humanitec environment to sync secrets to.",
|
||||
scope: "The Humanitec scope that secrets should be synced to."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -224,9 +224,6 @@ const envSchema = z
|
||||
DATADOG_SERVICE: zpStr(z.string().optional().default("infisical-core")),
|
||||
DATADOG_HOSTNAME: zpStr(z.string().optional()),
|
||||
|
||||
// humanitec
|
||||
INF_APP_CONNECTION_HUMANITEC_ACCESS_KEY: zpStr(z.string().optional()),
|
||||
|
||||
/* CORS ----------------------------------------------------------------------------- */
|
||||
|
||||
CORS_ALLOWED_ORIGINS: zpStr(
|
||||
|
||||
@@ -136,8 +136,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
return "Service Account Impersonation";
|
||||
case DatabricksConnectionMethod.ServicePrincipal:
|
||||
return "Service Principal";
|
||||
case HumanitecConnectionMethod.AccessKey:
|
||||
return "Access Key";
|
||||
case HumanitecConnectionMethod.API_TOKEN:
|
||||
return "API Token";
|
||||
default:
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export enum HumanitecConnectionMethod {
|
||||
AccessKey = "access-key"
|
||||
API_TOKEN = "api-token"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AxiosError, AxiosResponse } from "axios";
|
||||
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
@@ -18,7 +19,7 @@ export const getHumanitecConnectionListItem = () => {
|
||||
return {
|
||||
name: "Humanitec" as const,
|
||||
app: AppConnection.Humanitec as const,
|
||||
methods: Object.values(HumanitecConnectionMethod) as [HumanitecConnectionMethod.AccessKey]
|
||||
methods: Object.values(HumanitecConnectionMethod) as [HumanitecConnectionMethod.API_TOKEN]
|
||||
};
|
||||
};
|
||||
|
||||
@@ -30,13 +31,13 @@ export const validateHumanitecConnectionCredentials = async (config: THumanitecC
|
||||
try {
|
||||
response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${inputCredentials.accessKeyId}`
|
||||
Authorization: `Bearer ${inputCredentials.apiToken}`
|
||||
}
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof AxiosError) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to validate credentials: ${error.response?.data || "Unknown error"}`
|
||||
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
||||
});
|
||||
}
|
||||
throw new BadRequestError({
|
||||
@@ -55,11 +56,11 @@ export const validateHumanitecConnectionCredentials = async (config: THumanitecC
|
||||
|
||||
export const listOrganizations = async (appConnection: THumanitecConnection): Promise<HumanitecOrgWithApps[]> => {
|
||||
const {
|
||||
credentials: { accessKeyId }
|
||||
credentials: { apiToken }
|
||||
} = appConnection;
|
||||
const response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`
|
||||
Authorization: `Bearer ${apiToken}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,34 +69,39 @@ export const listOrganizations = async (appConnection: THumanitecConnection): Pr
|
||||
message: "Failed to get organizations: Response was empty"
|
||||
});
|
||||
}
|
||||
|
||||
const orgs = response.data;
|
||||
const appPromises = orgs.map(async (org) => {
|
||||
return request.get<HumanitecApp[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${org.id}/apps`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`
|
||||
const orgsWithApps: HumanitecOrgWithApps[] = [];
|
||||
|
||||
for (const org of orgs) {
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const appsResponse = await request.get<HumanitecApp[]>(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${org.id}/apps`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!appsResponse.data) {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get apps for organization: Response was empty"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const appsResponses = await Promise.all(appPromises);
|
||||
|
||||
const orgsWithApps: HumanitecOrgWithApps[] = orgs.map((org, index) => {
|
||||
if (!appsResponses[index].data) {
|
||||
throw new InternalServerError({
|
||||
message: "Failed to get apps for organization: Response was empty"
|
||||
const apps = appsResponse.data;
|
||||
orgsWithApps.push({
|
||||
...org,
|
||||
apps: apps.map((app) => ({
|
||||
name: app.name,
|
||||
id: app.id,
|
||||
envs: app.envs
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error, `Failed to get apps for organization ${org.name}`);
|
||||
}
|
||||
|
||||
const apps = appsResponses[index].data;
|
||||
return {
|
||||
...org,
|
||||
apps: apps.map((app) => ({
|
||||
name: app.name,
|
||||
id: app.id,
|
||||
envs: app.envs
|
||||
}))
|
||||
};
|
||||
});
|
||||
}
|
||||
return orgsWithApps;
|
||||
};
|
||||
|
||||
@@ -11,19 +11,19 @@ import {
|
||||
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
|
||||
|
||||
export const HumanitecConnectionAccessTokenCredentialsSchema = z.object({
|
||||
accessKeyId: z.string().trim().min(1, "Access Key ID required")
|
||||
apiToken: z.string().trim().min(1, "API Token required")
|
||||
});
|
||||
|
||||
const BaseHumanitecConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Humanitec) });
|
||||
|
||||
export const HumanitecConnectionSchema = BaseHumanitecConnectionSchema.extend({
|
||||
method: z.literal(HumanitecConnectionMethod.AccessKey),
|
||||
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
|
||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseHumanitecConnectionSchema.extend({
|
||||
method: z.literal(HumanitecConnectionMethod.AccessKey),
|
||||
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
|
||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.pick({})
|
||||
})
|
||||
]);
|
||||
@@ -31,7 +31,7 @@ export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method",
|
||||
export const ValidateHumanitecConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(HumanitecConnectionMethod.AccessKey)
|
||||
.literal(HumanitecConnectionMethod.API_TOKEN)
|
||||
.describe(AppConnections?.CREATE(AppConnection.Humanitec).method),
|
||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.Humanitec).credentials
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
|
||||
import { AppConnection } from "../app-connection-enums";
|
||||
@@ -17,6 +18,7 @@ export const humanitecConnectionService = (getAppConnection: TGetAppConnectionFu
|
||||
const organizations = await getHumanitecOrganizations(appConnection);
|
||||
return organizations;
|
||||
} catch (error) {
|
||||
logger.error(error, "Failed to establish connection with Humanitec");
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export enum HumanitecSyncScope {
|
||||
Application = "application",
|
||||
Environment = "environment"
|
||||
}
|
||||
@@ -4,47 +4,60 @@ import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { HumanitecSyncScope } from "./humanitec-sync-enums";
|
||||
import { HumanitecSecret, THumanitecSyncWithCredentials } from "./humanitec-sync-types";
|
||||
|
||||
const getHumanitecSecrets = async (secretSync: THumanitecSyncWithCredentials) => {
|
||||
const {
|
||||
destinationConfig,
|
||||
connection: {
|
||||
credentials: { accessKeyId }
|
||||
credentials: { apiToken }
|
||||
}
|
||||
} = secretSync;
|
||||
|
||||
const { data } = await request.get<HumanitecSecret[]>(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values`,
|
||||
{
|
||||
try {
|
||||
let url = `${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}`;
|
||||
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
|
||||
url += `/envs/${destinationConfig.env}`;
|
||||
}
|
||||
url += "/values";
|
||||
|
||||
const { data } = await request.get<HumanitecSecret[]>(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`,
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return data;
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSecret = async (secretSync: THumanitecSyncWithCredentials, encryptedSecret: HumanitecSecret) => {
|
||||
const {
|
||||
destinationConfig,
|
||||
connection: {
|
||||
credentials: { accessKeyId }
|
||||
credentials: { apiToken }
|
||||
}
|
||||
} = secretSync;
|
||||
|
||||
try {
|
||||
await request.delete(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${encryptedSecret.key}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
let url = `${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}`;
|
||||
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
|
||||
url += `/envs/${destinationConfig.env}`;
|
||||
}
|
||||
url += `/values/${encryptedSecret.key}`;
|
||||
|
||||
await request.delete(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
@@ -58,7 +71,7 @@ const createSecret = async (secretSync: THumanitecSyncWithCredentials, secretMap
|
||||
const {
|
||||
destinationConfig,
|
||||
connection: {
|
||||
credentials: { accessKeyId }
|
||||
credentials: { apiToken }
|
||||
}
|
||||
} = secretSync;
|
||||
|
||||
@@ -71,24 +84,26 @@ const createSecret = async (secretSync: THumanitecSyncWithCredentials, secretMap
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`,
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${key}`,
|
||||
{
|
||||
value: secretMap[key].value,
|
||||
description: secretMap[key].comment || ""
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
if (destinationConfig.scope === HumanitecSyncScope.Environment) {
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${key}`,
|
||||
{
|
||||
value: secretMap[key].value,
|
||||
description: secretMap[key].comment || ""
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
@@ -102,22 +117,38 @@ const updateSecret = async (secretSync: THumanitecSyncWithCredentials, secretMap
|
||||
const {
|
||||
destinationConfig,
|
||||
connection: {
|
||||
credentials: { accessKeyId }
|
||||
credentials: { apiToken }
|
||||
}
|
||||
} = secretSync;
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${key}`,
|
||||
{
|
||||
value: secretMap[key].value,
|
||||
description: secretMap[key].comment || ""
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessKeyId}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
if (destinationConfig.scope === HumanitecSyncScope.Application) {
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/values/${key}`,
|
||||
{
|
||||
value: secretMap[key].value,
|
||||
description: secretMap[key].comment || ""
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
} else {
|
||||
await request.patch(
|
||||
`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${destinationConfig.org}/apps/${destinationConfig.app}/envs/${destinationConfig.env}/values/${key}`,
|
||||
{
|
||||
value: secretMap[key].value,
|
||||
description: secretMap[key].comment || ""
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { SecretSyncs } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { HumanitecSyncScope } from "@app/services/secret-sync/humanitec/humanitec-sync-enums";
|
||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||
import {
|
||||
BaseSecretSyncSchema,
|
||||
@@ -10,11 +11,19 @@ import {
|
||||
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const HumanitecSyncDestinationConfigSchema = z.object({
|
||||
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.app),
|
||||
org: z.string().min(1, "Org ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.org),
|
||||
env: z.string().min(1, "Env ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.env)
|
||||
});
|
||||
const HumanitecSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
scope: z.literal(HumanitecSyncScope.Application).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope),
|
||||
org: z.string().min(1, "Org ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.org),
|
||||
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.app)
|
||||
}),
|
||||
z.object({
|
||||
scope: z.literal(HumanitecSyncScope.Environment).describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.scope),
|
||||
org: z.string().min(1, "Org ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.org),
|
||||
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.app),
|
||||
env: z.string().min(1, "Env ID is required").describe(SecretSyncs.DESTINATION_CONFIG.HUMANITEC.env)
|
||||
})
|
||||
]);
|
||||
|
||||
const HumanitecSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./humanitec-sync-constants";
|
||||
export * from "./humanitec-sync-enums";
|
||||
export * from "./humanitec-sync-fns";
|
||||
export * from "./humanitec-sync-schemas";
|
||||
export * from "./humanitec-sync-types";
|
||||
|
||||
Reference in New Issue
Block a user