Merge branch 'main' into ENG-2636

This commit is contained in:
x032205
2025-05-09 01:34:19 -04:00
69 changed files with 2669 additions and 151 deletions

View File

@@ -66,6 +66,8 @@ import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-a
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
@@ -146,6 +148,13 @@ declare module "fastify" {
providerAuthToken: string;
externalProviderAccessToken?: string;
};
passportMachineIdentity: {
identityId: string;
user: {
uid: string;
mail?: string;
};
};
kmipUser: {
projectId: string;
clientId: string;
@@ -153,7 +162,9 @@ declare module "fastify" {
};
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>;
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>> & {
allowedFields?: TAllowedFields[];
};
}
interface FastifyInstance {
@@ -199,6 +210,7 @@ declare module "fastify" {
identityAzureAuth: TIdentityAzureAuthServiceFactory;
identityOidcAuth: TIdentityOidcAuthServiceFactory;
identityJwtAuth: TIdentityJwtAuthServiceFactory;
identityLdapAuth: TIdentityLdapAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;

View File

@@ -432,6 +432,11 @@ import {
TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate
} from "@app/db/schemas";
import {
TIdentityLdapAuths,
TIdentityLdapAuthsInsert,
TIdentityLdapAuthsUpdate
} from "@app/db/schemas/identity-ldap-auths";
import {
TMicrosoftTeamsIntegrations,
TMicrosoftTeamsIntegrationsInsert,
@@ -735,6 +740,11 @@ declare module "knex/types/tables" {
TIdentityJwtAuthsInsert,
TIdentityJwtAuthsUpdate
>;
[TableName.IdentityLdapAuth]: KnexOriginal.CompositeTableType<
TIdentityLdapAuths,
TIdentityLdapAuthsInsert,
TIdentityLdapAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,

View File

@@ -0,0 +1,39 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityLdapAuth))) {
await knex.schema.createTable(TableName.IdentityLdapAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.binary("encryptedBindDN").notNullable();
t.binary("encryptedBindPass").notNullable();
t.binary("encryptedLdapCaCertificate").nullable();
t.string("url").notNullable();
t.string("searchBase").notNullable();
t.string("searchFilter").notNullable();
t.jsonb("allowedFields").nullable();
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityLdapAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
}

View File

@@ -0,0 +1,32 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const IdentityLdapAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
identityId: z.string().uuid(),
encryptedBindDN: zodBuffer,
encryptedBindPass: zodBuffer,
encryptedLdapCaCertificate: zodBuffer.nullable().optional(),
url: z.string(),
searchBase: z.string(),
searchFilter: z.string(),
allowedFields: z.unknown().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
export type TIdentityLdapAuthsInsert = Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>;
export type TIdentityLdapAuthsUpdate = Partial<Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>>;

View File

@@ -80,6 +80,7 @@ export enum TableName {
IdentityAwsAuth = "identity_aws_auths",
IdentityOidcAuth = "identity_oidc_auths",
IdentityJwtAuth = "identity_jwt_auths",
IdentityLdapAuth = "identity_ldap_auths",
IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role",
@@ -232,7 +233,8 @@ export enum IdentityAuthMethod {
AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth",
OIDC_AUTH = "oidc-auth",
JWT_AUTH = "jwt-auth"
JWT_AUTH = "jwt-auth",
LDAP_AUTH = "ldap-auth"
}
export enum ProjectType {

View File

@@ -34,6 +34,7 @@ import { WorkflowIntegration } from "@app/services/workflow-integration/workflow
import { KmipPermission } from "../kmip/kmip-enum";
import { ApprovalStatus } from "../secret-approval-request/secret-approval-request-types";
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
export type TListProjectAuditLogDTO = {
filter: {
@@ -119,44 +120,60 @@ export enum EventType {
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
@@ -1034,6 +1051,55 @@ interface GetIdentityAzureAuthEvent {
};
}
interface LoginIdentityLdapAuthEvent {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
ldapUsername: string;
ldapEmail?: string;
};
}
interface AddIdentityLdapAuthEvent {
type: EventType.ADD_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url: string;
};
}
interface UpdateIdentityLdapAuthEvent {
type: EventType.UPDATE_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url?: string;
};
}
interface GetIdentityLdapAuthEvent {
type: EventType.GET_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
};
}
interface RevokeIdentityLdapAuthEvent {
type: EventType.REVOKE_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityOidcAuthEvent {
type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
metadata: {
@@ -2785,6 +2851,11 @@ export type Event =
| UpdateIdentityJwtAuthEvent
| GetIdentityJwtAuthEvent
| DeleteIdentityJwtAuthEvent
| LoginIdentityLdapAuthEvent
| AddIdentityLdapAuthEvent
| UpdateIdentityLdapAuthEvent
| GetIdentityLdapAuthEvent
| RevokeIdentityLdapAuthEvent
| CreateEnvironmentEvent
| GetEnvironmentEvent
| UpdateEnvironmentEvent

View File

@@ -14,6 +14,11 @@ export type TLDAPConfig = {
caCert: string;
};
export type TTestLDAPConfigDTO = Omit<
TLDAPConfig,
"organization" | "id" | "groupSearchBase" | "groupSearchFilter" | "isActive" | "uniqueUserAttribute" | "searchBase"
>;
export type TCreateLdapCfgDTO = {
orgId: string;
isActive: boolean;

View File

@@ -2,15 +2,14 @@ import ldapjs from "ldapjs";
import { logger } from "@app/lib/logger";
import { TLDAPConfig } from "./ldap-config-types";
import { TLDAPConfig, TTestLDAPConfigDTO } from "./ldap-config-types";
export const isValidLdapFilter = (filter: string) => {
try {
ldapjs.parseFilter(filter);
return true;
} catch (error) {
logger.error("Invalid LDAP filter");
logger.error(error);
logger.error(error, "Invalid LDAP filter");
return false;
}
};
@@ -20,7 +19,7 @@ export const isValidLdapFilter = (filter: string) => {
* @param ldapConfig - The LDAP configuration to test
* @returns {Boolean} isConnected - Whether or not the connection was successful
*/
export const testLDAPConfig = async (ldapConfig: TLDAPConfig): Promise<boolean> => {
export const testLDAPConfig = async (ldapConfig: TTestLDAPConfigDTO): Promise<boolean> => {
return new Promise((resolve) => {
const ldapClient = ldapjs.createClient({
url: ldapConfig.url,

View File

@@ -18,6 +18,7 @@ export enum ApiDocsTags {
KubernetesAuth = "Kubernetes Auth",
JwtAuth = "JWT Auth",
OidcAuth = "OIDC Auth",
LdapAuth = "LDAP Auth",
Groups = "Groups",
Organizations = "Organizations",
Projects = "Projects",
@@ -184,6 +185,49 @@ export const UNIVERSAL_AUTH = {
}
} as const;
export const LDAP_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
username: "The username of the LDAP user to login.",
password: "The password of the LDAP user to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
url: "The URL of the LDAP server.",
allowedFields:
"The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.",
searchBase: "The base DN to search for the LDAP user.",
searchFilter: "The filter to use to search for the LDAP user.",
bindDN: "The DN of the user to bind to the LDAP server.",
bindPass: "The password of the user to bind to the LDAP server.",
ldapCaCertificate: "The PEM-encoded CA certificate for the LDAP server.",
accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the configuration for.",
url: "The new URL of the LDAP server.",
allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.",
searchBase: "The new base DN to search for the LDAP user.",
searchFilter: "The new filter to use to search for the LDAP user.",
bindDN: "The new DN of the user to bind to the LDAP server.",
bindPass: "The new password of the user to bind to the LDAP server.",
ldapCaCertificate: "The new PEM-encoded CA certificate for the LDAP server.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the configuration for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the configuration for."
}
} as const;
export const AWS_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",

View File

@@ -84,7 +84,9 @@ const redactedKeys = [
"secrets",
"key",
"password",
"config"
"config",
"bindPass",
"bindDN"
];
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";

View File

@@ -160,6 +160,8 @@ import { identityJwtAuthDALFactory } from "@app/services/identity-jwt-auth/ident
import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
import { identityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
@@ -354,6 +356,7 @@ export const registerRoutes = async (
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
const identityLdapAuthDAL = identityLdapAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
@@ -1445,6 +1448,16 @@ export const registerRoutes = async (
kmsService
});
const identityLdapAuthService = identityLdapAuthServiceFactory({
identityLdapAuthDAL,
permissionService,
kmsService,
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService,
identityDAL
});
const gatewayService = gatewayServiceFactory({
permissionService,
gatewayDAL,
@@ -1705,6 +1718,7 @@ export const registerRoutes = async (
identityAzureAuth: identityAzureAuthService,
identityOidcAuth: identityOidcAuthService,
identityJwtAuth: identityJwtAuthService,
identityLdapAuth: identityLdapAuthService,
accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService,
secretApprovalPolicy: secretApprovalPolicyService,

View File

@@ -0,0 +1,497 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
// All the any rules are disabled because passport typesense with fastify is really poor
import { Authenticator } from "@fastify/passport";
import fastifySession from "@fastify/session";
import { FastifyRequest } from "fastify";
import { IncomingMessage } from "http";
import LdapStrategy from "passport-ldapauth";
import { z } from "zod";
import { IdentityLdapAuthsSchema } from "@app/db/schemas/identity-ldap-auths";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { isValidLdapFilter } from "@app/ee/services/ldap-config/ldap-fns";
import { ApiDocsTags, LDAP_AUTH } from "@app/lib/api-docs";
import { getConfig } from "@app/lib/config/env";
import { UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { AllowedFieldsSchema } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
const passport = new Authenticator({ key: "ldap-identity-auth", userProperty: "passportMachineIdentity" });
await server.register(fastifySession, { secret: appCfg.COOKIE_SECRET_SIGN_KEY });
await server.register(passport.initialize());
await server.register(passport.secureSession());
const getLdapPassportOpts = (req: FastifyRequest, done: any) => {
const { identityId } = req.body as {
identityId: string;
};
process.nextTick(async () => {
try {
const { ldapConfig, opts } = await server.services.identityLdapAuth.getLdapConfig(identityId);
req.ldapConfig = {
...ldapConfig,
isActive: true,
groupSearchBase: "",
uniqueUserAttribute: "",
groupSearchFilter: ""
};
done(null, opts);
} catch (err) {
logger.error(err, "Error in LDAP verification callback");
done(err);
}
});
};
passport.use(
new LdapStrategy(
getLdapPassportOpts as any,
// eslint-disable-next-line
async (req: IncomingMessage, user, cb) => {
try {
const requestBody = (req as unknown as FastifyRequest).body as {
username: string;
password: string;
identityId: string;
};
if (!requestBody.username || !requestBody.password) {
return cb(new UnauthorizedError({ message: "Invalid request. Missing username or password." }), false);
}
if (!requestBody.identityId) {
return cb(new UnauthorizedError({ message: "Invalid request. Missing identity ID." }), false);
}
const { ldapConfig } = req as unknown as FastifyRequest;
if (ldapConfig.allowedFields) {
for (const field of ldapConfig.allowedFields) {
if (!user[field.key]) {
return cb(
new UnauthorizedError({ message: `Invalid request. Missing field ${field.key} on user.` }),
false
);
}
const value = field.value.split(",");
if (!value.includes(user[field.key])) {
return cb(
new UnauthorizedError({
message: `Invalid request. User field '${field.key}' does not match required fields.`
}),
false
);
}
}
}
return cb(null, { identityId: requestBody.identityId, user });
} catch (error) {
logger.error(error, "Error in LDAP verification callback");
return cb(error, false);
}
}
)
);
server.route({
method: "POST",
url: "/ldap-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Login with LDAP Auth",
body: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.LOGIN.identityId),
username: z.string().describe(LDAP_AUTH.LOGIN.username),
password: z.string().describe(LDAP_AUTH.LOGIN.password)
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
preValidation: passport.authenticate("ldapauth", {
failWithError: true,
session: false
}) as any,
errorHandler: (error) => {
if (error.name === "AuthenticationError") {
throw new UnauthorizedError({ message: "Invalid credentials" });
}
throw error;
},
handler: async (req) => {
if (!req.passportMachineIdentity?.identityId) {
throw new UnauthorizedError({ message: "Invalid request. Missing identity ID or LDAP entry details." });
}
const { identityId, user } = req.passportMachineIdentity;
const { accessToken, identityLdapAuth, identityMembershipOrg } = await server.services.identityLdapAuth.login({
identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
event: {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
metadata: {
identityId,
ldapEmail: user.mail,
ldapUsername: user.uid
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityLdapAuth.accessTokenTTL,
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/ldap-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Attach LDAP Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId)
}),
body: z
.object({
url: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.url),
bindDN: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindDN),
bindPass: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindPass),
searchBase: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.searchBase),
searchFilter: z
.string()
.trim()
.min(1)
.default("(uid={{username}})")
.refine(isValidLdapFilter, "Invalid LDAP search filter")
.describe(LDAP_AUTH.ATTACH.searchFilter),
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
accessTokenTTL: z
.number()
.int()
.min(0)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
accessTokenMaxTTL: z
.number()
.int()
.min(1)
.max(315360000)
.default(2592000)
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
"Access Token TTL cannot be greater than Access Token Max TTL."
),
response: {
200: z.object({
identityLdapAuth: IdentityLdapAuthsSchema.omit({
encryptedBindDN: true,
encryptedBindPass: true,
encryptedLdapCaCertificate: true
})
})
}
},
handler: async (req) => {
const identityLdapAuth = await server.services.identityLdapAuth.attachLdapAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId,
isActorSuperAdmin: isSuperAdmin(req.auth)
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.ADD_IDENTITY_LDAP_AUTH,
metadata: {
identityId: req.params.identityId,
url: identityLdapAuth.url,
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
allowedFields: req.body.allowedFields
}
}
});
return { identityLdapAuth };
}
});
server.route({
method: "PATCH",
url: "/ldap-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Update LDAP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.UPDATE.identityId)
}),
body: z
.object({
url: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.url),
bindDN: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindDN),
bindPass: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindPass),
searchBase: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.searchBase),
searchFilter: z
.string()
.trim()
.min(1)
.optional()
.refine((v) => v === undefined || isValidLdapFilter(v), "Invalid LDAP search filter")
.describe(LDAP_AUTH.UPDATE.searchFilter),
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.UPDATE.allowedFields),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional()
.describe(LDAP_AUTH.UPDATE.accessTokenTrustedIps),
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(LDAP_AUTH.UPDATE.accessTokenTTL),
accessTokenNumUsesLimit: z
.number()
.int()
.min(0)
.optional()
.describe(LDAP_AUTH.UPDATE.accessTokenNumUsesLimit),
accessTokenMaxTTL: z
.number()
.int()
.max(315360000)
.min(0)
.optional()
.describe(LDAP_AUTH.UPDATE.accessTokenMaxTTL)
})
.refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
"Access Token TTL cannot be greater than Access Token Max TTL."
),
response: {
200: z.object({
identityLdapAuth: IdentityLdapAuthsSchema.omit({
encryptedBindDN: true,
encryptedBindPass: true,
encryptedLdapCaCertificate: true
})
})
}
},
handler: async (req) => {
const identityLdapAuth = await server.services.identityLdapAuth.updateLdapAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
metadata: {
identityId: req.params.identityId,
url: identityLdapAuth.url,
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
allowedFields: req.body.allowedFields
}
}
});
return { identityLdapAuth };
}
});
server.route({
method: "GET",
url: "/ldap-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Retrieve LDAP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.RETRIEVE.identityId)
}),
response: {
200: z.object({
identityLdapAuth: IdentityLdapAuthsSchema.omit({
encryptedBindDN: true,
encryptedBindPass: true,
encryptedLdapCaCertificate: true
}).extend({
bindDN: z.string(),
bindPass: z.string(),
ldapCaCertificate: z.string().optional()
})
})
}
},
handler: async (req) => {
const identityLdapAuth = await server.services.identityLdapAuth.getLdapAuth({
identityId: req.params.identityId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.GET_IDENTITY_LDAP_AUTH,
metadata: {
identityId: identityLdapAuth.identityId
}
}
});
return { identityLdapAuth };
}
});
server.route({
method: "DELETE",
url: "/ldap-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.LdapAuth],
description: "Delete LDAP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim().describe(LDAP_AUTH.REVOKE.identityId)
}),
response: {
200: z.object({
identityLdapAuth: IdentityLdapAuthsSchema.omit({
encryptedBindDN: true,
encryptedBindPass: true,
encryptedLdapCaCertificate: true
})
})
}
},
handler: async (req) => {
const identityLdapAuth = await server.services.identityLdapAuth.revokeIdentityLdapAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.REVOKE_IDENTITY_LDAP_AUTH,
metadata: {
identityId: identityLdapAuth.identityId
}
}
});
return { identityLdapAuth };
}
});
};

