Merge branch 'heads/main' into daniel/fips-initative

This commit is contained in:
Daniel Hougaard
2025-07-07 09:55:26 +04:00
720 changed files with 13000 additions and 13897 deletions
@@ -27,7 +27,8 @@ export enum AppConnection {
Render = "render",
Flyio = "flyio",
GitLab = "gitlab",
Cloudflare = "cloudflare"
Cloudflare = "cloudflare",
Zabbix = "zabbix"
}
export enum AWSRegion {
@@ -105,6 +105,7 @@ import {
validateWindmillConnectionCredentials,
WindmillConnectionMethod
} from "./windmill";
import { getZabbixConnectionListItem, validateZabbixConnectionCredentials, ZabbixConnectionMethod } from "./zabbix";
export const listAppConnectionOptions = () => {
return [
@@ -136,7 +137,8 @@ export const listAppConnectionOptions = () => {
getRenderConnectionListItem(),
getFlyioConnectionListItem(),
getGitLabConnectionListItem(),
getCloudflareConnectionListItem()
getCloudflareConnectionListItem(),
getZabbixConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -216,7 +218,8 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Render]: validateRenderConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Flyio]: validateFlyioConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.GitLab]: validateGitLabConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator
[AppConnection.Cloudflare]: validateCloudflareConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Zabbix]: validateZabbixConnectionCredentials as TAppConnectionCredentialsValidator
};
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
@@ -253,6 +256,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case VercelConnectionMethod.ApiToken:
case OnePassConnectionMethod.ApiToken:
case CloudflareConnectionMethod.APIToken:
case ZabbixConnectionMethod.ApiToken:
return "API Token";
case PostgresConnectionMethod.UsernameAndPassword:
case MsSqlConnectionMethod.UsernameAndPassword:
@@ -332,7 +336,8 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Render]: platformManagedCredentialsNotSupported,
[AppConnection.Flyio]: platformManagedCredentialsNotSupported,
[AppConnection.GitLab]: platformManagedCredentialsNotSupported,
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported
[AppConnection.Cloudflare]: platformManagedCredentialsNotSupported,
[AppConnection.Zabbix]: platformManagedCredentialsNotSupported
};
export const enterpriseAppCheck = async (
@@ -29,7 +29,8 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Render]: "Render",
[AppConnection.Flyio]: "Fly.io",
[AppConnection.GitLab]: "GitLab",
[AppConnection.Cloudflare]: "Cloudflare"
[AppConnection.Cloudflare]: "Cloudflare",
[AppConnection.Zabbix]: "Zabbix"
};
export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanType> = {
@@ -61,5 +62,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Render]: AppConnectionPlanType.Regular,
[AppConnection.Flyio]: AppConnectionPlanType.Regular,
[AppConnection.GitLab]: AppConnectionPlanType.Regular,
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular
[AppConnection.Cloudflare]: AppConnectionPlanType.Regular,
[AppConnection.Zabbix]: AppConnectionPlanType.Regular
};
@@ -80,6 +80,8 @@ import { ValidateVercelConnectionCredentialsSchema } from "./vercel";
import { vercelConnectionService } from "./vercel/vercel-connection-service";
import { ValidateWindmillConnectionCredentialsSchema } from "./windmill";
import { windmillConnectionService } from "./windmill/windmill-connection-service";
import { ValidateZabbixConnectionCredentialsSchema } from "./zabbix";
import { zabbixConnectionService } from "./zabbix/zabbix-connection-service";
export type TAppConnectionServiceFactoryDep = {
appConnectionDAL: TAppConnectionDALFactory;
@@ -119,7 +121,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Render]: ValidateRenderConnectionCredentialsSchema,
[AppConnection.Flyio]: ValidateFlyioConnectionCredentialsSchema,
[AppConnection.GitLab]: ValidateGitLabConnectionCredentialsSchema,
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema
[AppConnection.Cloudflare]: ValidateCloudflareConnectionCredentialsSchema,
[AppConnection.Zabbix]: ValidateZabbixConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
@@ -529,6 +532,7 @@ export const appConnectionServiceFactory = ({
render: renderConnectionService(connectAppConnectionById),
flyio: flyioConnectionService(connectAppConnectionById),
gitlab: gitlabConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
cloudflare: cloudflareConnectionService(connectAppConnectionById)
cloudflare: cloudflareConnectionService(connectAppConnectionById),
zabbix: zabbixConnectionService(connectAppConnectionById)
};
};
@@ -165,6 +165,12 @@ import {
TWindmillConnectionConfig,
TWindmillConnectionInput
} from "./windmill";
import {
TValidateZabbixConnectionCredentialsSchema,
TZabbixConnection,
TZabbixConnectionConfig,
TZabbixConnectionInput
} from "./zabbix";
export type TAppConnection = { id: string } & (
| TAwsConnection
@@ -196,6 +202,7 @@ export type TAppConnection = { id: string } & (
| TFlyioConnection
| TGitLabConnection
| TCloudflareConnection
| TZabbixConnection
);
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
@@ -232,6 +239,7 @@ export type TAppConnectionInput = { id: string } & (
| TFlyioConnectionInput
| TGitLabConnectionInput
| TCloudflareConnectionInput
| TZabbixConnectionInput
);
export type TSqlConnectionInput =
@@ -275,7 +283,8 @@ export type TAppConnectionConfig =
| TRenderConnectionConfig
| TFlyioConnectionConfig
| TGitLabConnectionConfig
| TCloudflareConnectionConfig;
| TCloudflareConnectionConfig
| TZabbixConnectionConfig;
export type TValidateAppConnectionCredentialsSchema =
| TValidateAwsConnectionCredentialsSchema
@@ -306,7 +315,8 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateRenderConnectionCredentialsSchema
| TValidateFlyioConnectionCredentialsSchema
| TValidateGitLabConnectionCredentialsSchema
| TValidateCloudflareConnectionCredentialsSchema;
| TValidateCloudflareConnectionCredentialsSchema
| TValidateZabbixConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = {
connectionId: string;
@@ -0,0 +1,4 @@
export * from "./zabbix-connection-enums";
export * from "./zabbix-connection-fns";
export * from "./zabbix-connection-schemas";
export * from "./zabbix-connection-types";
@@ -0,0 +1,3 @@
export enum ZabbixConnectionMethod {
ApiToken = "api-token"
}
@@ -0,0 +1,108 @@
import { AxiosError } from "axios";
import RE2 from "re2";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { ZabbixConnectionMethod } from "./zabbix-connection-enums";
import {
TZabbixConnection,
TZabbixConnectionConfig,
TZabbixHost,
TZabbixHostListResponse
} from "./zabbix-connection-types";
const TRAILING_SLASH_REGEX = new RE2("/+$");
export const getZabbixConnectionListItem = () => {
return {
name: "Zabbix" as const,
app: AppConnection.Zabbix as const,
methods: Object.values(ZabbixConnectionMethod) as [ZabbixConnectionMethod.ApiToken]
};
};
export const validateZabbixConnectionCredentials = async (config: TZabbixConnectionConfig) => {
const { apiToken, instanceUrl } = config.credentials;
await blockLocalAndPrivateIpAddresses(instanceUrl);
try {
const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
const payload = {
jsonrpc: "2.0",
method: "authentication.get",
params: {
output: "extend"
},
id: 1
};
const response: { data: { error?: { message: string }; result?: string } } = await request.post(apiUrl, payload, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiToken}`
}
});
if (response.data.error) {
throw new BadRequestError({
message: response.data.error.message
});
}
return config.credentials;
} catch (error) {
if (error instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to connect to Zabbix instance: ${error.message}`
});
}
throw error;
}
};
export const listZabbixHosts = async (appConnection: TZabbixConnection): Promise<TZabbixHost[]> => {
const { apiToken, instanceUrl } = appConnection.credentials;
await blockLocalAndPrivateIpAddresses(instanceUrl);
try {
const apiUrl = `${instanceUrl.replace(TRAILING_SLASH_REGEX, "")}/api_jsonrpc.php`;
const payload = {
jsonrpc: "2.0",
method: "host.get",
params: {
output: ["hostid", "host"],
sortfield: "host",
sortorder: "ASC"
},
id: 1
};
const response: { data: TZabbixHostListResponse } = await request.post(apiUrl, payload, {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiToken}`
}
});
return response.data.result
? response.data.result.map((host) => ({
hostId: host.hostid,
host: host.host
}))
: [];
} 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"
});
}
};
@@ -0,0 +1,62 @@
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 { ZabbixConnectionMethod } from "./zabbix-connection-enums";
export const ZabbixConnectionApiTokenCredentialsSchema = z.object({
apiToken: z
.string()
.trim()
.min(1, "API Token required")
.max(1000)
.describe(AppConnections.CREDENTIALS.ZABBIX.apiToken),
instanceUrl: z.string().trim().url("Invalid Instance URL").describe(AppConnections.CREDENTIALS.ZABBIX.instanceUrl)
});
const BaseZabbixConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Zabbix) });
export const ZabbixConnectionSchema = BaseZabbixConnectionSchema.extend({
method: z.literal(ZabbixConnectionMethod.ApiToken),
credentials: ZabbixConnectionApiTokenCredentialsSchema
});
export const SanitizedZabbixConnectionSchema = z.discriminatedUnion("method", [
BaseZabbixConnectionSchema.extend({
method: z.literal(ZabbixConnectionMethod.ApiToken),
credentials: ZabbixConnectionApiTokenCredentialsSchema.pick({ instanceUrl: true })
})
]);
export const ValidateZabbixConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z.literal(ZabbixConnectionMethod.ApiToken).describe(AppConnections.CREATE(AppConnection.Zabbix).method),
credentials: ZabbixConnectionApiTokenCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Zabbix).credentials
)
})
]);
export const CreateZabbixConnectionSchema = ValidateZabbixConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Zabbix)
);
export const UpdateZabbixConnectionSchema = z
.object({
credentials: ZabbixConnectionApiTokenCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Zabbix).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Zabbix));
export const ZabbixConnectionListItemSchema = z.object({
name: z.literal("Zabbix"),
app: z.literal(AppConnection.Zabbix),
methods: z.nativeEnum(ZabbixConnectionMethod).array()
});
@@ -0,0 +1,30 @@
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import { listZabbixHosts } from "./zabbix-connection-fns";
import { TZabbixConnection } from "./zabbix-connection-types";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TZabbixConnection>;
export const zabbixConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
const listHosts = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Zabbix, connectionId, actor);
try {
const hosts = await listZabbixHosts(appConnection);
return hosts;
} catch (error) {
logger.error(error, "Failed to establish connection with zabbix");
return [];
}
};
return {
listHosts
};
};
@@ -0,0 +1,33 @@
import z from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums";
import {
CreateZabbixConnectionSchema,
ValidateZabbixConnectionCredentialsSchema,
ZabbixConnectionSchema
} from "./zabbix-connection-schemas";
export type TZabbixConnection = z.infer<typeof ZabbixConnectionSchema>;
export type TZabbixConnectionInput = z.infer<typeof CreateZabbixConnectionSchema> & {
app: AppConnection.Zabbix;
};
export type TValidateZabbixConnectionCredentialsSchema = typeof ValidateZabbixConnectionCredentialsSchema;
export type TZabbixConnectionConfig = DiscriminativePick<TZabbixConnectionInput, "method" | "app" | "credentials"> & {
orgId: string;
};
export type TZabbixHost = {
hostId: string;
host: string;
};
export type TZabbixHostListResponse = {
jsonrpc: string;
result: { hostid: string; host: string }[];
error?: { message: string };
};