mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 02:51:51 +00:00
feature: ldap connection and password rotation
This commit is contained in:
@@ -12,7 +12,8 @@ export enum AppConnection {
|
||||
MsSql = "mssql",
|
||||
Camunda = "camunda",
|
||||
Windmill = "windmill",
|
||||
Auth0 = "auth0"
|
||||
Auth0 = "auth0",
|
||||
Ldap = "ldap"
|
||||
}
|
||||
|
||||
export enum AWSRegion {
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
HumanitecConnectionMethod,
|
||||
validateHumanitecConnectionCredentials
|
||||
} from "./humanitec";
|
||||
import { getLdapConnectionListItem, LdapConnectionMethod, validateLdapConnectionCredentials } from "./ldap";
|
||||
import { getMsSqlConnectionListItem, MsSqlConnectionMethod } from "./mssql";
|
||||
import { getPostgresConnectionListItem, PostgresConnectionMethod } from "./postgres";
|
||||
import {
|
||||
@@ -71,7 +72,8 @@ export const listAppConnectionOptions = () => {
|
||||
getMsSqlConnectionListItem(),
|
||||
getCamundaConnectionListItem(),
|
||||
getWindmillConnectionListItem(),
|
||||
getAuth0ConnectionListItem()
|
||||
getAuth0ConnectionListItem(),
|
||||
getLdapConnectionListItem()
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
@@ -135,7 +137,8 @@ export const validateAppConnectionCredentials = async (
|
||||
[AppConnection.Vercel]: validateVercelConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
[AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator,
|
||||
[AppConnection.Ldap]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator
|
||||
};
|
||||
|
||||
return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection);
|
||||
@@ -170,6 +173,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
return "Access Token";
|
||||
case Auth0ConnectionMethod.ClientCredentials:
|
||||
return "Client Credentials";
|
||||
case LdapConnectionMethod.SimpleBind:
|
||||
return "Simple Bind";
|
||||
default:
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
@@ -214,5 +219,6 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
|
||||
[AppConnection.Camunda]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Vercel]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Windmill]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported
|
||||
[AppConnection.Auth0]: platformManagedCredentialsNotSupported,
|
||||
[AppConnection.Ldap]: platformManagedCredentialsNotSupported // we could support this in the future
|
||||
};
|
||||
|
||||
@@ -14,5 +14,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||
[AppConnection.MsSql]: "Microsoft SQL Server",
|
||||
[AppConnection.Camunda]: "Camunda",
|
||||
[AppConnection.Windmill]: "Windmill",
|
||||
[AppConnection.Auth0]: "Auth0"
|
||||
[AppConnection.Auth0]: "Auth0",
|
||||
[AppConnection.Ldap]: "LDAP"
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
||||
import { githubConnectionService } from "./github/github-connection-service";
|
||||
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
||||
import { ValidateLdapConnectionCredentialsSchema } from "./ldap";
|
||||
import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import { ValidatePostgresConnectionCredentialsSchema } from "./postgres";
|
||||
import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud";
|
||||
@@ -74,7 +75,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
||||
[AppConnection.MsSql]: ValidateMsSqlConnectionCredentialsSchema,
|
||||
[AppConnection.Camunda]: ValidateCamundaConnectionCredentialsSchema,
|
||||
[AppConnection.Windmill]: ValidateWindmillConnectionCredentialsSchema,
|
||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema
|
||||
[AppConnection.Auth0]: ValidateAuth0ConnectionCredentialsSchema,
|
||||
[AppConnection.Ldap]: ValidateLdapConnectionCredentialsSchema
|
||||
};
|
||||
|
||||
export const appConnectionServiceFactory = ({
|
||||
|
||||
@@ -57,6 +57,12 @@ import {
|
||||
THumanitecConnectionInput,
|
||||
TValidateHumanitecConnectionCredentialsSchema
|
||||
} from "./humanitec";
|
||||
import {
|
||||
TLdapConnection,
|
||||
TLdapConnectionConfig,
|
||||
TLdapConnectionInput,
|
||||
TValidateLdapConnectionCredentialsSchema
|
||||
} from "./ldap";
|
||||
import { TMsSqlConnection, TMsSqlConnectionInput, TValidateMsSqlConnectionCredentialsSchema } from "./mssql";
|
||||
import {
|
||||
TPostgresConnection,
|
||||
@@ -97,6 +103,7 @@ export type TAppConnection = { id: string } & (
|
||||
| TCamundaConnection
|
||||
| TWindmillConnection
|
||||
| TAuth0Connection
|
||||
| TLdapConnection
|
||||
);
|
||||
|
||||
export type TAppConnectionRaw = NonNullable<Awaited<ReturnType<TAppConnectionDALFactory["findById"]>>>;
|
||||
@@ -118,6 +125,7 @@ export type TAppConnectionInput = { id: string } & (
|
||||
| TCamundaConnectionInput
|
||||
| TWindmillConnectionInput
|
||||
| TAuth0ConnectionInput
|
||||
| TLdapConnectionInput
|
||||
);
|
||||
|
||||
export type TSqlConnectionInput = TPostgresConnectionInput | TMsSqlConnectionInput;
|
||||
@@ -144,7 +152,8 @@ export type TAppConnectionConfig =
|
||||
| TSqlConnectionConfig
|
||||
| TCamundaConnectionConfig
|
||||
| TWindmillConnectionConfig
|
||||
| TAuth0ConnectionConfig;
|
||||
| TAuth0ConnectionConfig
|
||||
| TLdapConnectionConfig;
|
||||
|
||||
export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateAwsConnectionCredentialsSchema
|
||||
@@ -160,7 +169,8 @@ export type TValidateAppConnectionCredentialsSchema =
|
||||
| TValidateTerraformCloudConnectionCredentialsSchema
|
||||
| TValidateVercelConnectionCredentialsSchema
|
||||
| TValidateWindmillConnectionCredentialsSchema
|
||||
| TValidateAuth0ConnectionCredentialsSchema;
|
||||
| TValidateAuth0ConnectionCredentialsSchema
|
||||
| TValidateLdapConnectionCredentialsSchema;
|
||||
|
||||
export type TListAwsConnectionKmsKeys = {
|
||||
connectionId: string;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./ldap-connection-enums";
|
||||
export * from "./ldap-connection-fns";
|
||||
export * from "./ldap-connection-schemas";
|
||||
export * from "./ldap-connection-types";
|
||||
@@ -0,0 +1,7 @@
|
||||
export enum LdapConnectionMethod {
|
||||
SimpleBind = "simple-bind"
|
||||
}
|
||||
|
||||
export enum LdapProvider {
|
||||
ActiveDirectory = "active-directory"
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import ldap from "ldapjs";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import { LdapConnectionMethod } from "./ldap-connection-enums";
|
||||
import { TLdapConnectionConfig } from "./ldap-connection-types";
|
||||
|
||||
export const getLdapConnectionListItem = () => {
|
||||
return {
|
||||
name: "LDAP" as const,
|
||||
app: AppConnection.Ldap as const,
|
||||
methods: Object.values(LdapConnectionMethod) as [LdapConnectionMethod.SimpleBind]
|
||||
};
|
||||
};
|
||||
|
||||
const LDAP_TIMEOUT = 15_000;
|
||||
|
||||
export const getLdapConnectionClient = async ({
|
||||
url,
|
||||
dn,
|
||||
password,
|
||||
sslCertificate,
|
||||
sslRejectUnauthorized = true
|
||||
}: TLdapConnectionConfig["credentials"]) => {
|
||||
await blockLocalAndPrivateIpAddresses(url);
|
||||
|
||||
const isSSL = url.startsWith("ldaps");
|
||||
|
||||
return new Promise<ldap.Client>((resolve, reject) => {
|
||||
const client = ldap.createClient({
|
||||
url,
|
||||
timeout: LDAP_TIMEOUT,
|
||||
connectTimeout: LDAP_TIMEOUT,
|
||||
tlsOptions: isSSL
|
||||
? {
|
||||
rejectUnauthorized: sslRejectUnauthorized,
|
||||
ca: sslCertificate ? [sslCertificate] : undefined
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
|
||||
client.on("error", (err: Error) => {
|
||||
logger.error(err, "LDAP Error");
|
||||
reject(new Error(`Provider Error - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectError", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Error");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connect Error - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectRefused", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Refused");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connection Refused - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connectTimeout", (err: Error) => {
|
||||
logger.error(err, "LDAP Connection Timeout");
|
||||
client.destroy();
|
||||
reject(new Error(`Provider Connection Timeout - ${err.message}`));
|
||||
});
|
||||
|
||||
client.on("connect", () => {
|
||||
client.bind(dn, password, (err) => {
|
||||
if (err) {
|
||||
logger.error(err, "LDAP Bind Error");
|
||||
reject(new Error(`Bind Error: ${err.message}`));
|
||||
client.destroy();
|
||||
}
|
||||
|
||||
resolve(client);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const validateLdapConnectionCredentials = async ({ credentials }: TLdapConnectionConfig) => {
|
||||
let client: ldap.Client | undefined;
|
||||
|
||||
try {
|
||||
client = await getLdapConnectionClient(credentials);
|
||||
|
||||
// this shouldn't occur as handle connection error events in client but here as fallback
|
||||
if (!client.connected) {
|
||||
throw new BadRequestError({ message: "Unable to connect to LDAP server" });
|
||||
}
|
||||
|
||||
return credentials;
|
||||
} catch (e: unknown) {
|
||||
throw new BadRequestError({
|
||||
message: `Unable to validate connection: ${(e as Error).message || "verify credentials"}`
|
||||
});
|
||||
} finally {
|
||||
client?.destroy();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
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 { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums";
|
||||
|
||||
export const LdapConnectionSimpleBindCredentialsSchema = z.object({
|
||||
provider: z.nativeEnum(LdapProvider).describe(AppConnections.CREDENTIALS.LDAP.provider),
|
||||
url: z.string().trim().min(1, "URL required").describe(AppConnections.CREDENTIALS.LDAP.url),
|
||||
dn: z.string().trim().min(1, "Distinguished Name (DN) required").describe(AppConnections.CREDENTIALS.LDAP.dn),
|
||||
password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.LDAP.password),
|
||||
sslRejectUnauthorized: z.boolean().optional().describe(AppConnections.CREDENTIALS.LDAP.sslRejectUnauthorized),
|
||||
sslCertificate: z
|
||||
.string()
|
||||
.trim()
|
||||
.transform((value) => value || undefined)
|
||||
.optional()
|
||||
.describe(AppConnections.CREDENTIALS.LDAP.sslCertificate)
|
||||
});
|
||||
|
||||
const BaseLdapConnectionSchema = BaseAppConnectionSchema.extend({
|
||||
app: z.literal(AppConnection.Ldap)
|
||||
});
|
||||
|
||||
export const LdapConnectionSchema = z.intersection(
|
||||
BaseLdapConnectionSchema,
|
||||
z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema
|
||||
})
|
||||
])
|
||||
);
|
||||
|
||||
export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseLdapConnectionSchema.extend({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.pick({
|
||||
provider: true,
|
||||
url: true,
|
||||
dn: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z.literal(LdapConnectionMethod.SimpleBind).describe(AppConnections.CREATE(AppConnection.Ldap).method),
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.describe(
|
||||
AppConnections.CREATE(AppConnection.Ldap).credentials
|
||||
)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.Ldap)
|
||||
);
|
||||
|
||||
export const UpdateLdapConnectionSchema = z
|
||||
.object({
|
||||
credentials: LdapConnectionSimpleBindCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.Ldap).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Ldap));
|
||||
|
||||
export const LdapConnectionListItemSchema = z.object({
|
||||
name: z.literal("LDAP"),
|
||||
app: z.literal(AppConnection.Ldap),
|
||||
// the below is preferable but currently breaks with our zod to json schema parser
|
||||
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
|
||||
methods: z.nativeEnum(LdapConnectionMethod).array()
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { DiscriminativePick } from "@app/lib/types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import {
|
||||
CreateLdapConnectionSchema,
|
||||
LdapConnectionSchema,
|
||||
ValidateLdapConnectionCredentialsSchema
|
||||
} from "./ldap-connection-schemas";
|
||||
|
||||
export type TLdapConnection = z.infer<typeof LdapConnectionSchema>;
|
||||
|
||||
export type TLdapConnectionInput = z.infer<typeof CreateLdapConnectionSchema> & {
|
||||
app: AppConnection.Ldap;
|
||||
};
|
||||
|
||||
export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema;
|
||||
|
||||
export type TLdapConnectionConfig = DiscriminativePick<TLdapConnection, "method" | "app" | "credentials"> & {
|
||||
orgId: string;
|
||||
};
|
||||
@@ -31,7 +31,8 @@ export const SanitizedMsSqlConnectionSchema = z.discriminatedUnion("method", [
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -29,7 +29,8 @@ export const SanitizedPostgresConnectionSchema = z.discriminatedUnion("method",
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user