View File

@@ -19,6 +19,7 @@ import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityJwtAuthRouter } from "./identity-jwt-auth-router";
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
import { registerIdentityLdapAuthRouter } from "./identity-ldap-auth-router";
import { registerIdentityOidcAuthRouter } from "./identity-oidc-auth-router";
import { registerIdentityRouter } from "./identity-router";
import { registerIdentityTokenAuthRouter } from "./identity-token-auth-router";
@@ -63,6 +64,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await authRouter.register(registerIdentityAzureAuthRouter);
await authRouter.register(registerIdentityOidcAuthRouter);
await authRouter.register(registerIdentityJwtAuthRouter);
await authRouter.register(registerIdentityLdapAuthRouter);
},
{ prefix: "/auth" }
);

View File

@@ -30,6 +30,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
.leftJoin(TableName.IdentityGcpAuth, `${TableName.Identity}.id`, `${TableName.IdentityGcpAuth}.identityId`)
.leftJoin(TableName.IdentityAwsAuth, `${TableName.Identity}.id`, `${TableName.IdentityAwsAuth}.identityId`)
.leftJoin(TableName.IdentityAzureAuth, `${TableName.Identity}.id`, `${TableName.IdentityAzureAuth}.identityId`)
.leftJoin(TableName.IdentityLdapAuth, `${TableName.Identity}.id`, `${TableName.IdentityLdapAuth}.identityId`)
.leftJoin(
TableName.IdentityKubernetesAuth,
`${TableName.Identity}.id`,
@@ -48,6 +49,7 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityOidcAuth).as("accessTokenTrustedIpsOidc"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityTokenAuth).as("accessTokenTrustedIpsToken"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityJwtAuth).as("accessTokenTrustedIpsJwt"),
db.ref("accessTokenTrustedIps").withSchema(TableName.IdentityLdapAuth).as("accessTokenTrustedIpsLdap"),
db.ref("name").withSchema(TableName.Identity)
)
.first();
@@ -63,7 +65,8 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
trustedIpsKubernetesAuth: doc.accessTokenTrustedIpsK8s,
trustedIpsOidcAuth: doc.accessTokenTrustedIpsOidc,
trustedIpsAccessTokenAuth: doc.accessTokenTrustedIpsToken,
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt
trustedIpsAccessJwtAuth: doc.accessTokenTrustedIpsJwt,
trustedIpsAccessLdapAuth: doc.accessTokenTrustedIpsLdap
};
} catch (error) {
throw new DatabaseError({ error, name: "IdAccessTokenFindOne" });

View File

@@ -186,7 +186,8 @@ export const identityAccessTokenServiceFactory = ({
[IdentityAuthMethod.KUBERNETES_AUTH]: identityAccessToken.trustedIpsKubernetesAuth,
[IdentityAuthMethod.OIDC_AUTH]: identityAccessToken.trustedIpsOidcAuth,
[IdentityAuthMethod.TOKEN_AUTH]: identityAccessToken.trustedIpsAccessTokenAuth,
[IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth
[IdentityAuthMethod.JWT_AUTH]: identityAccessToken.trustedIpsAccessJwtAuth,
[IdentityAuthMethod.LDAP_AUTH]: identityAccessToken.trustedIpsAccessLdapAuth
};
const trustedIps = trustedIpsMap[identityAccessToken.authMethod as IdentityAuthMethod];

View File

@@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TIdentityLdapAuthDALFactory = ReturnType<typeof identityLdapAuthDALFactory>;
export const identityLdapAuthDALFactory = (db: TDbClient) => {
const ldapAuthOrm = ormify(db, TableName.IdentityLdapAuth);
return ldapAuthOrm;
};

View File

@@ -0,0 +1,543 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
} from "@app/ee/services/permission/permission-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { validateIdentityUpdateForSuperAdminPrivileges } from "../super-admin/super-admin-fns";
import { TIdentityLdapAuthDALFactory } from "./identity-ldap-auth-dal";
import {
AllowedFieldsSchema,
TAttachLdapAuthDTO,
TGetLdapAuthDTO,
TLoginLdapAuthDTO,
TRevokeLdapAuthDTO,
TUpdateLdapAuthDTO
} from "./identity-ldap-auth-types";
type TIdentityLdapAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create" | "delete">;
identityLdapAuthDAL: Pick<
TIdentityLdapAuthDALFactory,
"findOne" | "transaction" | "create" | "updateById" | "delete"
>;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: TKmsServiceFactory;
identityDAL: TIdentityDALFactory;
};
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
export const identityLdapAuthServiceFactory = ({
identityAccessTokenDAL,
identityDAL,
identityLdapAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService,
kmsService
}: TIdentityLdapAuthServiceFactoryDep) => {
const getLdapConfig = async (identityId: string) => {
const identity = await identityDAL.findOne({ id: identityId });
if (!identity) throw new NotFoundError({ message: `Identity with ID '${identityId}' not found` });
const identityOrgMembership = await identityOrgMembershipDAL.findOne({ identityId: identity.id });
if (!identityOrgMembership) throw new NotFoundError({ message: `Identity with ID '${identityId}' not found` });
const ldapAuth = await identityLdapAuthDAL.findOne({ identityId: identity.id });
if (!ldapAuth) throw new NotFoundError({ message: `LDAP auth with ID '${identityId}' not found` });
const parsedAllowedFields = ldapAuth.allowedFields
? AllowedFieldsSchema.array().parse(ldapAuth.allowedFields)
: undefined;
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityOrgMembership.orgId
});
const bindDN = decryptor({ cipherTextBlob: ldapAuth.encryptedBindDN }).toString();
const bindPass = decryptor({ cipherTextBlob: ldapAuth.encryptedBindPass }).toString();
const ldapCaCertificate = ldapAuth.encryptedLdapCaCertificate
? decryptor({ cipherTextBlob: ldapAuth.encryptedLdapCaCertificate }).toString()
: undefined;
const ldapConfig = {
id: ldapAuth.id,
organization: identityOrgMembership.orgId,
url: ldapAuth.url,
bindDN,
bindPass,
searchBase: ldapAuth.searchBase,
searchFilter: ldapAuth.searchFilter,
caCert: ldapCaCertificate || "",
allowedFields: parsedAllowedFields
};
const opts = {
server: {
url: ldapAuth.url,
bindDN,
bindCredentials: bindPass,
searchBase: ldapAuth.searchBase,
searchFilter: ldapAuth.searchFilter,
...(ldapCaCertificate
? {
tlsOptions: {
ca: [ldapCaCertificate]
}
}
: {})
},
passReqToCallback: true
};
return { opts, ldapConfig };
};
const login = async ({ identityId }: TLoginLdapAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) {
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
if (!identityLdapAuth) {
throw new NotFoundError({ message: `Failed to find LDAP auth for identity with ID ${identityId}` });
}
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
if (!plan.ldap) {
throw new BadRequestError({
message:
"Failed to login to identity due to plan restriction. Upgrade plan to login to use LDAP authentication."
});
}
const identityAccessToken = await identityLdapAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
identityId: identityLdapAuth.identityId,
isAccessTokenRevoked: false,
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
authMethod: IdentityAuthMethod.LDAP_AUTH
},
tx
);
return newToken;
});
const appCfg = getConfig();
const accessToken = jwt.sign(
{
identityId: identityLdapAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
Number(identityAccessToken.accessTokenTTL) === 0
? undefined
: {
expiresIn: Number(identityAccessToken.accessTokenTTL)
}
);
return { accessToken, identityLdapAuth, identityAccessToken, identityMembershipOrg };
};
const attachLdapAuth = async ({
identityId,
url,
searchBase,
searchFilter,
bindDN,
bindPass,
ldapCaCertificate,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId,
isActorSuperAdmin,
allowedFields
}: TAttachLdapAuthDTO) => {
await validateIdentityUpdateForSuperAdminPrivileges(identityId, isActorSuperAdmin);
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({
message: "Failed to add LDAP Auth to already configured identity"
});
}
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
if (!plan.ldap) {
throw new BadRequestError({
message: "Failed to add LDAP Auth to identity due to plan restriction. Upgrade plan to add LDAP Auth."
});
}
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
const identityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
const { encryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.orgId
});
const { cipherTextBlob: encryptedBindPass } = encryptor({
plainText: Buffer.from(bindPass)
});
let encryptedLdapCaCertificate: Buffer | undefined;
if (ldapCaCertificate) {
const { cipherTextBlob: encryptedCertificate } = encryptor({
plainText: Buffer.from(ldapCaCertificate)
});
encryptedLdapCaCertificate = encryptedCertificate;
}
const { cipherTextBlob: encryptedBindDN } = encryptor({
plainText: Buffer.from(bindDN)
});
const isConnected = await testLDAPConfig({
bindDN,
bindPass,
caCert: ldapCaCertificate || "",
url
});
if (!isConnected) {
throw new BadRequestError({
message:
"Failed to connect to LDAP server. Please ensure that the LDAP server is running and your credentials are correct."
});
}
const doc = await identityLdapAuthDAL.create(
{
identityId: identityMembershipOrg.identityId,
encryptedBindDN,
encryptedBindPass,
searchBase,
searchFilter,
url,
encryptedLdapCaCertificate,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined
},
tx
);
return doc;
});
return { ...identityLdapAuth, orgId: identityMembershipOrg.orgId };
};
const updateLdapAuth = async ({
identityId,
url,
searchBase,
searchFilter,
bindDN,
bindPass,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUpdateLdapAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new NotFoundError({
message: "The identity does not have LDAP Auth attached"
});
}
const identityLdapAuth = await identityLdapAuthDAL.findOne({ identityId });
if (
(accessTokenMaxTTL || identityLdapAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || identityLdapAuth.accessTokenTTL) > (accessTokenMaxTTL || identityLdapAuth.accessTokenMaxTTL)
) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
if (!plan.ldap) {
throw new BadRequestError({
message: "Failed to update LDAP Auth due to plan restriction. Upgrade plan to update LDAP Auth."
});
}
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
const { encryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.orgId
});
let encryptedBindPass: Buffer | undefined;
if (bindPass) {
const { cipherTextBlob: bindPassCiphertext } = encryptor({
plainText: Buffer.from(bindPass)
});
encryptedBindPass = bindPassCiphertext;
}
let encryptedLdapCaCertificate: Buffer | undefined;
if (ldapCaCertificate) {
const { cipherTextBlob: ldapCaCertificateCiphertext } = encryptor({
plainText: Buffer.from(ldapCaCertificate)
});
encryptedLdapCaCertificate = ldapCaCertificateCiphertext;
}
let encryptedBindDN: Buffer | undefined;
if (bindDN) {
const { cipherTextBlob: bindDNCiphertext } = encryptor({
plainText: Buffer.from(bindDN)
});
encryptedBindDN = bindDNCiphertext;
}
const { ldapConfig } = await getLdapConfig(identityId);
const isConnected = await testLDAPConfig({
bindDN: bindDN || ldapConfig.bindDN,
bindPass: bindPass || ldapConfig.bindPass,
caCert: ldapCaCertificate || ldapConfig.caCert,
url: url || ldapConfig.url
});
if (!isConnected) {
throw new BadRequestError({
message:
"Failed to connect to LDAP server. Please ensure that the LDAP server is running and your credentials are correct."
});
}
const updatedLdapAuth = await identityLdapAuthDAL.updateById(identityLdapAuth.id, {
url,
searchBase,
searchFilter,
encryptedBindDN,
encryptedBindPass,
encryptedLdapCaCertificate,
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined
});
return { ...updatedLdapAuth, orgId: identityMembershipOrg.orgId };
};
const getLdapAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetLdapAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({
message: "The identity does not have LDAP Auth attached"
});
}
const ldapIdentityAuth = await identityLdapAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.orgId
});
const bindDN = decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedBindDN }).toString();
const bindPass = decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedBindPass }).toString();
const ldapCaCertificate = ldapIdentityAuth.encryptedLdapCaCertificate
? decryptor({ cipherTextBlob: ldapIdentityAuth.encryptedLdapCaCertificate }).toString()
: undefined;
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Read, OrgPermissionSubjects.Identity);
return { ...ldapIdentityAuth, orgId: identityMembershipOrg.orgId, bindDN, bindPass, ldapCaCertificate };
};
const revokeIdentityLdapAuth = async ({
identityId,
actorId,
actor,
actorAuthMethod,
actorOrgId
}: TRevokeLdapAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (!identityMembershipOrg.identity.authMethods.includes(IdentityAuthMethod.LDAP_AUTH)) {
throw new BadRequestError({
message: "The identity does not have LDAP Auth attached"
});
}
const { permission, membership } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
const { permission: rolePermission } = await permissionService.getOrgPermission(
ActorType.IDENTITY,
identityMembershipOrg.identityId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity,
permission,
rolePermission
);
if (!permissionBoundary.isValid)
throw new PermissionBoundaryError({
message: constructPermissionErrorMessage(
"Failed to revoke LDAP auth of identity with more privileged role",
membership.shouldUseNewPrivilegeSystem,
OrgPermissionIdentityActions.RevokeAuth,
OrgPermissionSubjects.Identity
),
details: { missingPermissions: permissionBoundary.missingPermissions }
});
const revokedIdentityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
const [deletedLdapAuth] = await identityLdapAuthDAL.delete({ identityId }, tx);
await identityAccessTokenDAL.delete({ identityId, authMethod: IdentityAuthMethod.LDAP_AUTH }, tx);
return { ...deletedLdapAuth, orgId: identityMembershipOrg.orgId };
});
return revokedIdentityLdapAuth;
};
return {
attachLdapAuth,
getLdapConfig,
updateLdapAuth,
login,
revokeIdentityLdapAuth,
getLdapAuth
};
};

View File

@@ -0,0 +1,56 @@
import { z } from "zod";
import { TProjectPermission } from "@app/lib/types";
export const AllowedFieldsSchema = z.object({
key: z.string().trim(),
value: z
.string()
.trim()
.transform((val) => val.replace(/\s/g, ""))
});
export type TAllowedFields = z.infer<typeof AllowedFieldsSchema>;
export type TAttachLdapAuthDTO = {
identityId: string;
url: string;
searchBase: string;
searchFilter: string;
bindDN: string;
bindPass: string;
ldapCaCertificate?: string;
allowedFields?: TAllowedFields[];
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: { ipAddress: string }[];
isActorSuperAdmin?: boolean;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateLdapAuthDTO = {
identityId: string;
url?: string;
searchBase?: string;
searchFilter?: string;
bindDN?: string;
bindPass?: string;
allowedFields?: TAllowedFields[];
ldapCaCertificate?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TGetLdapAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TLoginLdapAuthDTO = {
identityId: string;
};
export type TRevokeLdapAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;

View File

@@ -8,7 +8,8 @@ export const buildAuthMethods = ({
oidcId,
azureId,
tokenId,
jwtId
jwtId,
ldapId
}: {
uaId?: string;
gcpId?: string;
@@ -18,6 +19,7 @@ export const buildAuthMethods = ({
azureId?: string;
tokenId?: string;
jwtId?: string;
ldapId?: string;
}) => {
return [
...[uaId ? IdentityAuthMethod.UNIVERSAL_AUTH : null],
@@ -27,6 +29,7 @@ export const buildAuthMethods = ({
...[oidcId ? IdentityAuthMethod.OIDC_AUTH : null],
...[azureId ? IdentityAuthMethod.AZURE_AUTH : null],
...[tokenId ? IdentityAuthMethod.TOKEN_AUTH : null],
...[jwtId ? IdentityAuthMethod.JWT_AUTH : null]
...[jwtId ? IdentityAuthMethod.JWT_AUTH : null],
...[ldapId ? IdentityAuthMethod.LDAP_AUTH : null]
].filter((authMethod) => authMethod) as IdentityAuthMethod[];
};

View File

@@ -14,6 +14,7 @@ import {
TIdentityUniversalAuths,
TOrgRoles
} from "@app/db/schemas";
import { TIdentityLdapAuths } from "@app/db/schemas/identity-ldap-auths";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
import { buildKnexFilterForSearchResource } from "@app/lib/search-resource/db";
@@ -81,6 +82,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.IdentityJwtAuth}.identityId`
)
.leftJoin<TIdentityLdapAuths>(
TableName.IdentityLdapAuth,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.IdentityLdapAuth}.identityId`
)
.select(
selectAllTableCols(TableName.IdentityOrgMembership),
@@ -93,7 +99,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth),
db.ref("name").withSchema(TableName.Identity)
);
@@ -200,6 +206,12 @@ export const identityOrgDALFactory = (db: TDbClient) => {
"paginatedIdentity.identityId",
`${TableName.IdentityJwtAuth}.identityId`
)
.leftJoin<TIdentityLdapAuths>(
TableName.IdentityLdapAuth,
"paginatedIdentity.identityId",
`${TableName.IdentityLdapAuth}.identityId`
)
.select(
db.ref("id").withSchema("paginatedIdentity"),
db.ref("role").withSchema("paginatedIdentity"),
@@ -217,7 +229,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth)
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth)
)
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
@@ -259,6 +272,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
oidcId,
azureId,
tokenId,
ldapId,
createdAt,
updatedAt
}) => ({
@@ -290,7 +304,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
oidcId,
azureId,
tokenId,
jwtId
jwtId,
ldapId
})
}
}),
@@ -406,6 +421,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.IdentityJwtAuth}.identityId`
)
.leftJoin(
TableName.IdentityLdapAuth,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.IdentityLdapAuth}.identityId`
)
.select(
db.ref("id").withSchema(TableName.IdentityOrgMembership),
db.ref("total_count").withSchema("searchedIdentities"),
@@ -424,7 +444,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
db.ref("id").as("oidcId").withSchema(TableName.IdentityOidcAuth),
db.ref("id").as("azureId").withSchema(TableName.IdentityAzureAuth),
db.ref("id").as("tokenId").withSchema(TableName.IdentityTokenAuth),
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth)
db.ref("id").as("jwtId").withSchema(TableName.IdentityJwtAuth),
db.ref("id").as("ldapId").withSchema(TableName.IdentityLdapAuth)
)
// cr stands for custom role
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
@@ -467,6 +488,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
oidcId,
azureId,
tokenId,
ldapId,
createdAt,
updatedAt
}) => ({
@@ -498,7 +520,8 @@ export const identityOrgDALFactory = (db: TDbClient) => {
oidcId,
azureId,
tokenId,
jwtId
jwtId,
ldapId
})
}
}),

View File

@@ -0,0 +1,4 @@
---
title: "Attach"
openapi: "POST /api/v1/auth/ldap-auth/identities/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Login"
openapi: "POST /api/v1/auth/ldap-auth/login"
---

View File

@@ -0,0 +1,4 @@
---
title: "Retrieve"
openapi: "GET /api/v1/auth/ldap-auth/identities/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Revoke"
openapi: "DELETE /api/v1/auth/ldap-auth/identities/{identityId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/auth/ldap-auth/identities/{identityId}"
---

View File

@@ -62,7 +62,7 @@ access the Infisical API using the AWS Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -62,7 +62,7 @@ access the Infisical API using the Azure Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -68,7 +68,7 @@ access the Infisical API using the GCP ID Token authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
@@ -237,7 +237,7 @@ access the Infisical API using the GCP IAM authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -57,7 +57,7 @@ In the following steps, we explore how to create and use identities to access th
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -163,7 +163,7 @@ In the following steps, we explore how to create and use identities for your app
</Step>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -0,0 +1,92 @@
---
title: General
description: "Learn how to authenticate with Infisical using LDAP."
---
<Note>
LDAP is a paid feature. If you're using Infisical Cloud, then it is available under the Enterprise Tier. If you're self-hosting Infisical, then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Note>
**LDAP Auth** is an LDAP based authentication method that allows you to authenticate with Infisical using a machine identity configured with an [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) directory.
## Guide
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![Create identity](/images/platform/identities/ldap/identities-org-create-identity.png)
When creating an identity, you specify an organization level role for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![Create identity modal](/images/platform/identities/ldap/identities-org-create-identity-modal.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the Organization Roles tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
</Step>
<Step title="Configuring LDAP auth for your identity">
To configure LDAP auth for your identity, press the **Add Auth Method** button on the identity's page.
![Add auth method](/images/platform/identities/ldap/identities-org-add-auth-method.png)
Now select **LDAP Auth** from the list of available auth methods for the identity.
![Select LDAP auth](/images/platform/identities/ldap/identities-org-add-auth-method-modal.png)
After selecting **LDAP Auth**, you'll see the form you need to fill out to configure LDAP auth for your identity. The following fields are available:
- `URL`: The LDAP server to connect to such as `ldap://ldap.your-org.com`, `ldaps://ldap.myorg.com:636` _(for connection over SSL/TLS)_, etc.
- `Bind DN`: The DN to bind to the LDAP server with.
- `Bind Pass`: The password to bind to the LDAP server with.
- `Search Base / DN`: Base DN under which to perform user search such as `ou=Users,dc=acme,dc=com`.
- `User Search Filter`: Template used to construct the LDAP user search filter such as `(uid={{username}})`; use literal `{{username}}` to have the given username used in the search. The default is `(uid={{username}})` which is compatible with several common directory schemas.
- `Required Attributes`: A key/value pair of attributes that must be present in the LDAP user entry for them to be authenticated. As an example, if you set key `uid` to value `user1,user2,user3`, then only users with `uid` of `user1`, `user2`, or `user3` will be able to login with this identity. Each value is a comma separated list of attributes.
- `CA Certificate`: The CA certificate to use when verifying the LDAP server certificate. This field is optional but recommended.
- `Access Token TTL` _(default is 2592000 equivalent to 30 days)_: The lifetime for an access token in seconds. This value will be referenced at renewal time.
- `Access Token Max TTL` _(default is 2592000 equivalent to 30 days)_: The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.
- `Access Token Max Number of Uses` _(default is 0)_: The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.
- `Access Token Trusted IPs`: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address.
Once you've filled out the form, press **Add** to save your changes.
![Configure LDAP auth](/images/platform/identities/ldap/identities-org-configure-ldap.png)
<Step title="Authenticating with the identity">
After configuring LDAP auth for your identity, you can authenticate with the identity and obtain an access token using your LDAP credentials.
```bash
curl --request POST \
--url https://app.infisical.com/api/v1/auth/ldap-auth/login \
--header 'Content-Type: application/json' \
--data '{
"identityId": "<string>",
"username": "<string>",
"password": "<string>"
}'
```
<Note>
For EU Cloud and Self-Hosted users, make sure to replace `https://app.infisical.com` with `https://eu.infisical.com` or your self-hosted instance's URL in the request URL.
</Note>
If successful, you'll receive an access token in the response body.
```json
{
"accessToken": "your-access-token",
"expiresIn": 2592000,
"accessTokenMaxTTL": 2592000,
"tokenType": "Bearer"
}
```
You can read more about the login API endpoint [here](/api-reference/endpoints/ldap-auth/login).
</Step>
</Step>
</Steps>

View File

@@ -0,0 +1,102 @@
---
title: JumpCloud
description: "Learn how to authenticate with Infisical using LDAP with JumpCloud."
---
<Note>
LDAP is a paid feature. If you're using Infisical Cloud, then it is available under the Enterprise Tier. If you're self-hosting Infisical, then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Note>
**LDAP Auth** is an LDAP based authentication method that allows you to authenticate with Infisical using a machine identity configured with an [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) directory.
## Guide
<Steps>
<Step title="Prepare LDAP in JumpCloud">
In JumpCloud, head to USER MANAGEMENT > Users and create a new user via the Manual user entry option.
This user will be used as a privileged service account to facilitate Infisical's ability to bind/search the LDAP directory.
Next after creating the user, under User Security Settings and Permissions > Permission Settings, check the box next to Enable as LDAP Bind DN.
![User management](/images/platform/identities/ldap/jumpcloud-users-management.png)
</Step>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![Create identity](/images/platform/identities/ldap/identities-org-create-identity.png)
When creating an identity, you specify an organization level role for it to assume; you can configure roles in Organization Settings > Access Control > Organization Roles.
![Create identity modal](/images/platform/identities/ldap/identities-org-create-identity-modal.png)
Now input a few details for your new identity. Here's some guidance for each field:
- Name (required): A friendly name for the identity.
- Role (required): A role from the Organization Roles tab for the identity to assume. The organization role assigned will determine what organization level resources this identity can have access to.
Once you've created an identity, you'll be redirected to a page where you can manage the identity.
</Step>
<Step title="Configuring LDAP auth for your identity">
To configure LDAP auth for your identity, press the **Add Auth Method** button on the identity's page.
![Add auth method](/images/platform/identities/ldap/identities-org-add-auth-method.png)
Now select **LDAP Auth** from the list of available auth methods for the identity.
![Select LDAP auth](/images/platform/identities/ldap/identities-org-add-auth-method-modal.png)
After selecting **LDAP Auth**, you'll see the form you need to fill out to configure LDAP auth for your identity. The following fields are available:
- `URL`: The LDAP server to connect to (`ldaps://ldap.jumpcloud.com:636`).
- `Bind DN`: The distinguished name of object to bind when performing the user search (`uid=<ldap-user-username>,ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
- `Bind Pass`: The password to use along with Bind DN when performing the user search. This is the password for the user created in the previous step.
- `Search Base / DN`: Base DN under which to perform user search (`ou=Users,o=<your-org-id>,dc=jumpcloud,dc=com`).
- `User Search Filter`: Template used to construct the LDAP user search filter (`(uid={{username}})`).
- `Required Attributes`: A key/value pair of attributes that must be present in the LDAP user entry for them to be authenticated. As an example, if you set key `uid` to value `user1,user2,user3`, then only users with `uid` of `user1`, `user2`, or `user3` will be able to login with this identity. Each value is a comma separated list of attributes.
- `CA Certificate`: The CA certificate to use when verifying the LDAP server certificate (instructions to obtain the certificate for JumpCloud [here](https://jumpcloud.com/support/connect-to-ldap-with-tls-ssl)).
- `Access Token TTL` _(default is 2592000 equivalent to 30 days)_: The lifetime for an access token in seconds. This value will be referenced at renewal time.
- `Access Token Max TTL` _(default is 2592000 equivalent to 30 days)_: The maximum lifetime for an access token in seconds. This value will be referenced at renewal time.
- `Access Token Max Number of Uses` _(default is 0)_: The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses.
- `Access Token Trusted IPs`: The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address.
Once you've filled out the form, press **Add** to save your changes.
![Configure LDAP auth](/images/platform/identities/ldap/identities-org-configure-ldap.png)
<Step title="Authenticating with the identity">
After configuring LDAP auth for your identity, you can authenticate with the identity and obtain an access token using your LDAP credentials.
```bash
curl --request POST \
--url https://app.infisical.com/api/v1/auth/ldap-auth/login \
--header 'Content-Type: application/json' \
--data '{
"identityId": "<string>",
"username": "<string>",
"password": "<string>"
}'
```
<Note>
For EU Cloud and Self-Hosted users, make sure to replace `https://app.infisical.com` with `https://eu.infisical.com` or your self-hosted instance's URL in the request URL.
</Note>
If successful, you'll receive an access token in the response body.
```json
{
"accessToken": "your-access-token",
"expiresIn": 2592000,
"accessTokenMaxTTL": 2592000,
"tokenType": "Bearer"
}
```
You can read more about the login API endpoint [here](/api-reference/endpoints/ldap-auth/login).
</Step>
</Step>
</Steps>

View File

@@ -52,7 +52,7 @@ In the following steps, we explore how to create and use identities to access th
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -56,7 +56,7 @@ In the following steps, we explore how to create and use identities to access th
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -55,7 +55,7 @@ In the following steps, we explore how to create and use identities to access th
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -55,7 +55,7 @@ In the following steps, we explore how to create and use identities to access th
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -38,7 +38,7 @@ using the Token Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -42,7 +42,7 @@ using the Universal Auth authentication method.
<Steps>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

@@ -232,7 +232,7 @@ spec:
</Step>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)
@@ -407,7 +407,7 @@ spec:
</Step>
<Step title="Creating an identity">
To create an identity, head to your Organization Settings > Access Control > Machine Identities and press **Create identity**.
To create an identity, head to your Organization Settings > Access Control > Identities and press **Create identity**.
![identities organization](/images/platform/identities/identities-org.png)

View File

@@ -247,68 +247,88 @@
{
"group": "Authentication Methods",
"pages": [
"documentation/platform/auth-methods/email-password",
"documentation/platform/token",
"documentation/platform/identities/token-auth",
"documentation/platform/identities/universal-auth",
"documentation/platform/identities/kubernetes-auth",
"documentation/platform/identities/gcp-auth",
"documentation/platform/identities/azure-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/identities/jwt-auth",
{
"group": "OIDC Auth",
"group": "User Authentication",
"pages": [
"documentation/platform/identities/oidc-auth/general",
"documentation/platform/identities/oidc-auth/github",
"documentation/platform/identities/oidc-auth/circleci",
"documentation/platform/identities/oidc-auth/gitlab",
"documentation/platform/identities/oidc-auth/terraform-cloud"
]
},
"documentation/platform/mfa",
{
"group": "SSO",
"pages": [
"documentation/platform/sso/overview",
"documentation/platform/sso/google",
"documentation/platform/sso/github",
"documentation/platform/sso/gitlab",
"documentation/platform/sso/okta",
"documentation/platform/sso/azure",
"documentation/platform/sso/jumpcloud",
"documentation/platform/sso/keycloak-saml",
"documentation/platform/sso/google-saml",
"documentation/platform/sso/auth0-saml",
"documentation/platform/auth-methods/email-password",
{
"group": "Keycloak OIDC",
"group": "SSO",
"pages": [
"documentation/platform/sso/keycloak-oidc/overview",
"documentation/platform/sso/keycloak-oidc/group-membership-mapping"
"documentation/platform/sso/overview",
"documentation/platform/sso/google",
"documentation/platform/sso/github",
"documentation/platform/sso/gitlab",
"documentation/platform/sso/okta",
"documentation/platform/sso/azure",
"documentation/platform/sso/jumpcloud",
"documentation/platform/sso/keycloak-saml",
"documentation/platform/sso/google-saml",
"documentation/platform/sso/auth0-saml",
{
"group": "Keycloak OIDC",
"pages": [
"documentation/platform/sso/keycloak-oidc/overview",
"documentation/platform/sso/keycloak-oidc/group-membership-mapping"
]
},
"documentation/platform/sso/auth0-oidc",
"documentation/platform/sso/general-oidc"
]
},
"documentation/platform/sso/auth0-oidc",
"documentation/platform/sso/general-oidc"
{
"group": "LDAP",
"pages": [
"documentation/platform/ldap/overview",
"documentation/platform/ldap/jumpcloud",
"documentation/platform/ldap/general"
]
},
{
"group": "SCIM",
"pages": [
"documentation/platform/scim/overview",
"documentation/platform/scim/okta",
"documentation/platform/scim/azure",
"documentation/platform/scim/jumpcloud",
"documentation/platform/scim/group-mappings"
]
}
]
},
{
"group": "LDAP",
"group": "Machine Identities",
"pages": [
"documentation/platform/ldap/overview",
"documentation/platform/ldap/jumpcloud",
"documentation/platform/ldap/general"
]
},
{
"group": "SCIM",
"pages": [
"documentation/platform/scim/overview",
"documentation/platform/scim/okta",
"documentation/platform/scim/azure",
"documentation/platform/scim/jumpcloud",
"documentation/platform/scim/group-mappings"
"documentation/platform/identities/token-auth",
"documentation/platform/identities/universal-auth",
"documentation/platform/identities/kubernetes-auth",
"documentation/platform/identities/gcp-auth",
"documentation/platform/identities/azure-auth",
"documentation/platform/identities/aws-auth",
"documentation/platform/identities/jwt-auth",
{
"group": "OIDC Auth",
"pages": [
"documentation/platform/identities/oidc-auth/general",
"documentation/platform/identities/oidc-auth/github",
"documentation/platform/identities/oidc-auth/circleci",
"documentation/platform/identities/oidc-auth/gitlab",
"documentation/platform/identities/oidc-auth/terraform-cloud"
]
},
{
"group": "LDAP Auth",
"pages": [
"documentation/platform/identities/ldap-auth/general",
"documentation/platform/identities/ldap-auth/jumpcloud"
]
}
]
},
"documentation/platform/token",
"documentation/platform/mfa",
"documentation/platform/github-org-sync"
]
},
@@ -717,6 +737,16 @@
"api-reference/endpoints/jwt-auth/revoke"
]
},
{
"group": "LDAP Auth",
"pages": [
"api-reference/endpoints/ldap-auth/login",
"api-reference/endpoints/ldap-auth/attach",
"api-reference/endpoints/ldap-auth/retrieve",
"api-reference/endpoints/ldap-auth/update",
"api-reference/endpoints/ldap-auth/revoke"
]
},
{
"group": "Groups",
"pages": [

View File

@@ -182,10 +182,17 @@ export const eventToNameMap: { [K in EventType]: string } = {
"Microsoft Teams Workflow Integration Check Installation Status",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS]: "Get Microsoft Teams tenant teams",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET]: "Get Microsoft Teams Workflow Integration",
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]: "List Microsoft Teams Workflow Integration"
[EventType.MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST]:
"List Microsoft Teams Workflow Integration",
[EventType.LOGIN_IDENTITY_LDAP_AUTH]: "Identity login via LDAP Auth",
[EventType.ADD_IDENTITY_LDAP_AUTH]: "Attached LDAP Auth to identity",
[EventType.UPDATE_IDENTITY_LDAP_AUTH]: "Updated LDAP Auth for identity",
[EventType.GET_IDENTITY_LDAP_AUTH]: "Retrieved LDAP Auth for identity",
[EventType.REVOKE_IDENTITY_LDAP_AUTH]: "Revoked LDAP Auth for identity"
};
export const userAgentTTypeoNameMap: { [K in UserAgentType]: string } = {
export const userAgentTypeToNameMap: { [K in UserAgentType]: string } = {
[UserAgentType.WEB]: "Web",
[UserAgentType.CLI]: "CLI",
[UserAgentType.K8_OPERATOR]: "K8s operator",

View File

@@ -47,6 +47,13 @@ export enum EventType {
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",

View File

@@ -8,5 +8,6 @@ export const identityAuthToNameMap: { [I in IdentityAuthMethod]: string } = {
[IdentityAuthMethod.AWS_AUTH]: "AWS Auth",
[IdentityAuthMethod.AZURE_AUTH]: "Azure Auth",
[IdentityAuthMethod.OIDC_AUTH]: "OIDC Auth",
[IdentityAuthMethod.LDAP_AUTH]: "LDAP Auth",
[IdentityAuthMethod.JWT_AUTH]: "JWT Auth"
};

View File

@@ -6,6 +6,7 @@ export enum IdentityAuthMethod {
AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth",
OIDC_AUTH = "oidc-auth",
LDAP_AUTH = "ldap-auth",
JWT_AUTH = "jwt-auth"
}

View File

@@ -1,51 +1,4 @@
export { identityAuthToNameMap } from "./constants";
export { IdentityAuthMethod } from "./enums";
export {
useAddIdentityAwsAuth,
useAddIdentityAzureAuth,
useAddIdentityGcpAuth,
useAddIdentityJwtAuth,
useAddIdentityKubernetesAuth,
useAddIdentityOidcAuth,
useAddIdentityTokenAuth,
useAddIdentityUniversalAuth,
useCreateIdentity,
useCreateIdentityUniversalAuthClientSecret,
useCreateTokenIdentityTokenAuth,
useDeleteIdentity,
useDeleteIdentityAwsAuth,
useDeleteIdentityAzureAuth,
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth,
useRevokeIdentityTokenAuthToken,
useRevokeIdentityUniversalAuthClientSecret,
useUpdateIdentity,
useUpdateIdentityAwsAuth,
useUpdateIdentityAzureAuth,
useUpdateIdentityGcpAuth,
useUpdateIdentityJwtAuth,
useUpdateIdentityKubernetesAuth,
useUpdateIdentityOidcAuth,
useUpdateIdentityTokenAuth,
useUpdateIdentityTokenAuthToken,
useUpdateIdentityUniversalAuth
} from "./mutations";
export {
useGetIdentityAwsAuth,
useGetIdentityAzureAuth,
useGetIdentityById,
useGetIdentityGcpAuth,
useGetIdentityJwtAuth,
useGetIdentityKubernetesAuth,
useGetIdentityOidcAuth,
useGetIdentityProjectMemberships,
useGetIdentityTokenAuth,
useGetIdentityTokensTokenAuth,
useGetIdentityUniversalAuth,
useGetIdentityUniversalAuthClientSecrets,
useSearchIdentities
} from "./queries";
export * from "./mutations";
export * from "./queries";

View File

@@ -10,6 +10,7 @@ import {
AddIdentityGcpAuthDTO,
AddIdentityJwtAuthDTO,
AddIdentityKubernetesAuthDTO,
AddIdentityLdapAuthDTO,
AddIdentityOidcAuthDTO,
AddIdentityTokenAuthDTO,
AddIdentityUniversalAuthDTO,
@@ -25,6 +26,7 @@ import {
DeleteIdentityGcpAuthDTO,
DeleteIdentityJwtAuthDTO,
DeleteIdentityKubernetesAuthDTO,
DeleteIdentityLdapAuthDTO,
DeleteIdentityOidcAuthDTO,
DeleteIdentityTokenAuthDTO,
DeleteIdentityUniversalAuthClientSecretDTO,
@@ -36,6 +38,7 @@ import {
IdentityGcpAuth,
IdentityJwtAuth,
IdentityKubernetesAuth,
IdentityLdapAuth,
IdentityOidcAuth,
IdentityTokenAuth,
IdentityUniversalAuth,
@@ -47,6 +50,7 @@ import {
UpdateIdentityGcpAuthDTO,
UpdateIdentityJwtAuthDTO,
UpdateIdentityKubernetesAuthDTO,
UpdateIdentityLdapAuthDTO,
UpdateIdentityOidcAuthDTO,
UpdateIdentityTokenAuthDTO,
UpdateIdentityUniversalAuthDTO,
@@ -1049,3 +1053,116 @@ export const useRevokeIdentityTokenAuthToken = () => {
}
});
};
export const useAddIdentityLdapAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityLdapAuth, object, AddIdentityLdapAuthDTO>({
mutationFn: async ({
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`,
{
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return data.identityLdapAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
});
}
});
};
export const useUpdateIdentityLdapAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityLdapAuth, object, UpdateIdentityLdapAuthDTO>({
mutationFn: async ({
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}) => {
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`,
{
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}
);
return data.identityLdapAuth;
},
onSuccess: (_, { identityId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
});
}
});
};
export const useDeleteIdentityLdapAuth = () => {
const queryClient = useQueryClient();
return useMutation<IdentityLdapAuth, object, DeleteIdentityLdapAuthDTO>({
mutationFn: async ({ identityId }) => {
const { data } = await apiRequest.delete(`/api/v1/auth/ldap-auth/identities/${identityId}`);
return data.identityLdapAuth;
},
onSuccess: (_, { organizationId, identityId }) => {
queryClient.invalidateQueries({
queryKey: organizationKeys.getOrgIdentityMemberships(organizationId)
});
queryClient.invalidateQueries({ queryKey: identitiesKeys.getIdentityById(identityId) });
queryClient.invalidateQueries({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId)
});
}
});
};

View File

@@ -11,6 +11,7 @@ import {
IdentityGcpAuth,
IdentityJwtAuth,
IdentityKubernetesAuth,
IdentityLdapAuth,
IdentityMembership,
IdentityMembershipOrg,
IdentityOidcAuth,
@@ -34,6 +35,7 @@ export const identitiesKeys = {
getIdentityAzureAuth: (identityId: string) => [{ identityId }, "identity-azure-auth"] as const,
getIdentityTokenAuth: (identityId: string) => [{ identityId }, "identity-token-auth"] as const,
getIdentityJwtAuth: (identityId: string) => [{ identityId }, "identity-jwt-auth"] as const,
getIdentityLdapAuth: (identityId: string) => [{ identityId }, "identity-ldap-auth"] as const,
getIdentityTokensTokenAuth: (identityId: string) =>
[{ identityId }, "identity-tokens-token-auth"] as const,
getIdentityProjectMemberships: (identityId: string) =>
@@ -231,6 +233,27 @@ export const useGetIdentityTokenAuth = (
});
};
export const useGetIdentityLdapAuth = (
identityId: string,
options?: TReactQueryOptions["options"]
) => {
return useQuery({
queryKey: identitiesKeys.getIdentityLdapAuth(identityId),
queryFn: async () => {
const {
data: { identityLdapAuth }
} = await apiRequest.get<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`
);
return identityLdapAuth;
},
staleTime: 0,
gcTime: 0,
...options,
enabled: Boolean(identityId) && (options?.enabled ?? true)
});
};
export const useGetIdentityTokensTokenAuth = (identityId: string) => {
return useQuery({
enabled: Boolean(identityId),

View File

@@ -425,6 +425,72 @@ export type IdentityTokenAuth = {
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityLdapAuthDTO = {
organizationId: string;
identityId: string;
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
searchFilter: string;
ldapCaCertificate?: string;
allowedFields?: {
key: string;
value: string;
}[];
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: {
ipAddress: string;
}[];
};
export type UpdateIdentityLdapAuthDTO = {
identityId: string;
organizationId: string;
url?: string;
bindDN?: string;
bindPass?: string;
searchBase?: string;
searchFilter?: string;
ldapCaCertificate?: string;
allowedFields?: {
key: string;
value: string;
}[];
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: {
ipAddress: string;
}[];
};
export type DeleteIdentityLdapAuthDTO = {
organizationId: string;
identityId: string;
};
export type IdentityLdapAuth = {
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
searchFilter: string;
ldapCaCertificate?: string;
allowedFields?: {
key: string;
value: string;
}[];
identityId: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: IdentityTrustedIp[];
};
export type AddIdentityTokenAuthDTO = {
organizationId: string;
identityId: string;

View File

@@ -13,6 +13,7 @@ import { IdentityAzureAuthForm } from "./IdentityAzureAuthForm";
import { IdentityGcpAuthForm } from "./IdentityGcpAuthForm";
import { IdentityJwtAuthForm } from "./IdentityJwtAuthForm";
import { IdentityKubernetesAuthForm } from "./IdentityKubernetesAuthForm";
import { IdentityLdapAuthForm } from "./IdentityLdapAuthForm";
import { IdentityOidcAuthForm } from "./IdentityOidcAuthForm";
import { IdentityTokenAuthForm } from "./IdentityTokenAuthForm";
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
@@ -46,6 +47,7 @@ const identityAuthMethods = [
{ label: "AWS Auth", value: IdentityAuthMethod.AWS_AUTH },
{ label: "Azure Auth", value: IdentityAuthMethod.AZURE_AUTH },
{ label: "OIDC Auth", value: IdentityAuthMethod.OIDC_AUTH },
{ label: "LDAP Auth", value: IdentityAuthMethod.LDAP_AUTH },
{
label: "JWT Auth",
value: IdentityAuthMethod.JWT_AUTH
@@ -186,6 +188,16 @@ export const IdentityAuthMethodModalContent = ({
handlePopUpToggle={handlePopUpToggle}
/>
)
},
[IdentityAuthMethod.LDAP_AUTH]: {
render: () => (
<IdentityLdapAuthForm
identityId={identityAuthMethodData.identityId}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
)
}
};

View File

@@ -0,0 +1,608 @@
import { useEffect, useState } from "react";
import { Controller, useFieldArray, useForm } from "react-hook-form";
import { faPlus, faQuestionCircle, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
FormControl,
IconButton,
Input,
Tab,
TabList,
TabPanel,
Tabs,
TextArea,
Tooltip
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context";
import {
useAddIdentityLdapAuth,
useGetIdentityLdapAuth,
useUpdateIdentityLdapAuth
} from "@app/hooks/api";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { IdentityFormTab } from "./types";
const schema = z
.object({
url: z.string().min(1),
bindDN: z.string(),
bindPass: z.string(),
searchBase: z.string(),
searchFilter: z.string(), // defaults to (uid={{username}})
ldapCaCertificate: z
.string()
.optional()
.transform((val) => val || undefined),
allowedFields: z
.object({
key: z.string().trim(),
value: z
.string()
.trim()
.transform((val) => val.replace(/\s/g, ""))
})
.array()
.optional(),
accessTokenTTL: z.string().refine((val) => Number(val) <= 315360000, {
message: "Access Token TTL cannot be greater than 315360000"
}),
accessTokenMaxTTL: z.string().refine((val) => Number(val) <= 315360000, {
message: "Access Token Max TTL cannot be greater than 315360000"
}),
accessTokenNumUsesLimit: z.string(),
accessTokenTrustedIps: z
.array(
z.object({
ipAddress: z.string().max(50)
})
)
.min(1)
})
.required();
export type FormData = z.infer<typeof schema>;
type Props = {
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["identityAuthMethod"]>,
state?: boolean
) => void;
identityId?: string;
isUpdate?: boolean;
};
export const IdentityLdapAuthForm = ({
handlePopUpOpen,
handlePopUpToggle,
identityId,
isUpdate
}: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { subscription } = useSubscription();
const { mutateAsync: addMutateAsync } = useAddIdentityLdapAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityLdapAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const { data } = useGetIdentityLdapAuth(identityId ?? "", {
enabled: isUpdate
});
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
url: "",
bindDN: "",
bindPass: "",
searchBase: "",
searchFilter: "(uid={{username}})",
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
}
});
const {
fields: accessTokenTrustedIpsFields,
append: appendAccessTokenTrustedIp,
remove: removeAccessTokenTrustedIp
} = useFieldArray({ control, name: "accessTokenTrustedIps" });
const {
fields: allowedFieldsFields,
append: appendAllowedField,
remove: removeAllowedField
} = useFieldArray({ control, name: "allowedFields" });
useEffect(() => {
if (data) {
reset({
url: data.url,
bindDN: data.bindDN,
bindPass: data.bindPass,
searchBase: data.searchBase,
searchFilter: data.searchFilter,
ldapCaCertificate: data.ldapCaCertificate || undefined,
allowedFields: data.allowedFields,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
accessTokenTrustedIps: data.accessTokenTrustedIps.map(
({ ipAddress, prefix }: IdentityTrustedIp) => {
return {
ipAddress: `${ipAddress}${prefix !== undefined ? `/${prefix}` : ""}`
};
}
)
});
} else {
reset({
url: "",
bindDN: "",
bindPass: "",
searchBase: "",
searchFilter: "(uid={{username}})",
ldapCaCertificate: undefined,
allowedFields: [],
accessTokenTTL: "2592000",
accessTokenMaxTTL: "2592000",
accessTokenNumUsesLimit: "0",
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
}
}, [data]);
useEffect(() => {
if (!subscription?.ldap) {
handlePopUpOpen("upgradePlan");
handlePopUpToggle("identityAuthMethod", false);
}
}, [subscription]);
const onFormSubmit = async ({
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}: FormData) => {
try {
if (!identityId) return;
if (data) {
await updateMutateAsync({
organizationId: orgId,
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
} else {
await addMutateAsync({
organizationId: orgId,
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
}
handlePopUpToggle("identityAuthMethod", false);
createNotification({
text: `Successfully ${isUpdate ? "updated" : "configured"} auth method`,
type: "success"
});
reset();
} catch {
createNotification({
text: `Failed to ${isUpdate ? "update" : "configure"} identity`,
type: "error"
});
}
};
return (
<form
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
[
"url",
"bindDN",
"bindPass",
"searchBase",
"searchFilter",
"accessTokenTTL",
"allowedFields",
"accessTokenMaxTTL",
"accessTokenNumUsesLimit"
].includes(Object.keys(fields)[0])
? IdentityFormTab.Configuration
: IdentityFormTab.Advanced
);
})}
>
<Tabs value={tabValue} onValueChange={(value) => setTabValue(value as IdentityFormTab)}>
<TabList>
<Tab value={IdentityFormTab.Configuration}>Configuration</Tab>
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
<Controller
control={control}
defaultValue="2592000"
name="url"
render={({ field, fieldState: { error } }) => (
<FormControl
label="LDAP URL"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="ldaps://domain-or-ip:636" type="text" />
</FormControl>
)}
/>
<Controller
control={control}
name="bindDN"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="Bind DN"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="bindPass"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="Bind Pass"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="********" type="password" />
</FormControl>
)}
/>
<Controller
control={control}
name="searchBase"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="Search Base / DN"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="ou=machines,dc=acme,dc=com" />
</FormControl>
)}
/>
<Controller
control={control}
name="searchFilter"
render={({ field, fieldState: { error } }) => (
<FormControl
isRequired
label="Search Filter"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="(uid={{username}})" />
</FormControl>
)}
/>
{allowedFieldsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`allowedFields.${index}.key`}
render={({ field, fieldState: { error } }) => {
const isFirstField = index === 0;
return (
<FormControl
className="mb-0 flex-grow"
label={isFirstField ? "Required Attributes" : undefined}
icon={
isFirstField ? (
<Tooltip
className="max-w-[420px]"
content={
<div className="max-h-[300px] space-y-4 overflow-y-auto text-sm">
<p>
Specify the fields that the user must contain in their LDAP entry
in order to authenticate with this identity. If nothing is
specified, all users in the configured LDAP directory will be able
to authenticate.
<p className="mt-2">
You can specify multiple required attributes by separating them
with a comma.
</p>
</p>
<div className="space-y-2">
<p>Example:</p>
<p className="text-xs text-gray-400">
&apos;uid&apos; → &apos;user1,user2,user3&apos;
<br />
&apos;mail&apos; → &apos;user@example.com&apos;
</p>
</div>
<p>
The above example would allow users with the UID user1, user2, or
user3 to authenticate but only if their emails also match
user@example.com
</p>
</div>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" />
</Tooltip>
) : undefined
}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="uid"
/>
</FormControl>
);
}}
/>
<Controller
control={control}
name={`allowedFields.${index}.value`}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => field.onChange(e)}
placeholder="userid1,userid2,userid3"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => removeAllowedField(index)}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() =>
appendAllowedField({
key: "",
value: ""
})
}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add Required Attribute
</Button>
</div>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token TTL (seconds)"
tooltipText="The lifetime for an acccess token in seconds. This value will be referenced at renewal time."
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max TTL (seconds)"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum lifetime for an access token in seconds. This value will be referenced at renewal time."
>
<Input {...field} placeholder="2592000" type="number" min="0" step="1" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Access Token Max Number of Uses"
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The maximum number of times that an access token can be used; a value of 0 implies infinite number of uses."
>
<Input {...field} placeholder="0" type="number" min="0" step="1" />
</FormControl>
)}
/>
</TabPanel>
<TabPanel value={IdentityFormTab.Advanced}>
<Controller
control={control}
name="ldapCaCertificate"
render={({ field, fieldState: { error } }) => (
<FormControl
label="CA Certificate"
isOptional
errorText={error?.message}
isError={Boolean(error)}
tooltipText="An optional PEM-encoded CA cert for the LDAP server. This is used by the TLS client for secure communication with the LDAP server."
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
{accessTokenTrustedIpsFields.map(({ id }, index) => (
<div className="mb-3 flex items-end space-x-2" key={id}>
<Controller
control={control}
name={`accessTokenTrustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Access Token Trusted IPs" : undefined}
isError={Boolean(error)}
errorText={error?.message}
tooltipText="The IPs or CIDR ranges that access tokens can be used from. By default, each token is given the 0.0.0.0/0, allowing usage from any network address."
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => {
if (subscription?.ipAllowlisting) {
removeAccessTokenTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="p-3"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
))}
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() => {
if (subscription?.ipAllowlisting) {
appendAccessTokenTrustedIp({
ipAddress: "0.0.0.0/0"
});
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
Add IP Address
</Button>
</div>
</TabPanel>
</Tabs>
<div className="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isUpdate ? "Update" : "Add"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpToggle("identityAuthMethod", false)}
>
Cancel
</Button>
</div>
</form>
);
};

View File

@@ -30,7 +30,7 @@ export const Route = createFileRoute(
link: linkOptions({ to: "/" })
},
{
label: "access control"
label: "Access Control"
}
]
})

View File

@@ -38,7 +38,7 @@ import { useGetUserWorkspaces } from "@app/hooks/api";
import {
eventToNameMap,
secretEvents,
userAgentTTypeoNameMap
userAgentTypeToNameMap
} from "@app/hooks/api/auditLogs/constants";
import { EventType } from "@app/hooks/api/auditLogs/enums";
import { UserAgentType } from "@app/hooks/api/auth/types";
@@ -47,7 +47,7 @@ import { LogFilterItem } from "./LogFilterItem";
import { AuditLogFilterFormData, Presets } from "./types";
const eventTypes = Object.entries(eventToNameMap).map(([value, label]) => ({ label, value }));
const userAgentTypes = Object.entries(userAgentTTypeoNameMap).map(([value, label]) => ({
const userAgentTypes = Object.entries(userAgentTypeToNameMap).map(([value, label]) => ({
label,
value
}));

View File

@@ -11,6 +11,7 @@ import {
useDeleteIdentityGcpAuth,
useDeleteIdentityJwtAuth,
useDeleteIdentityKubernetesAuth,
useDeleteIdentityLdapAuth,
useDeleteIdentityOidcAuth,
useDeleteIdentityTokenAuth,
useDeleteIdentityUniversalAuth
@@ -22,6 +23,7 @@ import { ViewIdentityAzureAuthContent } from "./ViewIdentityAzureAuthContent";
import { ViewIdentityGcpAuthContent } from "./ViewIdentityGcpAuthContent";
import { ViewIdentityJwtAuthContent } from "./ViewIdentityJwtAuthContent";
import { ViewIdentityKubernetesAuthContent } from "./ViewIdentityKubernetesAuthContent";
import { ViewIdentityLdapAuthContent } from "./ViewIdentityLdapAuthContent";
import { ViewIdentityOidcAuthContent } from "./ViewIdentityOidcAuthContent";
import { ViewIdentityTokenAuthContent } from "./ViewIdentityTokenAuthContent";
import { ViewIdentityUniversalAuthContent } from "./ViewIdentityUniversalAuthContent";
@@ -61,6 +63,7 @@ export const Content = ({
const { mutateAsync: revokeAzureAuth } = useDeleteIdentityAzureAuth();
const { mutateAsync: revokeOidcAuth } = useDeleteIdentityOidcAuth();
const { mutateAsync: revokeJwtAuth } = useDeleteIdentityJwtAuth();
const { mutateAsync: revokeLdapAuth } = useDeleteIdentityLdapAuth();
let Component: (props: ViewAuthMethodProps) => JSX.Element;
let revokeMethod: (revokeOptions: TRevokeOptions) => Promise<any>;
@@ -100,6 +103,10 @@ export const Content = ({
revokeMethod = revokeJwtAuth;
Component = ViewIdentityJwtAuthContent;
break;
case IdentityAuthMethod.LDAP_AUTH:
revokeMethod = revokeLdapAuth;
Component = ViewIdentityLdapAuthContent;
break;
default:
throw new Error(`Unhandled Auth Method: ${authMethod}`);
}

View File

@@ -47,10 +47,10 @@ export const ViewIdentityAwsAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -47,10 +47,10 @@ export const ViewIdentityAzureAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -47,10 +47,10 @@ export const ViewIdentityGcpAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -49,10 +49,10 @@ export const ViewIdentityJwtAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -51,10 +51,10 @@ export const ViewIdentityKubernetesAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -0,0 +1,103 @@
import { faBan, faEye } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Badge, EmptyState, Spinner, Tooltip } from "@app/components/v2";
import { useGetIdentityLdapAuth } from "@app/hooks/api";
import { IdentityLdapAuthForm } from "@app/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityLdapAuthForm";
import { ViewIdentityContentWrapper } from "@app/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityContentWrapper";
import { IdentityAuthFieldDisplay } from "./IdentityAuthFieldDisplay";
import { ViewAuthMethodProps } from "./types";
export const ViewIdentityLdapAuthContent = ({
identityId,
handlePopUpToggle,
handlePopUpOpen,
onDelete,
popUp
}: ViewAuthMethodProps) => {
const { data, isPending } = useGetIdentityLdapAuth(identityId);
if (isPending) {
return (
<div className="flex w-full items-center justify-center">
<Spinner className="text-mineshaft-400" />
</div>
);
}
if (!data) {
return (
<EmptyState icon={faBan} title="Could not find LDAP Auth associated with this Identity." />
);
}
if (popUp.identityAuthMethod.isOpen) {
return (
<IdentityLdapAuthForm
identityId={identityId}
isUpdate
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
);
}
return (
<ViewIdentityContentWrapper
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">
{data.accessTokenNumUsesLimit}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Trusted IPs">
{data.accessTokenTrustedIps.map((ip) => ip.ipAddress).join(", ")}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="LDAP URL">{data.url}</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Bind DN">{data.bindDN}</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Bind Pass">
<Tooltip
side="right"
className="max-w-xl p-2"
content={<p className="break-words rounded bg-mineshaft-600 p-2">{data.bindPass}</p>}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Search Base / DN">
{data.searchBase}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Search Filter">{data.searchFilter}</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="CA Certificate">
{data.ldapCaCertificate && (
<Tooltip
side="right"
className="max-w-xl p-2"
content={
<p className="break-words rounded bg-mineshaft-600 p-2">{data.ldapCaCertificate}</p>
}
>
<div className="w-min">
<Badge className="flex h-5 w-min items-center gap-1.5 whitespace-nowrap bg-mineshaft-400/50 text-bunker-300">
<FontAwesomeIcon icon={faEye} />
<span>Reveal</span>
</Badge>
</div>
</Tooltip>
)}
</IdentityAuthFieldDisplay>
</ViewIdentityContentWrapper>
);
};

View File

@@ -48,10 +48,10 @@ export const ViewIdentityOidcAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -50,10 +50,10 @@ export const ViewIdentityTokenAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -62,10 +62,10 @@ export const ViewIdentityUniversalAuthContent = ({
onEdit={() => handlePopUpOpen("identityAuthMethod")}
onDelete={onDelete}
>
<IdentityAuthFieldDisplay label="Access Token TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token TTL (seconds)">
{data.accessTokenTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max TLL (seconds)">
<IdentityAuthFieldDisplay label="Access Token Max TTL (seconds)">
{data.accessTokenMaxTTL}
</IdentityAuthFieldDisplay>
<IdentityAuthFieldDisplay label="Access Token Max Number of Uses">

View File

@@ -20,7 +20,7 @@ export const Route = createFileRoute(
link: linkOptions({ to: "/organization/access-management" })
},
{
label: "identities"
label: "Identities"
}
]
})