Compare commits

...

41 Commits

Author SHA1 Message Date
Maidul Islam
dd0f5cebd2 Merge pull request #4301 from Infisical/docs-product-split
Update docs to be multi-product
2025-08-04 14:54:16 -07:00
Maidul Islam
1b29a4564a fix typos 2025-08-04 14:52:47 -07:00
Maidul Islam
9e3c0c8583 fix links 2025-08-04 14:51:02 -07:00
Maidul Islam
16ebe0f8e7 small nits 2025-08-04 14:11:13 -07:00
carlosmonastyrski
e8eb1b5f8b Merge pull request #4300 from Infisical/feat/machineAuthTemplates
Add Machine Auth Templates
2025-08-04 17:24:10 -03:00
x032205
6e37b9f969 Merge pull request #4309 from Infisical/log-available-auth-methods-on-pass-reset
Log available auth methods on password reset
2025-08-04 16:22:44 -04:00
x032205
899b7fe024 Log available auth methods on password reset 2025-08-04 16:16:52 -04:00
Carlos Monastyrski
098a8b81be Final improvements on machine auth templates 2025-08-04 17:01:44 -03:00
Daniel Hougaard
e852cd8b4a Merge pull request #4287 from cyrgim/add-support-image-pull-secret
feat(helm): add support for imagePullSecrets
2025-08-04 23:36:23 +04:00
Carlos Monastyrski
830a2f9581 Renamed identity auth template permissions 2025-08-04 16:28:57 -03:00
Carlos Monastyrski
dc4db40936 Add space between identities tables 2025-08-04 16:14:24 -03:00
Carlos Monastyrski
0beff3cc1c Fixed /ldap-auth/identities/:identityId response schema 2025-08-04 16:05:39 -03:00
x032205
5a3325fc53 Merge pull request #4308 from Infisical/fix-github-hostname-check
fix github hostname check
2025-08-04 14:37:31 -04:00
Carlos Monastyrski
3dde786621 General improvements on auth templates 2025-08-04 15:29:07 -03:00
Akhil Mohan
da6b233db1 Merge pull request #4307 from Infisical/helm-update-v0.9.5
Update Helm chart to version v0.9.5
2025-08-04 23:57:23 +05:30
x032205
6958f1cfbd fix github hostname check 2025-08-04 14:24:09 -04:00
akhilmhdh
adf7a88d67 Update Helm chart to version v0.9.5 2025-08-04 18:22:44 +00:00
Akhil Mohan
b8cd836225 Merge pull request #4296 from Infisical/feat/operator-ldap
feat: ldap auth for k8s operator
2025-08-04 23:46:19 +05:30
=
6826b1c242 feat: made review changed 2025-08-04 23:36:05 +05:30
Daniel Hougaard
35012fde03 fix: added ldap identity auth example 2025-08-04 21:57:07 +04:00
x032205
6e14b2f793 Merge pull request #4306 from Infisical/log-github-error
log github error
2025-08-04 13:48:38 -04:00
x032205
5a3aa3d608 log github error 2025-08-04 13:42:00 -04:00
Daniel Hougaard
95b327de50 Merge pull request #4299 from Infisical/daniel/injector-ldap-auth-docs
docs(agent-injector): ldap auth method
2025-08-04 21:26:27 +04:00
Scott Wilson
a3c36f82f3 Merge pull request #4305 from Infisical/add-react-import-to-email-components
fix: add react import to email button component
2025-08-04 10:22:10 -07:00
Scott Wilson
42612da57d Merge pull request #4293 from Infisical/minor-ui-feedback
improvements: adjust secret search padding when no clear icon and fix access approval reviewer tooltips display
2025-08-04 10:20:32 -07:00
x032205
98a08d136e Merge pull request #4302 from Infisical/fix-timeout-for-audit-prune
Add timeout to audit log
2025-08-04 12:28:48 -04:00
x032205
6c74b875f3 up to 10 mins 2025-08-04 10:46:10 -04:00
x032205
793cd4c144 Add timeout to audit log 2025-08-04 10:43:25 -04:00
Tuan Dang
dc0cc4c29d Update images for user + machine identities 2025-08-04 18:48:46 +07:00
Tuan Dang
6dd639be60 Update docs to be multi-product 2025-08-04 16:58:00 +07:00
Carlos Monastyrski
ebe05661d3 Addressed pr comments 2025-08-03 13:02:20 -03:00
Carlos Monastyrski
4f0007faa5 Add Machine Auth Templates 2025-08-03 12:19:57 -03:00
Sid
ec0be1166f feat: Secret reminder from date filter (#4289)
* feat: add fromDate in reminders

* feat: update reminder form

* fix: lint

* chore: generate schema

* fix: reminder logic

* fix: update ui

* fix: pr change

---------

Co-authored-by: sidwebworks <xodeveloper@gmail.com>
2025-08-03 01:10:23 +05:30
Daniel Hougaard
899d01237c docs(agent-injector): ldap auth method 2025-08-02 19:43:27 +04:00
=
4b9e57ae61 feat: review changes for reptile 2025-08-01 21:10:26 +05:30
Akhil Mohan
eb27983990 Update k8-operator/packages/util/kubernetes.go
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-08-01 21:08:33 +05:30
=
fa311b032c feat: removed comments 2025-08-01 21:06:17 +05:30
=
71651f85fe docs: ldap auth in operator 2025-08-01 21:04:44 +05:30
=
d28d3449de feat: added ldap authentication to operator 2025-08-01 21:04:29 +05:30
Scott Wilson
4f26365c21 improvements: adjust secret search padding when no clear icon and fix access approval reviewer tooltips 2025-07-31 19:58:26 -07:00
cyrgim
4704774c63 feat(helm): add support for imagePullSecrets 2025-07-31 07:01:51 +02:00
125 changed files with 5180 additions and 1375 deletions

View File

@@ -99,6 +99,7 @@ const main = async () => {
(el) =>
!el.tableName.includes("_migrations") &&
!el.tableName.includes("audit_logs_") &&
!el.tableName.includes("active_locks") &&
el.tableName !== "intermediate_audit_logs"
);

View File

@@ -18,6 +18,7 @@ import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/extern
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
import { TIdentityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template";
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
import { TKmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
@@ -300,6 +301,7 @@ declare module "fastify" {
reminder: TReminderServiceFactory;
bus: TEventBusService;
sse: TServerSentEventsService;
identityAuthTemplate: TIdentityAuthTemplateServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

View File

@@ -494,6 +494,11 @@ import {
TAccessApprovalPoliciesEnvironmentsInsert,
TAccessApprovalPoliciesEnvironmentsUpdate
} from "@app/db/schemas/access-approval-policies-environments";
import {
TIdentityAuthTemplates,
TIdentityAuthTemplatesInsert,
TIdentityAuthTemplatesUpdate
} from "@app/db/schemas/identity-auth-templates";
import {
TIdentityLdapAuths,
TIdentityLdapAuthsInsert,
@@ -878,6 +883,11 @@ declare module "knex/types/tables" {
TIdentityProjectAdditionalPrivilegeInsert,
TIdentityProjectAdditionalPrivilegeUpdate
>;
[TableName.IdentityAuthTemplate]: KnexOriginal.CompositeTableType<
TIdentityAuthTemplates,
TIdentityAuthTemplatesInsert,
TIdentityAuthTemplatesUpdate
>;
[TableName.AccessApprovalPolicy]: KnexOriginal.CompositeTableType<
TAccessApprovalPolicies,

View File

@@ -0,0 +1,19 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.Reminder, "fromDate"))) {
await knex.schema.alterTable(TableName.Reminder, (t) => {
t.timestamp("fromDate", { useTz: true }).nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Reminder, "fromDate")) {
await knex.schema.alterTable(TableName.Reminder, (t) => {
t.dropColumn("fromDate");
});
}
}

View File

@@ -0,0 +1,36 @@
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.IdentityAuthTemplate))) {
await knex.schema.createTable(TableName.IdentityAuthTemplate, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.binary("templateFields").notNullable();
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.string("name", 64).notNullable();
t.string("authMethod").notNullable();
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.IdentityAuthTemplate);
}
if (!(await knex.schema.hasColumn(TableName.IdentityLdapAuth, "templateId"))) {
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
t.uuid("templateId").nullable();
t.foreign("templateId").references("id").inTable(TableName.IdentityAuthTemplate).onDelete("SET NULL");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.IdentityLdapAuth, "templateId")) {
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
t.dropForeign(["templateId"]);
t.dropColumn("templateId");
});
}
await knex.schema.dropTableIfExists(TableName.IdentityAuthTemplate);
await dropOnUpdateTrigger(knex, TableName.IdentityAuthTemplate);
}

View File

@@ -0,0 +1,24 @@
// 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 IdentityAuthTemplatesSchema = z.object({
id: z.string().uuid(),
templateFields: zodBuffer,
orgId: z.string().uuid(),
name: z.string(),
authMethod: z.string(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TIdentityAuthTemplates = z.infer<typeof IdentityAuthTemplatesSchema>;
export type TIdentityAuthTemplatesInsert = Omit<z.input<typeof IdentityAuthTemplatesSchema>, TImmutableDBKeys>;
export type TIdentityAuthTemplatesUpdate = Partial<Omit<z.input<typeof IdentityAuthTemplatesSchema>, TImmutableDBKeys>>;

View File

@@ -25,7 +25,8 @@ export const IdentityLdapAuthsSchema = z.object({
allowedFields: z.unknown().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
accessTokenPeriod: z.coerce.number().default(0)
accessTokenPeriod: z.coerce.number().default(0),
templateId: z.string().uuid().nullable().optional()
});
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;

View File

@@ -91,6 +91,7 @@ export enum TableName {
IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role",
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
IdentityAuthTemplate = "identity_auth_templates",
// used by both identity and users
IdentityMetadata = "identity_metadata",
ResourceMetadata = "resource_metadata",

View File

@@ -14,7 +14,8 @@ export const RemindersSchema = z.object({
repeatDays: z.number().nullable().optional(),
nextReminderDate: z.date(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
fromDate: z.date().nullable().optional()
});
export type TReminders = z.infer<typeof RemindersSchema>;

View File

@@ -0,0 +1,391 @@
import { z } from "zod";
import { IdentityAuthTemplatesSchema } from "@app/db/schemas/identity-auth-templates";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import {
IdentityAuthTemplateMethod,
TEMPLATE_SUCCESS_MESSAGES,
TEMPLATE_VALIDATION_MESSAGES
} from "@app/ee/services/identity-auth-template/identity-auth-template-enums";
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";
const ldapTemplateFieldsSchema = z.object({
url: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.URL_REQUIRED),
bindDN: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.BIND_DN_REQUIRED),
bindPass: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.BIND_PASSWORD_REQUIRED),
searchBase: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.SEARCH_BASE_REQUIRED),
ldapCaCertificate: z.string().trim().optional()
});
export const registerIdentityTemplateRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Create identity auth template",
security: [
{
bearerAuth: []
}
],
body: z.object({
name: z
.string()
.trim()
.min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_REQUIRED)
.max(64, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_MAX_LENGTH),
authMethod: z.nativeEnum(IdentityAuthTemplateMethod),
templateFields: ldapTemplateFieldsSchema
}),
response: {
200: IdentityAuthTemplatesSchema.extend({
templateFields: z.record(z.string(), z.unknown())
})
}
},
handler: async (req) => {
const template = await server.services.identityAuthTemplate.createTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
name: req.body.name,
authMethod: req.body.authMethod,
templateFields: req.body.templateFields
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE,
metadata: {
templateId: template.id,
name: template.name
}
}
});
return template;
}
});
server.route({
method: "PATCH",
url: "/:templateId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Update identity auth template",
security: [
{
bearerAuth: []
}
],
params: z.object({
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
}),
body: z.object({
name: z
.string()
.trim()
.min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_REQUIRED)
.max(64, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_MAX_LENGTH)
.optional(),
templateFields: ldapTemplateFieldsSchema.partial().optional()
}),
response: {
200: IdentityAuthTemplatesSchema.extend({
templateFields: z.record(z.string(), z.unknown())
})
}
},
handler: async (req) => {
const template = await server.services.identityAuthTemplate.updateTemplate({
templateId: req.params.templateId,
name: req.body.name,
templateFields: req.body.templateFields,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE,
metadata: {
templateId: template.id,
name: template.name
}
}
});
return template;
}
});
server.route({
method: "DELETE",
url: "/:templateId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Delete identity auth template",
security: [
{
bearerAuth: []
}
],
params: z.object({
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
const template = await server.services.identityAuthTemplate.deleteTemplate({
templateId: req.params.templateId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE,
metadata: {
templateId: template.id,
name: template.name
}
}
});
return { message: TEMPLATE_SUCCESS_MESSAGES.DELETED };
}
});
server.route({
method: "GET",
url: "/:templateId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Get identity auth template by ID",
security: [
{
bearerAuth: []
}
],
params: z.object({
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
}),
response: {
200: IdentityAuthTemplatesSchema.extend({
templateFields: ldapTemplateFieldsSchema
})
}
},
handler: async (req) => {
const template = await server.services.identityAuthTemplate.getTemplate({
templateId: req.params.templateId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return template;
}
});
server.route({
method: "GET",
url: "/search",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "List identity auth templates",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
limit: z.coerce.number().positive().max(100).default(5).optional(),
offset: z.coerce.number().min(0).default(0).optional(),
search: z.string().optional()
}),
response: {
200: z.object({
templates: IdentityAuthTemplatesSchema.extend({
templateFields: ldapTemplateFieldsSchema
}).array(),
totalCount: z.number()
})
}
},
handler: async (req) => {
const { templates, totalCount } = await server.services.identityAuthTemplate.listTemplates({
limit: req.query.limit,
offset: req.query.offset,
search: req.query.search,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return { templates, totalCount };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Get identity auth templates by authentication method",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
authMethod: z.nativeEnum(IdentityAuthTemplateMethod)
}),
response: {
200: IdentityAuthTemplatesSchema.extend({
templateFields: ldapTemplateFieldsSchema
}).array()
}
},
handler: async (req) => {
const templates = await server.services.identityAuthTemplate.getTemplatesByAuthMethod({
authMethod: req.query.authMethod,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return templates;
}
});
server.route({
method: "GET",
url: "/:templateId/usage",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Get template usage by template ID",
security: [
{
bearerAuth: []
}
],
params: z.object({
templateId: z.string()
}),
response: {
200: z
.object({
identityId: z.string(),
identityName: z.string()
})
.array()
}
},
handler: async (req) => {
const templates = await server.services.identityAuthTemplate.findTemplateUsages({
templateId: req.params.templateId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return templates;
}
});
server.route({
method: "POST",
url: "/:templateId/delete-usage",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
description: "Unlink identity auth template usage",
security: [
{
bearerAuth: []
}
],
params: z.object({
templateId: z.string()
}),
body: z.object({
identityIds: z.string().array()
}),
response: {
200: z
.object({
authId: z.string(),
identityId: z.string(),
identityName: z.string()
})
.array()
}
},
handler: async (req) => {
const templates = await server.services.identityAuthTemplate.unlinkTemplateUsage({
templateId: req.params.templateId,
identityIds: req.body.identityIds,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
return templates;
}
});
};

View File

@@ -13,6 +13,7 @@ import { registerGatewayRouter } from "./gateway-router";
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
import { registerGroupRouter } from "./group-router";
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
import { registerIdentityTemplateRouter } from "./identity-template-router";
import { registerKmipRouter } from "./kmip-router";
import { registerKmipSpecRouter } from "./kmip-spec-router";
import { registerLdapRouter } from "./ldap-router";
@@ -125,6 +126,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerExternalKmsRouter, {
prefix: "/external-kms"
});
await server.register(registerIdentityTemplateRouter, { prefix: "/identity-templates" });
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });

View File

@@ -152,42 +152,54 @@ export const auditLogDALFactory = (db: TDbClient) => {
// delete all audit log that have expired
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const runPrune = async (dbClient: knex.Knex) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const today = new Date();
let deletedAuditLogIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
const today = new Date();
let deletedAuditLogIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
do {
try {
const findExpiredLogSubQuery = (tx || db)(TableName.AuditLog)
.where("expiresAt", "<", today)
.where("createdAt", "<", today) // to use audit log partition
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
.select("id")
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
do {
try {
const findExpiredLogSubQuery = dbClient(TableName.AuditLog)
.where("expiresAt", "<", today)
.where("createdAt", "<", today) // to use audit log partition
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
.select("id")
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
// eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
.whereIn("id", findExpiredLogSubQuery)
.del()
.returning("id");
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete audit log on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
// eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await dbClient(TableName.AuditLog)
.whereIn("id", findExpiredLogSubQuery)
.del()
.returning("id");
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete audit log on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
};
if (tx) {
await runPrune(tx);
} else {
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
await db.transaction(async (trx) => {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
await runPrune(trx);
});
}
};
const create: TAuditLogDALFactory["create"] = async (tx) => {

View File

@@ -161,6 +161,9 @@ export enum EventType {
CREATE_IDENTITY = "create-identity",
UPDATE_IDENTITY = "update-identity",
DELETE_IDENTITY = "delete-identity",
MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE = "machine-identity-auth-template-create",
MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE = "machine-identity-auth-template-update",
MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE = "machine-identity-auth-template-delete",
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
@@ -830,6 +833,30 @@ interface LoginIdentityUniversalAuthEvent {
};
}
interface MachineIdentityAuthTemplateCreateEvent {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE;
metadata: {
templateId: string;
name: string;
};
}
interface MachineIdentityAuthTemplateUpdateEvent {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE;
metadata: {
templateId: string;
name: string;
};
}
interface MachineIdentityAuthTemplateDeleteEvent {
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE;
metadata: {
templateId: string;
name: string;
};
}
interface AddIdentityUniversalAuthEvent {
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
metadata: {
@@ -1325,6 +1352,7 @@ interface AddIdentityLdapAuthEvent {
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url: string;
templateId?: string | null;
};
}
@@ -1338,6 +1366,7 @@ interface UpdateIdentityLdapAuthEvent {
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url?: string;
templateId?: string | null;
};
}
@@ -3439,6 +3468,9 @@ export type Event =
| UpdateIdentityEvent
| DeleteIdentityEvent
| LoginIdentityUniversalAuthEvent
| MachineIdentityAuthTemplateCreateEvent
| MachineIdentityAuthTemplateUpdateEvent
| MachineIdentityAuthTemplateDeleteEvent
| AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent
| DeleteIdentityUniversalAuthEvent

View File

@@ -0,0 +1,83 @@
/* eslint-disable no-case-declarations */
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { buildFindFilter, ormify } from "@app/lib/knex";
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
export type TIdentityAuthTemplateDALFactory = ReturnType<typeof identityAuthTemplateDALFactory>;
export const identityAuthTemplateDALFactory = (db: TDbClient) => {
const identityAuthTemplateOrm = ormify(db, TableName.IdentityAuthTemplate);
const findByOrgId = async (
orgId: string,
{ limit, offset, search, tx }: { limit?: number; offset?: number; search?: string; tx?: Knex } = {}
) => {
let query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ orgId });
let countQuery = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ orgId });
if (search) {
const searchFilter = `%${search.toLowerCase()}%`;
query = query.whereRaw("LOWER(name) LIKE ?", [searchFilter]);
countQuery = countQuery.whereRaw("LOWER(name) LIKE ?", [searchFilter]);
}
query = query.orderBy("createdAt", "desc");
if (limit !== undefined) {
query = query.limit(limit);
}
if (offset !== undefined) {
query = query.offset(offset);
}
const docs = await query;
const [{ count }] = (await countQuery.count("* as count")) as [{ count: string | number }];
return { docs, totalCount: Number(count) };
};
const findByAuthMethod = async (authMethod: string, orgId: string, tx?: Knex) => {
const query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate)
.where({ authMethod, orgId })
.orderBy("createdAt", "desc");
const docs = await query;
return docs;
};
const findTemplateUsages = async (templateId: string, authMethod: string, tx?: Knex) => {
switch (authMethod) {
case IdentityAuthTemplateMethod.LDAP:
const query = (tx || db.replicaNode())(TableName.IdentityLdapAuth)
.join(TableName.Identity, `${TableName.IdentityLdapAuth}.identityId`, `${TableName.Identity}.id`)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter({ templateId }, TableName.IdentityLdapAuth))
.select(
db.ref("identityId").withSchema(TableName.IdentityLdapAuth),
db.ref("name").withSchema(TableName.Identity).as("identityName")
);
const docs = await query;
return docs;
default:
return [];
}
};
const findByIdAndOrgId = async (id: string, orgId: string, tx?: Knex) => {
const query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ id, orgId });
const doc = await query;
return doc?.[0];
};
return {
...identityAuthTemplateOrm,
findByOrgId,
findByAuthMethod,
findTemplateUsages,
findByIdAndOrgId
};
};

View File

@@ -0,0 +1,22 @@
export enum IdentityAuthTemplateMethod {
LDAP = "ldap"
}
export const TEMPLATE_VALIDATION_MESSAGES = {
TEMPLATE_NAME_REQUIRED: "Template name is required",
TEMPLATE_NAME_MAX_LENGTH: "Template name must be at most 64 characters long",
AUTH_METHOD_REQUIRED: "Auth method is required",
TEMPLATE_ID_REQUIRED: "Template ID is required",
LDAP: {
URL_REQUIRED: "LDAP URL is required",
BIND_DN_REQUIRED: "Bind DN is required",
BIND_PASSWORD_REQUIRED: "Bind password is required",
SEARCH_BASE_REQUIRED: "Search base is required"
}
} as const;
export const TEMPLATE_SUCCESS_MESSAGES = {
CREATED: "Template created successfully",
UPDATED: "Template updated successfully",
DELETED: "Template deleted successfully"
} as const;

View File

@@ -0,0 +1,454 @@
import { ForbiddenError } from "@casl/ability";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import {
OrgPermissionMachineIdentityAuthTemplateActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgPermission } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { TIdentityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TIdentityAuthTemplateDALFactory } from "./identity-auth-template-dal";
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
import {
TDeleteIdentityAuthTemplateDTO,
TFindTemplateUsagesDTO,
TGetIdentityAuthTemplateDTO,
TGetTemplatesByAuthMethodDTO,
TLdapTemplateFields,
TListIdentityAuthTemplatesDTO,
TUnlinkTemplateUsageDTO
} from "./identity-auth-template-types";
type TIdentityAuthTemplateServiceFactoryDep = {
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
identityLdapAuthDAL: TIdentityLdapAuthDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
};
export type TIdentityAuthTemplateServiceFactory = ReturnType<typeof identityAuthTemplateServiceFactory>;
export const identityAuthTemplateServiceFactory = ({
identityAuthTemplateDAL,
identityLdapAuthDAL,
permissionService,
kmsService,
licenseService,
auditLogService
}: TIdentityAuthTemplateServiceFactoryDep) => {
// Plan check
const $checkPlan = async (orgId: string) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.machineIdentityAuthTemplates)
throw new BadRequestError({
message:
"Failed to use identity auth template due to plan restriction. Upgrade plan to access machine identity auth templates."
});
};
const createTemplate = async ({
name,
authMethod,
templateFields,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: {
name: string;
authMethod: string;
templateFields: Record<string, unknown>;
} & Omit<TOrgPermission, "orgId">) => {
await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const { encryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: actorOrgId
});
const template = await identityAuthTemplateDAL.create({
name,
authMethod,
templateFields: encryptor({ plainText: Buffer.from(JSON.stringify(templateFields)) }).cipherTextBlob,
orgId: actorOrgId
});
return { ...template, templateFields };
};
const updateTemplate = async ({
templateId,
name,
templateFields,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: {
templateId: string;
name?: string;
templateFields?: Record<string, unknown>;
} & Omit<TOrgPermission, "orgId">) => {
await $checkPlan(actorOrgId);
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
if (!template) {
throw new NotFoundError({ message: "Template not found" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
template.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const { encryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: template.orgId
});
let finalTemplateFields: Record<string, unknown> = {};
const updatedTemplate = await identityAuthTemplateDAL.transaction(async (tx) => {
const authTemplate = await identityAuthTemplateDAL.updateById(
templateId,
{
name,
...(templateFields && {
templateFields: encryptor({ plainText: Buffer.from(JSON.stringify(templateFields)) }).cipherTextBlob
})
},
tx
);
if (templateFields && template.authMethod === IdentityAuthTemplateMethod.LDAP) {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: template.orgId
});
const currentTemplateFields = JSON.parse(
decryptor({ cipherTextBlob: template.templateFields }).toString()
) as TLdapTemplateFields;
const mergedTemplateFields: TLdapTemplateFields = { ...currentTemplateFields, ...templateFields };
finalTemplateFields = mergedTemplateFields;
const ldapUpdateData: {
url?: string;
searchBase?: string;
encryptedBindDN?: Buffer;
encryptedBindPass?: Buffer;
encryptedLdapCaCertificate?: Buffer;
} = {};
if ("url" in templateFields) {
ldapUpdateData.url = mergedTemplateFields.url;
}
if ("searchBase" in templateFields) {
ldapUpdateData.searchBase = mergedTemplateFields.searchBase;
}
if ("bindDN" in templateFields) {
ldapUpdateData.encryptedBindDN = encryptor({
plainText: Buffer.from(mergedTemplateFields.bindDN)
}).cipherTextBlob;
}
if ("bindPass" in templateFields) {
ldapUpdateData.encryptedBindPass = encryptor({
plainText: Buffer.from(mergedTemplateFields.bindPass)
}).cipherTextBlob;
}
if ("ldapCaCertificate" in templateFields) {
ldapUpdateData.encryptedLdapCaCertificate = encryptor({
plainText: Buffer.from(mergedTemplateFields.ldapCaCertificate || "")
}).cipherTextBlob;
}
if (Object.keys(ldapUpdateData).length > 0) {
const updatedLdapAuths = await identityLdapAuthDAL.update({ templateId }, ldapUpdateData, tx);
await Promise.all(
updatedLdapAuths.map(async (updatedLdapAuth) => {
await auditLogService.createAuditLog({
actor: {
type: ActorType.PLATFORM,
metadata: {}
},
orgId: actorOrgId,
event: {
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
metadata: {
identityId: updatedLdapAuth.identityId,
templateId: template.id
}
}
});
})
);
}
}
return authTemplate;
});
return { ...updatedTemplate, templateFields: finalTemplateFields };
};
const deleteTemplate = async ({
templateId,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TDeleteIdentityAuthTemplateDTO) => {
await $checkPlan(actorOrgId);
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
if (!template) {
throw new NotFoundError({ message: "Template not found" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
template.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const deletedTemplate = await identityAuthTemplateDAL.transaction(async (tx) => {
// Remove template reference from identityLdapAuth records
const updatedLdapAuths = await identityLdapAuthDAL.update({ templateId }, { templateId: null }, tx);
await Promise.all(
updatedLdapAuths.map(async (updatedLdapAuth) => {
await auditLogService.createAuditLog({
actor: {
type: ActorType.PLATFORM,
metadata: {}
},
orgId: actorOrgId,
event: {
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
metadata: {
identityId: updatedLdapAuth.identityId,
templateId: template.id
}
}
});
})
);
// Delete the template
const [deletedTpl] = await identityAuthTemplateDAL.delete({ id: templateId }, tx);
return deletedTpl;
});
return deletedTemplate;
};
const getTemplate = async ({
templateId,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TGetIdentityAuthTemplateDTO) => {
await $checkPlan(actorOrgId);
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
if (!template) {
throw new NotFoundError({ message: "Template not found" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
template.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: template.orgId
});
const decryptedTemplateFields = decryptor({ cipherTextBlob: template.templateFields }).toString();
return {
...template,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
templateFields: JSON.parse(decryptedTemplateFields)
};
};
const listTemplates = async ({
limit,
offset,
search,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TListIdentityAuthTemplatesDTO) => {
await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const { docs, totalCount } = await identityAuthTemplateDAL.findByOrgId(actorOrgId, { limit, offset, search });
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: actorOrgId
});
return {
totalCount,
templates: docs.map((doc) => ({
...doc,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
templateFields: JSON.parse(decryptor({ cipherTextBlob: doc.templateFields }).toString())
}))
};
};
const getTemplatesByAuthMethod = async ({
authMethod,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TGetTemplatesByAuthMethodDTO) => {
await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const docs = await identityAuthTemplateDAL.findByAuthMethod(authMethod, actorOrgId);
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: actorOrgId
});
return docs.map((doc) => ({
...doc,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
templateFields: JSON.parse(decryptor({ cipherTextBlob: doc.templateFields }).toString())
}));
};
const findTemplateUsages = async ({
templateId,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TFindTemplateUsagesDTO) => {
await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
if (!template) {
throw new NotFoundError({ message: "Template not found" });
}
const docs = await identityAuthTemplateDAL.findTemplateUsages(templateId, template.authMethod);
return docs;
};
const unlinkTemplateUsage = async ({
templateId,
identityIds,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUnlinkTemplateUsageDTO) => {
await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
if (!template) {
throw new NotFoundError({ message: "Template not found" });
}
switch (template.authMethod) {
case IdentityAuthTemplateMethod.LDAP:
await identityLdapAuthDAL.update({ $in: { identityId: identityIds }, templateId }, { templateId: null });
break;
default:
break;
}
};
return {
createTemplate,
updateTemplate,
deleteTemplate,
getTemplate,
listTemplates,
getTemplatesByAuthMethod,
findTemplateUsages,
unlinkTemplateUsage
};
};

View File

@@ -0,0 +1,61 @@
import { TProjectPermission } from "@app/lib/types";
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
// Method-specific template field types
export type TLdapTemplateFields = {
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
ldapCaCertificate?: string;
};
// Union type for all template field types
export type TTemplateFieldsByMethod = {
[IdentityAuthTemplateMethod.LDAP]: TLdapTemplateFields;
};
// Generic base types that use conditional types for type safety
export type TCreateIdentityAuthTemplateDTO = {
name: string;
authMethod: IdentityAuthTemplateMethod;
templateFields: TTemplateFieldsByMethod[IdentityAuthTemplateMethod];
} & Omit<TProjectPermission, "projectId">;
export type TUpdateIdentityAuthTemplateDTO = {
templateId: string;
name?: string;
templateFields?: Partial<TTemplateFieldsByMethod[IdentityAuthTemplateMethod]>;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteIdentityAuthTemplateDTO = {
templateId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetIdentityAuthTemplateDTO = {
templateId: string;
} & Omit<TProjectPermission, "projectId">;
export type TListIdentityAuthTemplatesDTO = {
limit?: number;
offset?: number;
search?: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetTemplatesByAuthMethodDTO = {
authMethod: string;
} & Omit<TProjectPermission, "projectId">;
export type TFindTemplateUsagesDTO = {
templateId: string;
} & Omit<TProjectPermission, "projectId">;
export type TUnlinkTemplateUsageDTO = {
templateId: string;
identityIds: string[];
} & Omit<TProjectPermission, "projectId">;
// Specific LDAP types for convenience
export type TCreateLdapTemplateDTO = TCreateIdentityAuthTemplateDTO;
export type TUpdateLdapTemplateDTO = TUpdateIdentityAuthTemplateDTO;

View File

@@ -0,0 +1,6 @@
export type { TIdentityAuthTemplateDALFactory } from "./identity-auth-template-dal";
export { identityAuthTemplateDALFactory } from "./identity-auth-template-dal";
export * from "./identity-auth-template-enums";
export type { TIdentityAuthTemplateServiceFactory } from "./identity-auth-template-service";
export { identityAuthTemplateServiceFactory } from "./identity-auth-template-service";
export type * from "./identity-auth-template-types";

View File

@@ -31,7 +31,8 @@ export const getDefaultOnPremFeatures = () => {
caCrl: false,
sshHostGroups: false,
enterpriseSecretSyncs: false,
enterpriseAppConnections: false
enterpriseAppConnections: false,
machineIdentityAuthTemplates: false
};
};

View File

@@ -60,7 +60,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
enterpriseSecretSyncs: false,
enterpriseAppConnections: false,
fips: false,
eventSubscriptions: false
eventSubscriptions: false,
machineIdentityAuthTemplates: false
});
export const setupLicenseRequestWithStore = (

View File

@@ -75,6 +75,7 @@ export type TFeatureSet = {
secretScanning: false;
enterpriseSecretSyncs: false;
enterpriseAppConnections: false;
machineIdentityAuthTemplates: false;
fips: false;
eventSubscriptions: false;
};

View File

@@ -28,6 +28,15 @@ export enum OrgPermissionKmipActions {
Setup = "setup"
}
export enum OrgPermissionMachineIdentityAuthTemplateActions {
ListTemplates = "list-templates",
EditTemplates = "edit-templates",
CreateTemplates = "create-templates",
DeleteTemplates = "delete-templates",
UnlinkTemplates = "unlink-templates",
AttachTemplates = "attach-templates"
}
export enum OrgPermissionAdminConsoleAction {
AccessAllProjects = "access-all-projects"
}
@@ -88,6 +97,7 @@ export enum OrgPermissionSubjects {
Identity = "identity",
Kms = "kms",
AdminConsole = "organization-admin-console",
MachineIdentityAuthTemplate = "machine-identity-auth-template",
AuditLogs = "audit-logs",
ProjectTemplates = "project-templates",
AppConnections = "app-connections",
@@ -126,6 +136,7 @@ export type OrgPermissionSet =
)
]
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
| [OrgPermissionMachineIdentityAuthTemplateActions, OrgPermissionSubjects.MachineIdentityAuthTemplate]
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
@@ -237,6 +248,14 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
"Describe what action an entity can take."
)
}),
z.object({
subject: z
.literal(OrgPermissionSubjects.MachineIdentityAuthTemplate)
.describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionMachineIdentityAuthTemplateActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(OrgPermissionSubjects.Gateway).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGatewayActions).describe(
@@ -350,6 +369,25 @@ const buildAdminPermission = () => {
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
can(OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
can(OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
can(
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
can(
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
can(
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
can(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
return rules;
@@ -385,6 +423,16 @@ const buildMemberPermission = () => {
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
can(
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
can(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
return rules;
};

View File

@@ -18,6 +18,7 @@ import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "@app/services/
export enum ApiDocsTags {
Identities = "Identities",
IdentityTemplates = "Identity Templates",
TokenAuth = "Token Auth",
UniversalAuth = "Universal Auth",
GcpAuth = "GCP Auth",
@@ -214,6 +215,7 @@ export const LDAP_AUTH = {
password: "The password of the LDAP user to login."
},
ATTACH: {
templateId: "The ID of the identity auth template to attach the configuration onto.",
identityId: "The ID of the identity to attach the configuration onto.",
url: "The URL of the LDAP server.",
allowedFields:
@@ -240,7 +242,8 @@ export const LDAP_AUTH = {
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."
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
templateId: "The ID of the identity auth template to update the configuration to."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the configuration for."

View File

@@ -179,6 +179,8 @@ import { identityAccessTokenDALFactory } from "@app/services/identity-access-tok
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal";
import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service";
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
@@ -394,6 +396,7 @@ export const registerRoutes = async (
const identityProjectDAL = identityProjectDALFactory(db);
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
const identityAuthTemplateDAL = identityAuthTemplateDALFactory(db);
const identityTokenAuthDAL = identityTokenAuthDALFactory(db);
const identityUaDAL = identityUaDALFactory(db);
@@ -1461,6 +1464,15 @@ export const registerRoutes = async (
identityMetadataDAL
});
const identityAuthTemplateService = identityAuthTemplateServiceFactory({
identityAuthTemplateDAL,
identityLdapAuthDAL,
permissionService,
kmsService,
licenseService,
auditLogService
});
const identityAccessTokenService = identityAccessTokenServiceFactory({
identityAccessTokenDAL,
identityOrgMembershipDAL,
@@ -1604,7 +1616,8 @@ export const registerRoutes = async (
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService,
identityDAL
identityDAL,
identityAuthTemplateDAL
});
const dynamicSecretProviders = buildDynamicSecretProviders({
@@ -2008,6 +2021,7 @@ export const registerRoutes = async (
webhook: webhookService,
serviceToken: serviceTokenService,
identity: identityService,
identityAuthTemplate: identityAuthTemplateService,
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityTokenAuth: identityTokenAuthService,

View File

@@ -200,49 +200,104 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
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."
),
body: z.union([
// Template-based configuration
z
.object({
templateId: z.string().trim().describe(LDAP_AUTH.ATTACH.templateId),
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."
),
// Manual configuration
z
.object({
url: z.string().trim().describe(LDAP_AUTH.ATTACH.url),
bindDN: z.string().trim().describe(LDAP_AUTH.ATTACH.bindDN),
bindPass: z.string().trim().describe(LDAP_AUTH.ATTACH.bindPass),
searchBase: z.string().trim().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({
@@ -275,7 +330,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
allowedFields: req.body.allowedFields
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
}
}
});
@@ -309,6 +365,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
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),
templateId: z.string().trim().optional().describe(LDAP_AUTH.UPDATE.templateId),
searchFilter: z
.string()
.trim()
@@ -376,7 +433,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
accessTokenTTL: identityLdapAuth.accessTokenTTL,
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
allowedFields: req.body.allowedFields
allowedFields: req.body.allowedFields,
templateId: identityLdapAuth.templateId
}
}
});
@@ -413,7 +471,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
}).extend({
bindDN: z.string(),
bindPass: z.string(),
ldapCaCertificate: z.string().optional()
ldapCaCertificate: z.string().optional(),
templateId: z.string().optional().nullable()
})
})
}

View File

@@ -22,6 +22,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
fromDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
@@ -45,6 +46,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
fromDate: req.body.fromDate,
recipients: req.body.recipients
}
});

View File

@@ -35,7 +35,7 @@ export const getGitHubInstanceApiUrl = async (config: {
}) => {
const host = config.credentials.host || "github.com";
await blockLocalAndPrivateIpAddresses(host);
await blockLocalAndPrivateIpAddresses(`https://${host}`);
let apiBase: string;
if (config.credentials.instanceType === "server") {
@@ -348,6 +348,8 @@ export const validateGitHubConnectionCredentials = async (
});
}
} catch (e: unknown) {
logger.error(e, "Unable to verify GitHub connection");
if (e instanceof BadRequestError) {
throw e;
}

View File

@@ -193,6 +193,10 @@ export const authPaswordServiceFactory = ({
}
if (!user.authMethods?.includes(AuthMethod.EMAIL)) {
logger.error(
{ authMethods: user.authMethods },
"Unable to reset password, no email authentication method is configured"
);
throw new BadRequestError({ message: "Unable to reset password, no email authentication method is configured" });
}

View File

@@ -2,9 +2,14 @@
import { ForbiddenError } from "@casl/ability";
import { IdentityAuthMethod } from "@app/db/schemas";
import { TIdentityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template";
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 {
OrgPermissionIdentityActions,
OrgPermissionMachineIdentityAuthTemplateActions,
OrgPermissionSubjects
} from "@app/ee/services/permission/org-permission";
import {
constructPermissionErrorMessage,
validatePrivilegeChangeOperation
@@ -44,6 +49,7 @@ type TIdentityLdapAuthServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
kmsService: TKmsServiceFactory;
identityDAL: TIdentityDALFactory;
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
};
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
@@ -55,7 +61,8 @@ export const identityLdapAuthServiceFactory = ({
identityOrgMembershipDAL,
licenseService,
permissionService,
kmsService
kmsService,
identityAuthTemplateDAL
}: TIdentityLdapAuthServiceFactoryDep) => {
const getLdapConfig = async (identityId: string) => {
const identity = await identityDAL.findOne({ id: identityId });
@@ -173,6 +180,7 @@ export const identityLdapAuthServiceFactory = ({
const attachLdapAuth = async ({
identityId,
templateId,
url,
searchBase,
searchFilter,
@@ -213,6 +221,14 @@ export const identityLdapAuthServiceFactory = ({
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
if (templateId) {
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
}
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
if (!plan.ldap) {
@@ -241,33 +257,55 @@ export const identityLdapAuthServiceFactory = ({
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
const identityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
const { encryptor } = await kmsService.createCipherPairWithDataKey({
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.orgId
});
const template = templateId
? await identityAuthTemplateDAL.findByIdAndOrgId(templateId, identityMembershipOrg.orgId)
: undefined;
let ldapConfig: { bindDN: string; bindPass: string; searchBase: string; url: string; ldapCaCertificate?: string };
if (template) {
ldapConfig = JSON.parse(decryptor({ cipherTextBlob: template.templateFields }).toString());
} else {
if (!bindDN || !bindPass || !searchBase || !url) {
throw new BadRequestError({
message: "Invalid request. Missing bind DN, bind pass, search base, or URL."
});
}
ldapConfig = {
bindDN,
bindPass,
searchBase,
url,
ldapCaCertificate
};
}
const { cipherTextBlob: encryptedBindPass } = encryptor({
plainText: Buffer.from(bindPass)
plainText: Buffer.from(ldapConfig.bindPass)
});
const { cipherTextBlob: encryptedBindDN } = encryptor({
plainText: Buffer.from(ldapConfig.bindDN)
});
let encryptedLdapCaCertificate: Buffer | undefined;
if (ldapCaCertificate) {
if (ldapConfig.ldapCaCertificate) {
const { cipherTextBlob: encryptedCertificate } = encryptor({
plainText: Buffer.from(ldapCaCertificate)
plainText: Buffer.from(ldapConfig.ldapCaCertificate)
});
encryptedLdapCaCertificate = encryptedCertificate;
}
const { cipherTextBlob: encryptedBindDN } = encryptor({
plainText: Buffer.from(bindDN)
});
const isConnected = await testLDAPConfig({
bindDN,
bindPass,
caCert: ldapCaCertificate || "",
url
bindDN: ldapConfig.bindDN,
bindPass: ldapConfig.bindPass,
caCert: ldapConfig.ldapCaCertificate || "",
url: ldapConfig.url
});
if (!isConnected) {
@@ -282,15 +320,16 @@ export const identityLdapAuthServiceFactory = ({
identityId: identityMembershipOrg.identityId,
encryptedBindDN,
encryptedBindPass,
searchBase,
searchBase: ldapConfig.searchBase,
searchFilter,
url,
url: ldapConfig.url,
encryptedLdapCaCertificate,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
templateId
},
tx
);
@@ -301,6 +340,7 @@ export const identityLdapAuthServiceFactory = ({
const updateLdapAuth = async ({
identityId,
templateId,
url,
searchBase,
searchFilter,
@@ -344,6 +384,13 @@ export const identityLdapAuthServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
if (templateId) {
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
}
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
if (!plan.ldap) {
@@ -371,33 +418,56 @@ export const identityLdapAuthServiceFactory = ({
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
const { encryptor } = await kmsService.createCipherPairWithDataKey({
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
orgId: identityMembershipOrg.orgId
});
const template = templateId
? await identityAuthTemplateDAL.findByIdAndOrgId(templateId, identityMembershipOrg.orgId)
: undefined;
let config: {
bindDN?: string;
bindPass?: string;
searchBase?: string;
url?: string;
ldapCaCertificate?: string;
};
if (template) {
config = JSON.parse(decryptor({ cipherTextBlob: template.templateFields }).toString());
} else {
config = {
bindDN,
bindPass,
searchBase,
url,
ldapCaCertificate
};
}
let encryptedBindPass: Buffer | undefined;
if (bindPass) {
if (config.bindPass) {
const { cipherTextBlob: bindPassCiphertext } = encryptor({
plainText: Buffer.from(bindPass)
plainText: Buffer.from(config.bindPass)
});
encryptedBindPass = bindPassCiphertext;
}
let encryptedLdapCaCertificate: Buffer | undefined;
if (ldapCaCertificate) {
if (config.ldapCaCertificate) {
const { cipherTextBlob: ldapCaCertificateCiphertext } = encryptor({
plainText: Buffer.from(ldapCaCertificate)
plainText: Buffer.from(config.ldapCaCertificate)
});
encryptedLdapCaCertificate = ldapCaCertificateCiphertext;
}
let encryptedBindDN: Buffer | undefined;
if (bindDN) {
if (config.bindDN) {
const { cipherTextBlob: bindDNCiphertext } = encryptor({
plainText: Buffer.from(bindDN)
plainText: Buffer.from(config.bindDN)
});
encryptedBindDN = bindDNCiphertext;
@@ -406,10 +476,10 @@ export const identityLdapAuthServiceFactory = ({
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
bindDN: config.bindDN || ldapConfig.bindDN,
bindPass: config.bindPass || ldapConfig.bindPass,
caCert: config.ldapCaCertificate || ldapConfig.caCert,
url: config.url || ldapConfig.url
});
if (!isConnected) {
@@ -420,14 +490,15 @@ export const identityLdapAuthServiceFactory = ({
}
const updatedLdapAuth = await identityLdapAuthDAL.updateById(identityLdapAuth.id, {
url,
searchBase,
url: config.url,
searchBase: config.searchBase,
searchFilter,
encryptedBindDN,
encryptedBindPass,
encryptedLdapCaCertificate,
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
accessTokenMaxTTL,
templateId: template?.id || null,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps

View File

@@ -14,11 +14,12 @@ export type TAllowedFields = z.infer<typeof AllowedFieldsSchema>;
export type TAttachLdapAuthDTO = {
identityId: string;
url: string;
searchBase: string;
templateId?: string;
url?: string;
searchBase?: string;
searchFilter: string;
bindDN: string;
bindPass: string;
bindDN?: string;
bindPass?: string;
ldapCaCertificate?: string;
allowedFields?: TAllowedFields[];
accessTokenTTL: number;
@@ -30,6 +31,7 @@ export type TAttachLdapAuthDTO = {
export type TUpdateLdapAuthDTO = {
identityId: string;
templateId?: string;
url?: string;
searchBase?: string;
searchFilter?: string;

View File

@@ -79,25 +79,33 @@ export const reminderServiceFactory = ({
repeatDays,
nextReminderDate: nextReminderDateInput,
recipients,
projectId
projectId,
fromDate: fromDateInput
}: {
secretId?: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
fromDate?: string | null;
projectId: string;
}) => {
if (!secretId) {
throw new BadRequestError({ message: "secretId is required" });
}
let nextReminderDate;
let fromDate;
if (nextReminderDateInput) {
nextReminderDate = new Date(nextReminderDateInput);
}
if (repeatDays && repeatDays > 0) {
nextReminderDate = $addDays(repeatDays);
if (repeatDays) {
if (fromDateInput) {
fromDate = new Date(fromDateInput);
nextReminderDate = fromDate;
} else {
nextReminderDate = $addDays(repeatDays);
}
}
if (!nextReminderDate) {
@@ -112,7 +120,8 @@ export const reminderServiceFactory = ({
await reminderDAL.updateById(existingReminder.id, {
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
});
reminderId = existingReminder.id;
} else {
@@ -121,7 +130,8 @@ export const reminderServiceFactory = ({
secretId,
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
});
reminderId = newReminder.id;
}
@@ -280,14 +290,28 @@ export const reminderServiceFactory = ({
}
const processedReminders = remindersData.map(
({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients, projectId }) => {
({
secretId,
message,
repeatDays,
nextReminderDate: nextReminderDateInput,
recipients,
projectId,
fromDate: fromDateInput
}) => {
let nextReminderDate;
let fromDate;
if (nextReminderDateInput) {
nextReminderDate = new Date(nextReminderDateInput);
}
if (repeatDays && repeatDays > 0 && !nextReminderDate) {
nextReminderDate = $addDays(repeatDays);
if (repeatDays && !nextReminderDate) {
if (fromDateInput) {
fromDate = new Date(fromDateInput);
nextReminderDate = fromDate;
} else {
nextReminderDate = $addDays(repeatDays);
}
}
if (!nextReminderDate) {
@@ -302,17 +326,19 @@ export const reminderServiceFactory = ({
repeatDays,
nextReminderDate,
recipients: recipients ? [...new Set(recipients)] : [],
projectId
projectId,
fromDate
};
}
);
const newReminders = await reminderDAL.insertMany(
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate }) => ({
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate, fromDate }) => ({
secretId,
message,
repeatDays,
nextReminderDate
nextReminderDate,
fromDate
})),
tx
);

View File

@@ -8,6 +8,7 @@ export type TReminder = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate: Date;
fromDate?: Date | null;
createdAt: Date;
updatedAt: Date;
};
@@ -21,6 +22,7 @@ export type TCreateReminderDTO = {
secretId?: string;
message?: string | null;
repeatDays?: number | null;
fromDate?: string | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
@@ -31,6 +33,7 @@ export type TBatchCreateReminderDTO = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | Date | null;
fromDate?: Date | null;
recipients?: string[] | null;
projectId?: string;
}[];
@@ -95,6 +98,7 @@ export interface TReminderServiceFactory {
nextReminderDate?: string | null;
recipients?: string[] | null;
projectId: string;
fromDate?: string | null;
}) => Promise<{
id: string;
created: boolean;

View File

@@ -14,93 +14,49 @@
"navigation": {
"tabs": [
{
"tab": "Documentation",
"tab": "Platform",
"groups": [
{
"group": "Getting Started",
"pages": [
"documentation/getting-started/overview",
"documentation/getting-started/introduction",
{
"group": "Quickstart",
"pages": ["documentation/guides/local-development"]
},
{
"group": "Guides",
"group": "Concepts",
"pages": [
"documentation/guides/introduction",
"documentation/guides/node",
"documentation/guides/python",
"documentation/guides/nextjs-vercel",
"documentation/guides/microsoft-power-apps",
"documentation/guides/organization-structure"
"documentation/getting-started/concepts/deployment-models",
"documentation/getting-started/concepts/platform-hierarchy",
"documentation/getting-started/concepts/platform-iam",
"documentation/getting-started/concepts/client-integrations",
"documentation/getting-started/concepts/audit-logs"
]
},
{
"group": "Setup",
"pages": ["documentation/setup/networking"]
"group": "Guides",
"pages": ["documentation/guides/organization-structure"]
}
]
},
{
"group": "Platform",
"group": "Platform Reference",
"pages": [
"documentation/platform/organization",
"documentation/platform/project",
"documentation/platform/folder",
{
"group": "Secrets",
"group": "Projects",
"pages": [
"documentation/platform/secret-versioning",
"documentation/platform/pit-recovery",
"documentation/platform/secret-reference",
"documentation/platform/webhooks"
]
},
{
"group": "Internal PKI",
"pages": [
"documentation/platform/pki/overview",
"documentation/platform/pki/private-ca",
"documentation/platform/pki/external-ca",
"documentation/platform/pki/subscribers",
"documentation/platform/pki/certificates",
"documentation/platform/pki/acme-ca",
"documentation/platform/pki/est",
"documentation/platform/pki/alerting",
"documentation/platform/project",
"documentation/platform/project-templates",
{
"group": "Integrations",
"group": "KMS Configuration",
"pages": [
"documentation/platform/pki/pki-issuer",
"documentation/platform/pki/integration-guides/gloo-mesh"
"documentation/platform/kms-configuration/overview",
"documentation/platform/kms-configuration/aws-kms",
"documentation/platform/kms-configuration/aws-hsm",
"documentation/platform/kms-configuration/gcp-kms"
]
}
]
},
{
"group": "Infisical SSH",
"pages": [
"documentation/platform/ssh/overview",
"documentation/platform/ssh/host-groups"
]
},
{
"group": "Key Management (KMS)",
"pages": [
"documentation/platform/kms/overview",
"documentation/platform/kms/hsm-integration",
"documentation/platform/kms/kubernetes-encryption",
"documentation/platform/kms/kmip"
]
},
{
"group": "KMS Configuration",
"pages": [
"documentation/platform/kms-configuration/overview",
"documentation/platform/kms-configuration/aws-kms",
"documentation/platform/kms-configuration/aws-hsm",
"documentation/platform/kms-configuration/gcp-kms"
]
},
{
"group": "Identities",
"pages": [
@@ -140,57 +96,53 @@
]
},
{
"group": "Secret Rotation",
"group": "App Connections",
"pages": [
"documentation/platform/secret-rotation/overview",
"documentation/platform/secret-rotation/auth0-client-secret",
"documentation/platform/secret-rotation/aws-iam-user-secret",
"documentation/platform/secret-rotation/azure-client-secret",
"documentation/platform/secret-rotation/ldap-password",
"documentation/platform/secret-rotation/mssql-credentials",
"documentation/platform/secret-rotation/mysql-credentials",
"documentation/platform/secret-rotation/okta-client-secret",
"documentation/platform/secret-rotation/oracledb-credentials",
"documentation/platform/secret-rotation/postgres-credentials"
"integrations/app-connections/overview",
{
"group": "Connections",
"pages": [
"integrations/app-connections/1password",
"integrations/app-connections/auth0",
"integrations/app-connections/aws",
"integrations/app-connections/azure-app-configuration",
"integrations/app-connections/azure-client-secrets",
"integrations/app-connections/azure-devops",
"integrations/app-connections/azure-key-vault",
"integrations/app-connections/bitbucket",
"integrations/app-connections/camunda",
"integrations/app-connections/checkly",
"integrations/app-connections/cloudflare",
"integrations/app-connections/databricks",
"integrations/app-connections/digital-ocean",
"integrations/app-connections/flyio",
"integrations/app-connections/gcp",
"integrations/app-connections/github",
"integrations/app-connections/github-radar",
"integrations/app-connections/gitlab",
"integrations/app-connections/hashicorp-vault",
"integrations/app-connections/heroku",
"integrations/app-connections/humanitec",
"integrations/app-connections/ldap",
"integrations/app-connections/mssql",
"integrations/app-connections/mysql",
"integrations/app-connections/netlify",
"integrations/app-connections/oci",
"integrations/app-connections/okta",
"integrations/app-connections/oracledb",
"integrations/app-connections/postgres",
"integrations/app-connections/railway",
"integrations/app-connections/render",
"integrations/app-connections/supabase",
"integrations/app-connections/teamcity",
"integrations/app-connections/terraform-cloud",
"integrations/app-connections/vercel",
"integrations/app-connections/windmill",
"integrations/app-connections/zabbix"
]
}
]
},
{
"group": "Dynamic Secrets",
"pages": [
"documentation/platform/dynamic-secrets/overview",
"documentation/platform/dynamic-secrets/aws-elasticache",
"documentation/platform/dynamic-secrets/aws-iam",
"documentation/platform/dynamic-secrets/azure-entra-id",
"documentation/platform/dynamic-secrets/cassandra",
"documentation/platform/dynamic-secrets/elastic-search",
"documentation/platform/dynamic-secrets/gcp-iam",
"documentation/platform/dynamic-secrets/github",
"documentation/platform/dynamic-secrets/ldap",
"documentation/platform/dynamic-secrets/mongo-atlas",
"documentation/platform/dynamic-secrets/mongo-db",
"documentation/platform/dynamic-secrets/mssql",
"documentation/platform/dynamic-secrets/mysql",
"documentation/platform/dynamic-secrets/oracle",
"documentation/platform/dynamic-secrets/postgresql",
"documentation/platform/dynamic-secrets/rabbit-mq",
"documentation/platform/dynamic-secrets/redis",
"documentation/platform/dynamic-secrets/sap-ase",
"documentation/platform/dynamic-secrets/sap-hana",
"documentation/platform/dynamic-secrets/snowflake",
"documentation/platform/dynamic-secrets/totp",
"documentation/platform/dynamic-secrets/kubernetes",
"documentation/platform/dynamic-secrets/vertica"
]
},
{
"group": "Gateway",
"pages": [
"documentation/platform/gateways/overview",
"documentation/platform/gateways/gateway-security",
"documentation/platform/gateways/networking"
]
},
"documentation/platform/project-templates",
{
"group": "Workflow Integrations",
"pages": [
@@ -206,22 +158,20 @@
"documentation/platform/external-migrations/vault"
]
},
"documentation/platform/admin-panel/server-admin",
"documentation/platform/secret-sharing"
]
},
{
"group": "Connectivity",
"pages": [
"documentation/setup/networking",
{
"group": "Admin Consoles",
"group": "Gateway",
"pages": [
"documentation/platform/admin-panel/overview",
"documentation/platform/admin-panel/server-admin",
"documentation/platform/admin-panel/org-admin-console"
]
},
"documentation/platform/secret-sharing",
{
"group": "Secret Scanning",
"pages": [
"documentation/platform/secret-scanning/overview",
"documentation/platform/secret-scanning/bitbucket",
"documentation/platform/secret-scanning/github",
"documentation/platform/secret-scanning/gitlab"
"documentation/platform/gateways/overview",
"documentation/platform/gateways/gateway-security",
"documentation/platform/gateways/networking"
]
}
]
@@ -322,6 +272,7 @@
}
]
},
"documentation/platform/identities/auth-templates",
"documentation/platform/token",
"documentation/platform/mfa",
"documentation/platform/github-org-sync"
@@ -426,18 +377,80 @@
]
},
{
"tab": "Integrations",
"groups": [
"tab": "Products",
"menu": [
{
"group": "Infrastructure Integrations",
"pages": [
"integrations/platforms/ansible",
"integrations/platforms/apache-airflow",
"item": "Secrets Management",
"groups": [
{
"group": "Container orchestrators",
"group": "Secrets Management",
"pages": [
"documentation/platform/secrets-mgmt/overview",
"documentation/platform/secrets-mgmt/project",
"documentation/platform/folder",
{
"group": "Kubernetes",
"group": "Secret Rotation",
"pages": [
"documentation/platform/secret-rotation/overview",
"documentation/platform/secret-rotation/auth0-client-secret",
"documentation/platform/secret-rotation/aws-iam-user-secret",
"documentation/platform/secret-rotation/azure-client-secret",
"documentation/platform/secret-rotation/ldap-password",
"documentation/platform/secret-rotation/mssql-credentials",
"documentation/platform/secret-rotation/mysql-credentials",
"documentation/platform/secret-rotation/okta-client-secret",
"documentation/platform/secret-rotation/oracledb-credentials",
"documentation/platform/secret-rotation/postgres-credentials"
]
},
{
"group": "Dynamic Secrets",
"pages": [
"documentation/platform/dynamic-secrets/overview",
"documentation/platform/dynamic-secrets/aws-elasticache",
"documentation/platform/dynamic-secrets/aws-iam",
"documentation/platform/dynamic-secrets/azure-entra-id",
"documentation/platform/dynamic-secrets/cassandra",
"documentation/platform/dynamic-secrets/elastic-search",
"documentation/platform/dynamic-secrets/gcp-iam",
"documentation/platform/dynamic-secrets/github",
"documentation/platform/dynamic-secrets/ldap",
"documentation/platform/dynamic-secrets/mongo-atlas",
"documentation/platform/dynamic-secrets/mongo-db",
"documentation/platform/dynamic-secrets/mssql",
"documentation/platform/dynamic-secrets/mysql",
"documentation/platform/dynamic-secrets/oracle",
"documentation/platform/dynamic-secrets/postgresql",
"documentation/platform/dynamic-secrets/rabbit-mq",
"documentation/platform/dynamic-secrets/redis",
"documentation/platform/dynamic-secrets/sap-ase",
"documentation/platform/dynamic-secrets/sap-hana",
"documentation/platform/dynamic-secrets/snowflake",
"documentation/platform/dynamic-secrets/totp",
"documentation/platform/dynamic-secrets/kubernetes",
"documentation/platform/dynamic-secrets/vertica"
]
},
{
"group": "Guides",
"pages": [
"documentation/guides/introduction",
"documentation/guides/local-development",
"documentation/guides/node",
"documentation/guides/python",
"documentation/guides/nextjs-vercel",
"documentation/guides/microsoft-power-apps"
]
}
]
},
{
"group": "Infrastructure Integrations",
"pages": [
"integrations/platforms/ansible",
"integrations/platforms/apache-airflow",
{
"group": "Kubernetes Operator",
"pages": [
"integrations/platforms/kubernetes/overview",
"integrations/platforms/kubernetes/infisical-secret-crd",
@@ -447,222 +460,249 @@
},
"integrations/platforms/kubernetes-injector",
"integrations/platforms/kubernetes-csi",
"integrations/platforms/docker-swarm-with-agent",
"integrations/platforms/ecs-with-agent"
{
"group": "Agent",
"pages": [
"integrations/platforms/infisical-agent",
"integrations/platforms/docker-swarm-with-agent",
"integrations/platforms/ecs-with-agent"
]
},
{
"group": "Docker",
"pages": [
"integrations/platforms/docker-intro",
"integrations/platforms/docker",
"integrations/platforms/docker-pass-envs",
"integrations/platforms/docker-compose"
]
},
"integrations/frameworks/packer",
"integrations/frameworks/pulumi",
"integrations/frameworks/terraform"
]
},
{
"group": "Docker",
"group": "Secret Syncs",
"pages": [
"integrations/platforms/docker-intro",
"integrations/platforms/docker",
"integrations/platforms/docker-pass-envs",
"integrations/platforms/docker-compose"
"integrations/secret-syncs/overview",
{
"group": "Syncs",
"pages": [
"integrations/secret-syncs/1password",
"integrations/secret-syncs/aws-parameter-store",
"integrations/secret-syncs/aws-secrets-manager",
"integrations/secret-syncs/azure-app-configuration",
"integrations/secret-syncs/azure-devops",
"integrations/secret-syncs/azure-key-vault",
"integrations/secret-syncs/bitbucket",
"integrations/secret-syncs/camunda",
"integrations/secret-syncs/checkly",
"integrations/secret-syncs/cloudflare-pages",
"integrations/secret-syncs/cloudflare-workers",
"integrations/secret-syncs/databricks",
"integrations/secret-syncs/digital-ocean-app-platform",
"integrations/secret-syncs/flyio",
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/github",
"integrations/secret-syncs/gitlab",
"integrations/secret-syncs/hashicorp-vault",
"integrations/secret-syncs/heroku",
"integrations/secret-syncs/humanitec",
"integrations/secret-syncs/netlify",
"integrations/secret-syncs/oci-vault",
"integrations/secret-syncs/railway",
"integrations/secret-syncs/render",
"integrations/secret-syncs/supabase",
"integrations/secret-syncs/teamcity",
"integrations/secret-syncs/terraform-cloud",
"integrations/secret-syncs/vercel",
"integrations/secret-syncs/windmill",
"integrations/secret-syncs/zabbix"
]
}
]
},
"integrations/platforms/infisical-agent",
"integrations/frameworks/packer",
"integrations/frameworks/pulumi",
"integrations/frameworks/terraform"
]
},
{
"group": "App Connections",
"pages": [
"integrations/app-connections/overview",
{
"group": "Connections",
"group": "Native Integrations",
"pages": [
"integrations/app-connections/1password",
"integrations/app-connections/auth0",
"integrations/app-connections/aws",
"integrations/app-connections/azure-app-configuration",
"integrations/app-connections/azure-client-secrets",
"integrations/app-connections/azure-devops",
"integrations/app-connections/azure-key-vault",
"integrations/app-connections/bitbucket",
"integrations/app-connections/camunda",
"integrations/app-connections/checkly",
"integrations/app-connections/cloudflare",
"integrations/app-connections/databricks",
"integrations/app-connections/digital-ocean",
"integrations/app-connections/flyio",
"integrations/app-connections/gcp",
"integrations/app-connections/github",
"integrations/app-connections/github-radar",
"integrations/app-connections/gitlab",
"integrations/app-connections/hashicorp-vault",
"integrations/app-connections/heroku",
"integrations/app-connections/humanitec",
"integrations/app-connections/ldap",
"integrations/app-connections/mssql",
"integrations/app-connections/mysql",
"integrations/app-connections/netlify",
"integrations/app-connections/oci",
"integrations/app-connections/okta",
"integrations/app-connections/oracledb",
"integrations/app-connections/postgres",
"integrations/app-connections/railway",
"integrations/app-connections/render",
"integrations/app-connections/supabase",
"integrations/app-connections/teamcity",
"integrations/app-connections/terraform-cloud",
"integrations/app-connections/vercel",
"integrations/app-connections/windmill",
"integrations/app-connections/zabbix"
]
}
]
},
{
"group": "Secret Syncs",
"pages": [
"integrations/secret-syncs/overview",
{
"group": "Syncs",
"pages": [
"integrations/secret-syncs/1password",
"integrations/secret-syncs/aws-parameter-store",
"integrations/secret-syncs/aws-secrets-manager",
"integrations/secret-syncs/azure-app-configuration",
"integrations/secret-syncs/azure-devops",
"integrations/secret-syncs/azure-key-vault",
"integrations/secret-syncs/bitbucket",
"integrations/secret-syncs/camunda",
"integrations/secret-syncs/checkly",
"integrations/secret-syncs/cloudflare-pages",
"integrations/secret-syncs/cloudflare-workers",
"integrations/secret-syncs/databricks",
"integrations/secret-syncs/digital-ocean-app-platform",
"integrations/secret-syncs/flyio",
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/github",
"integrations/secret-syncs/gitlab",
"integrations/secret-syncs/hashicorp-vault",
"integrations/secret-syncs/heroku",
"integrations/secret-syncs/humanitec",
"integrations/secret-syncs/netlify",
"integrations/secret-syncs/oci-vault",
"integrations/secret-syncs/railway",
"integrations/secret-syncs/render",
"integrations/secret-syncs/supabase",
"integrations/secret-syncs/teamcity",
"integrations/secret-syncs/terraform-cloud",
"integrations/secret-syncs/vercel",
"integrations/secret-syncs/windmill",
"integrations/secret-syncs/zabbix"
]
}
]
},
{
"group": "Native Integrations",
"pages": [
{
"group": "AWS",
"pages": [
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager",
"integrations/cloud/aws-amplify"
{
"group": "AWS",
"pages": [
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager",
"integrations/cloud/aws-amplify"
]
},
"integrations/cloud/vercel",
"integrations/cloud/azure-key-vault",
"integrations/cloud/azure-app-configuration",
"integrations/cloud/azure-devops",
"integrations/cloud/gcp-secret-manager",
{
"group": "Cloudflare",
"pages": [
"integrations/cloud/cloudflare-pages",
"integrations/cloud/cloudflare-workers"
]
},
"integrations/cloud/terraform-cloud",
"integrations/cloud/databricks",
{
"group": "View more",
"pages": [
"integrations/cloud/digital-ocean-app-platform",
"integrations/cloud/heroku",
"integrations/cloud/netlify",
"integrations/cloud/flyio",
"integrations/cloud/railway",
"integrations/cloud/render",
"integrations/cloud/laravel-forge",
"integrations/cloud/supabase",
"integrations/cloud/northflank",
"integrations/cloud/hasura-cloud",
"integrations/cloud/qovery",
"integrations/cloud/hashicorp-vault",
"integrations/cloud/cloud-66",
"integrations/cloud/windmill"
]
}
]
},
"integrations/cloud/vercel",
"integrations/cloud/azure-key-vault",
"integrations/cloud/azure-app-configuration",
"integrations/cloud/azure-devops",
"integrations/cloud/gcp-secret-manager",
{
"group": "Cloudflare",
"group": "CI/CD Integrations",
"pages": [
"integrations/cloud/cloudflare-pages",
"integrations/cloud/cloudflare-workers"
"integrations/cicd/jenkins",
"integrations/cicd/githubactions",
"integrations/cicd/gitlab",
"integrations/cicd/bitbucket",
"integrations/cloud/teamcity",
{
"group": "View more",
"pages": [
"integrations/cicd/circleci",
"integrations/cicd/travisci",
"integrations/cicd/rundeck",
"integrations/cicd/codefresh",
"integrations/cloud/checkly",
"integrations/cicd/octopus-deploy"
]
}
]
},
"integrations/cloud/terraform-cloud",
"integrations/cloud/databricks",
{
"group": "View more",
"group": "Framework Integrations",
"pages": [
"integrations/cloud/digital-ocean-app-platform",
"integrations/cloud/heroku",
"integrations/cloud/netlify",
"integrations/cloud/flyio",
"integrations/cloud/railway",
"integrations/cloud/render",
"integrations/cloud/laravel-forge",
"integrations/cloud/supabase",
"integrations/cloud/northflank",
"integrations/cloud/hasura-cloud",
"integrations/cloud/qovery",
"integrations/cloud/hashicorp-vault",
"integrations/cloud/cloud-66",
"integrations/cloud/windmill"
"integrations/frameworks/spring-boot-maven",
"integrations/frameworks/react",
"integrations/frameworks/vue",
"integrations/frameworks/express",
{
"group": "View more",
"pages": [
"integrations/frameworks/nextjs",
"integrations/frameworks/nestjs",
"integrations/frameworks/sveltekit",
"integrations/frameworks/nuxt",
"integrations/frameworks/gatsby",
"integrations/frameworks/remix",
"integrations/frameworks/vite",
"integrations/frameworks/fiber",
"integrations/frameworks/django",
"integrations/frameworks/flask",
"integrations/frameworks/laravel",
"integrations/frameworks/rails",
"integrations/frameworks/dotnet",
"integrations/platforms/pm2",
"integrations/frameworks/ab-initio"
]
}
]
},
{
"group": "Build Tool Integrations",
"pages": ["integrations/build-tools/gradle"]
},
{
"group": "Others",
"pages": ["integrations/external/backstage"]
}
]
},
{
"item": "Secrets Scanning",
"groups": [
{
"group": "Secret Scanning",
"pages": [
"documentation/platform/secret-scanning/overview"
]
},
{
"group": "Datasources",
"pages": [
"documentation/platform/secret-scanning/bitbucket",
"documentation/platform/secret-scanning/github",
"documentation/platform/secret-scanning/gitlab"
]
}
]
},
{
"group": "CI/CD Integrations",
"pages": [
"integrations/cicd/jenkins",
"integrations/cicd/githubactions",
"integrations/cicd/gitlab",
"integrations/cicd/bitbucket",
"integrations/cloud/teamcity",
"item": "Infisical PKI",
"groups": [
{
"group": "View more",
"group": "Infisical PKI",
"pages": [
"integrations/cicd/circleci",
"integrations/cicd/travisci",
"integrations/cicd/rundeck",
"integrations/cicd/codefresh",
"integrations/cloud/checkly",
"integrations/cicd/octopus-deploy"
"documentation/platform/pki/overview",
"documentation/platform/pki/private-ca",
"documentation/platform/pki/external-ca",
"documentation/platform/pki/subscribers",
"documentation/platform/pki/certificates",
"documentation/platform/pki/acme-ca",
"documentation/platform/pki/est",
"documentation/platform/pki/alerting",
{
"group": "Integrations",
"pages": [
"documentation/platform/pki/pki-issuer",
"documentation/platform/pki/integration-guides/gloo-mesh"
]
}
]
}
]
},
{
"group": "Framework Integrations",
"pages": [
"integrations/frameworks/spring-boot-maven",
"integrations/frameworks/react",
"integrations/frameworks/vue",
"integrations/frameworks/express",
"item": "Infisical SSH",
"groups": [
{
"group": "View more",
"group": "Infisical SSH",
"pages": [
"integrations/frameworks/nextjs",
"integrations/frameworks/nestjs",
"integrations/frameworks/sveltekit",
"integrations/frameworks/nuxt",
"integrations/frameworks/gatsby",
"integrations/frameworks/remix",
"integrations/frameworks/vite",
"integrations/frameworks/fiber",
"integrations/frameworks/django",
"integrations/frameworks/flask",
"integrations/frameworks/laravel",
"integrations/frameworks/rails",
"integrations/frameworks/dotnet",
"integrations/platforms/pm2",
"integrations/frameworks/ab-initio"
"documentation/platform/ssh/overview",
"documentation/platform/ssh/host-groups"
]
}
]
},
{
"group": "Build Tool Integrations",
"pages": ["integrations/build-tools/gradle"]
},
{
"group": "Others",
"pages": ["integrations/external/backstage"]
"item": "Infisical KMS",
"groups": [
{
"group": "Infisical KMS",
"pages": [
"documentation/platform/kms/overview",
"documentation/platform/kms/hsm-integration",
"documentation/platform/kms/kubernetes-encryption",
"documentation/platform/kms/kmip"
]
}
]
}
]
},
{
"tab": "CLI",
"tab": "CLI Reference",
"groups": [
{
"group": "Command line",
@@ -1560,7 +1600,7 @@
"api-reference/endpoints/app-connections/mysql/delete"
]
},
{
{
"group": "Netlify",
"pages": [
"api-reference/endpoints/app-connections/netlify/list",

View File

@@ -0,0 +1,40 @@
---
title: "Audit Logs"
sidebarTitle: "Audit Logs"
description: "Understand how Infisical logs activity and supports external audit streaming."
---
Infisical records a detailed audit trail of actions across the platform — providing deep visibility into access, changes, and usage for security and compliance purposes.
Every interaction with Infisical resources generates an audit event. These events are immutable and include metadata such as the actor, event type, affected resources, timestamp, IP address, and client source.
Audit logs enable teams to:
- Monitor access and changes to secrets, certificates, and infrastructure.
- Investigate incidents with full context around who did what, when, and how.
- Meet compliance and governance requirements with structured activity records.
To learn more, refer to the [audit logs documentation](/documentation/platform/audit-logs).
## Log Coverage
Infisical tracks dozens of event types across the platform — including secret access, permission changes, certificate issuance, SSH session activity, and identity management.
Each audit entry includes structured fields that make it easy to search, filter, and correlate across systems. For example:
- Event Type: Action that occurred (e.g., `create-secret`, `issue-ssh-cert`).
- Actor: Who performed the action (user or machine identity).
- Resource: What was affected (e.g., project, secret, certificate).
- Context: IP address, user agent, permissions, and more.
## External Log Streaming
For centralized monitoring and long-term retention, Infisical supports [audit log streaming](/documentation/platform/audit-log-streams/audit-log-streams) to external systems.
You can forward logs to SIEM platforms, storage buckets, or observability stacks using JSON-based collectors. Infisical integrates well with tools like [Fluent Bit](/documentation/platform/audit-log-streams/audit-log-streams-with-fluentbit#deploy-fluent-bit), enabling teams to route logs to destinations such as:
- AWS S3
- Elasticsearch
- Splunk
- Datadog
- Cloud-native log pipelines

View File

@@ -0,0 +1,31 @@
---
title: "Client Ecosystem"
sidebarTitle: "Client Ecosystem"
description: "Get an overview of the CLI, SDKs, agents, APIs, and integrations that interact with Infisical."
---
Infisical provides a flexible interface for integrating into development workflows and infrastructure. Around it is a rich ecosystem of clients and integrations that allow users and systems to interact with Infisical across any environment.
These clients enable access to secrets, certificates, and other resources from wherever theyre needed—whether thats a developers terminal, a CI/CD pipeline, or a running Kubernetes workload.
## Available Clients and Interfaces
Infisical offers a non-exhaustive set of clients and interfaces to support a wide range of use cases:
- [CLI](/cli/overview): A powerful command-line interface for developers and operators to interact with Infisical from local or automated environments. Commonly used for secret access, SSH credential issuance, and more.
- [SDKs](/sdks/overview): Official client libraries for languages like Go, Node.js, and Python make it easy to integrate Infisical directly into applications and internal tooling.
- [HTTP API](/api-reference/overview/introduction): A fully documented RESTful API powers all core functionality and enables advanced or custom integrations.
- [Agents](/integrations/platforms/infisical-agent): Lightweight background processes that can fetch and sync secrets or credentials into local environments, containers, or file systems.
- [Kubernetes Operator](/integrations/platforms/kubernetes/overview): A native controller that syncs Infisical secrets into Kubernetes as native Secrets, and supports secure workload integration.
- [External Secrets Operator (ESO)](https://external-secrets.io/latest/provider/infisical): Allows Infisical to act as a backend provider for syncing secrets into Kubernetes `Secret` objects using the widely adopted External Secrets Operator.
- [Kubernetes PKI Issuer](/documentation/platform/pki/pki-issuer): A controller that issues X.509 certificates from Infisical PKI using the cert-manager Issuer and Certificate CRDs.
- [Secret Syncs](/integrations/secret-syncs/overview): Native integrations to forward secrets to services like GitHub, GitLab, AWS Secrets Manager, Vercel, and more.
This modular ecosystem lets teams use Infisical alongside their existing stack—without requiring opinionated workflows or lock-in.

View File

@@ -0,0 +1,52 @@
---
title: "Using Infisical: Cloud or Self-Hosted"
sidebarTitle: "Cloud vs. Self-Host"
description: "Choose between Infisical Cloud or a self-managed deployment"
---
Infisical can be used in two ways: via [Infisical Cloud](https://app.infisical.com), a managed offering, or through a self-hosted deployment within your own infrastructure.
Both options provide the same core platform capabilities. The decision depends on your operational model, trust boundaries, and compliance requirements. While Infisical Cloud comes with built-in security and operational guarantees, a self-hosted deployment gives you full control—but also full responsibility for securing and maintaining the system.
## Infisical Cloud
Infisical Cloud is our managed service found at [app.infisical.com](https://app.infisical.com). It includes automated updates, availability guarantees, and secure infrastructure operations.
For most teams, Infisical Cloud is the recommended way to get started. It simplifies adoption by removing the need to manage deployment, scaling, or maintenance internally.
Use this if:
- You prefer not to operate infrastructure or handle upgrades
- You require a secure, production-grade hosted service
- You want to adopt Infisical with minimal operational overhead
<Info>
<p>
By default, Infisical Cloud is a secure, multi-tenant service. For
enterprises with stricter isolation or regulatory needs, dedicated cloud
instances are available.
</p>
<p>Contact sales@infisical.com to learn more.</p>
</Info>
## Self-Hosted Infisical
Infisical can also be deployed and managed within your own infrastructure. This approach provides full control over platform configuration, data storage, and operational security. In this model, your team is responsible for maintaining uptime, monitoring, patching, and integrations.
Use this if:
- You require complete control over data, deployment, and security posture
- Your compliance model mandates self-managed or on-premise systems
- You need to tightly integrate with internal tooling and infrastructure
Infisical supports multiple deployment methods, including [Docker](/self-hosting/deployment-options/standalone-infisical), [Docker Compose](/self-hosting/deployment-options/docker-compose), [Kubernetes](/self-hosting/deployment-options/kubernetes-helm), and [Linux package](/self-hosting/deployment-options/native/linux-package/installation).
To learn more, refer to the [self-hosting documentation](/self-hosting/overview).
<Info>
<p>
The open-source core is available under the MIT license. Additional
enterprise features and support are available with a commercial license.
</p>
<p>Contact sales@infisical.com to learn more.</p>
</Info>

View File

@@ -0,0 +1,41 @@
---
title: "Platform Hierarchy"
sidebarTitle: "Platform Hierarchy"
description: "Understand how organizations and projects are structured in Infisical."
---
Infisical is structured around organizations and projects, allowing teams to manage multiple products, access scopes, and use cases within a single account while keeping boundaries and responsibilities clearly defined.
## Organizations
An [organization](/documentation/platform/organization) typically represents a company or high-level entity (e.g. Acme Corp). It acts as the umbrella for all projects, members, and billing settings.
[Users](/documentation/platform/identities/user-identities) are invited to an organization and assigned [organization-level roles](/documentation/platform/access-controls/role-based-access-controls#organization-level-access-controls) that determine what they can manage—such as members, machine identities, and billing details.
![organization](/images/platform/organization/organization.png)
## Projects
A [project](/documentation/platform/project) belongs to an organization and defines a specific scope of work. Each project has a product type such as Secrets Management, SSH, or PKI that determines what features are available in that project.
For example:
- A Secrets Management project manages application secrets across environments.
- An SSH project enables certificate-based access to infrastructure.
- A PKI project manages certificate authorities and X.509 certificate workflows.
Users are added to a project and assigned [project-level roles](/documentation/platform/access-controls/role-based-access-controls#project-level-access-controls) that determine what they can manage—such as secrets, access policies, or certificate authorities. A user can have different roles across projects, allowing for flexible and fine-grained access control that reflects how teams operate in practice.
![organization projects](/images/platform/organization/organization-projects.png)
## Key Characteristics
- Projects are isolated in terms of configuration, permissions, and product workflows.
- Access is managed independently at both the organization and project level.
- All projects within an organization share the same billing and user directory.
Teams can adopt Infisical incrementally—starting with one product and expanding as needed.

View File

@@ -0,0 +1,29 @@
---
title: "Platform Identity and Access Management"
sidebarTitle: "Platform IAM"
description: "Understand how users, machine identities, roles, and permissions are managed."
---
Infisical uses identity-based access control to govern how users and systems interact with secrets, certificates, infrastructure, and other resources on the platform.
There are two types of identities:
- [User identities](/documentation/platform/identities/user-identities): Represent individuals such as developers or administrators that typically access the platform via browser.
- [Machine identities](/documentation/platform/identities/machine-identities): Represent systems such as CI pipelines or applications that programmatically interact with the platform.
Each identity is granted access based on its assigned roles and permissions and must authenticate with the platform in order to access any resources.
To learn more, refer to the [identities documentation](/documentation/platform/identities/overview).
## Roles and Access
Infisical provides a robust and flexible access control system. The primary authorization mechanism is [role-based access control (RBAC)](/documentation/platform/access-controls/role-based-access-controls), where identities are assigned roles at two access control levels:
- [Organization-level access control](/documentation/platform/access-controls/role-based-access-controls#organization-level-access-controls): Control billing, member management, and platform-wide settings
- [Project-level access control](/documentation/platform/access-controls/role-based-access-controls#project-level-access-controls): Control access to specific product resources like secrets, SSH hosts, or certificates
Beyond RBAC, Infisical also supports additional project-level permissioning features, [including attribute-based access control (ABAC)](/documentation/platform/access-controls/abac/overview), [temporary access grants](/documentation/platform/access-controls/temporary-access), and [additional privileges](/documentation/platform/access-controls/additional-privileges) for select project types.
To learn more, refer to the [access control documentation](/documentation/platform/access-controls/overview).

View File

@@ -1,107 +0,0 @@
---
mode: 'custom'
---
export function openSearch() {
document.getElementById('search-bar-entry').click();
}
<div
className="relative w-full flex items-center justify-center"
style={{ height: '24rem', backgroundColor: '#1F1F33', overflow: 'hidden' }}
>
<div style={{ flex: 'none' }}>
<img
src="/images/background.png"
style={{ height: '68rem', width: '68rem' }}
/>
</div>
<div style={{ position: 'absolute', textAlign: 'center' }}>
<div
style={{
color: 'white',
fontWeight: '400',
fontSize: '48px',
margin: '0',
}}
>
Infisical Documentation
</div>
<p
style={{
color: 'white',
fontWeight: '400',
fontSize: '20px',
opacity: '0.7',
}}
>
What can we help you build?
</p>
<button
type="button"
className="mx-auto w-full flex items-center text-sm leading-6 shadow-sm text-gray-400 bg-white ring-1 ring-gray-400/20 focus:outline-primary"
id="home-search-entry"
style={{
maxWidth: '24rem',
borderRadius: '4px',
marginTop: '3rem',
paddingLeft: '0.75rem',
paddingRight: '0.75rem',
paddingTop: '0.75rem',
paddingBottom: '0.75rem',
}}
onClick={openSearch}
>
<svg
className="h-4 w-4 ml-1.5 mr-3 flex-none bg-gray-500 hover:bg-gray-600 dark:bg-white/50 dark:hover:bg-white/70"
style={{
maskImage:
'url("https://mintlify.b-cdn.net/v6.5.1/solid/magnifying-glass.svg")',
maskRepeat: 'no-repeat',
maskPosition: 'center center',
}}
/>
Start a chat with us...
</button>
</div>
</div>
<div style={{marginTop: '6rem', marginBottom: '8rem', maxWidth: '70rem', marginLeft: 'auto',
marginRight: 'auto', paddingLeft: '1.25rem',
paddingRight: '1.25rem' }}>
<div
style={{
textAlign: 'center',
fontSize: '24px',
fontWeight: '600',
color: '#121142',
marginBottom: '3rem',
}}
>
Choose a topic below or simply{' '}
<span className="text-primary">get started</span>
</div>
<CardGroup cols={3}>
<Card title="Getting Started" icon="book-open" href="/guides">
Practical guides and best practices to get you up and running quickly.
</Card>
<Card title="API Reference" icon="code-simple" href="/reference">
Comprehensive details about the Infisical API.
</Card>
<Card title="Security" icon="code-simple" href="/reference">
Learn more about Infisical's architecture and underlying security.
</Card>
<Card title="Self-hosting" icon="link-simple" href="/integrations">
Read self-hosting instruction for Infisical.
</Card>
<Card title="Integrations" icon="link-simple" href="/integrations">
Infisical's growing number of third-party integrations.
</Card>
<Card title="Releases" icon="party-horn" href="/release-notes">
News about features and changes in Pinecone and related tools.
</Card>
</CardGroup>
</div>

View File

@@ -1,106 +1,40 @@
---
title: "What is Infisical?"
sidebarTitle: "What is Infisical?"
description: "An Introduction to the Infisical secret management platform."
description: "The open source platform for managing secrets, certificates, and secure infrastructure access."
---
**[Infisical](https://infisical.com)** is the open source secret management platform that developers use to centralize their application configuration and secrets like API keys and database credentials as well as manage their internal PKI. Additionally, developers use Infisical to prevent secrets leaks to git and securely share secrets amongst engineers.
## What is Infisical?
[Infisical](https://infisical.com) is the [open source](https://github.com/Infisical/infisical), all-in-one platform for secrets, certificates, and privileged access management.
It provides modern security workflows — including secrets rotation, dynamic credentials, access approvals, and SSH certificate-based access — all within one platform designed for developers, infrastructure, and security teams.
Start managing secrets securely with [Infisical Cloud](https://app.infisical.com) or learn how to [host Infisical](/self-hosting/overview) yourself.
<CardGroup cols={2}>
<Card
title="Infisical Cloud"
href="https://app.infisical.com/signup"
icon="cloud"
color="#000000"
>
Get started with Infisical Cloud in just a few minutes.
</Card>
<Card
href="/self-hosting/overview"
title="Self-hosting"
icon="server"
color="#000000"
>
Self-host Infisical on your own infrastructure.
</Card>
</CardGroup>
## Why use Infisical?
## Why Infisical?
Managing secrets, credentials, and infrastructure access is a critical concern for engineering teams. As infrastructure scales and environments become more complex, [secrets start to sprawl](https://infisical.com/blog/what-is-secret-sprawl) — across codebases, CI/CD pipelines, configuration files, and cloud services. This makes them difficult to track, rotate, and secure.
Infisical helps developers achieve secure centralized secret management and provides all the tools to easily manage secrets in various environments and infrastructure components. In particular, here are some of the most common points that developers mention after adopting Infisical:
Without proper management, secret sprawl turns into risk: hardcoded credentials, unrotated keys, fragmented access controls that attackers can exploit amongst other things.
- Streamlined **local development** processes (switching .env files to [Infisical CLI](/cli/commands/run) and removing secrets from developer machines).
- **Best-in-class developer experience** with an easy-to-use [Web Dashboard](/documentation/platform/project).
- Simple secret management inside **[CI/CD pipelines](/integrations/cicd/githubactions)** and staging environments.
- Secure and compliant secret management practices in **[production environments](/sdks/overview)**.
- **Facilitated workflows** around [secret change management](/documentation/platform/pr-workflows), [access requests](/documentation/platform/access-controls/access-requests), [temporary access provisioning](/documentation/platform/access-controls/temporary-access), and more.
- **Improved security posture** thanks to [secret scanning](/cli/scanning-overview), [granular access control policies](/documentation/platform/access-controls/overview), [automated secret rotation](https://infisical.com/docs/documentation/platform/secret-rotation/overview), and [dynamic secrets](/documentation/platform/dynamic-secrets/overview) capabilities.
Infisical addresses this challenge by providing an all-in-one platform and workflows to:
## How does Infisical work?
- Securely store and manage application secrets from development to production.
- Scan code and pipelines for exposed credentials.
- Automate X.509 certificate issuance and renewal.
- Manage SSH access using short-lived, policy-driven certificates.
- Encrypt and decrypt sensitive data with centralized key control.
- Audit every access, credential use, and change.
To make secret management effortless and secure, Infisical follows a certain structure for enabling secret management workflows as defined below.
Infisical is designed to integrate cleanly into your stack—improving security without adding complexity.
**Identities** in Infisical are users or machine which have a certain set of roles and permissions assigned to them. Such identities are able to manage secrets in various **Clients** throughout the entire infrastructure. To do that, identities have to verify themselves through one of the available **Authentication Methods**.
## What does Infisical include?
As a result, the 3 main concepts that are important to understand are:
Infisical consists of several tightly integrated products, each designed to solve a specific part of the infrastructure security surface:
- **[Identities](/documentation/platform/identities/overview)**: users or machines with a set permissions assigned to them.
- **[Clients](/integrations/platforms/kubernetes)**: Infisical-developed tools for managing secrets in various infrastructure components (e.g., [Kubernetes Operator](/integrations/platforms/kubernetes), [Infisical Agent](/integrations/platforms/infisical-agent), [CLI](/cli/usage), [SDKs](/sdks/overview), [API](/api-reference/overview/introduction), [Web Dashboard](/documentation/platform/organization)).
- **[Authentication Methods](/documentation/platform/identities/universal-auth)**: ways for Identities to authenticate inside different clients (e.g., SAML SSO for Web Dashboard, Universal Auth for Infisical Agent, AWS Auth etc.).
## How to get started with Infisical?
Depending on your use case, it might be helpful to look into some of the resources and guides provided below.
<CardGroup cols={2}>
<Card
href="../../cli/overview"
title="Command Line Interface (CLI)"
icon="square-terminal"
color="#000000"
>
Inject secrets into any application process/environment.
</Card>
<Card
title="SDKs"
href="/documentation/getting-started/sdks"
icon="boxes-stacked"
color="#000000"
>
Fetch secrets with any programming language on demand.
</Card>
<Card
href="../../integrations/platforms/docker-intro"
title="Docker"
icon="docker"
color="#000000"
>
Inject secrets into Docker containers.
</Card>
<Card
href="../../integrations/platforms/kubernetes"
title="Kubernetes"
icon="server"
color="#000000"
>
Fetch and save secrets as native Kubernetes secrets.
</Card>
<Card
href="/documentation/getting-started/api"
title="REST API"
icon="cloud"
color="#000000"
>
Fetch secrets via HTTP request.
</Card>
<Card
href="/integrations/overview"
title="Native Integrations"
icon="clouds"
color="#000000"
>
Explore integrations for GitHub, Vercel, AWS, and more.
</Card>
</CardGroup>
- [Secrets Management](/documentation/platform/secrets-mgmt/overview): Securely store, access, and distribute secrets across environments with fine-grained controls, automatic rotation, and audit logging.
- [Secrets Scanning](/documentation/platform/secret-scanning/overview): Detect hardcoded secrets in code, CI pipelines, and infrastructure—integrated with GitHub, GitLab, Bitbucket, and more.
- [Infisical PKI](/documentation/platform/pki/overview): Issue and manage X.509 certificates using protocols like EST, with support for internal and external CAs.
- [Infisical SSH](/documentation/platform/ssh/overview): Provide short-lived SSH access to servers using certificate-based authentication, replacing static keys with policy-driven, time-bound control.
- [Infisical KMS](/documentation/platform/kms/overview): Encrypt and decrypt data using centrally managed keys with enforced access policies and full audit visibility.

View File

@@ -0,0 +1,77 @@
---
title: "Overview"
sidebarTitle: "Overview"
description: "The open source platform for managing secrets, certificates, and secure infrastructure access."
---
<Card
title="What is Infisical?"
href="/documentation/getting-started/introduction"
>
Learn what Infisical is and how it can help you manage secrets, certificates,
and secure access across your infrastructure.
</Card>
## Products
<Columns cols="2">
<Card
title="Secrets Management"
href="/documentation/platform/secrets-mgmt/overview"
>
Securely store, manage, and control access to sensitive application secrets across your environments.
</Card>
<Card
title="Secrets Scanning"
href="/documentation/platform/secret-scanning/overview"
>
Automatically detect and alert on hardcoded secrets in source code, CI pipelines, and infrastructure.
</Card>
<Card
title="Infisical PKI"
href="/documentation/platform/pki/overview"
>
Automate the issuance and management of X.509 certificates across your infrastructure using modern protocols like EST.
</Card>
<Card
title="Infisical SSH"
href="/documentation/platform/ssh/overview"
>
Replace static SSH keys with short-lived SSH certificates to simplify access and improve security.
</Card>
</Columns>
<Columns cols="1">
<Card
title="Infisical KMS"
href="/documentation/platform/kms/overview"
>
Encrypt and decrypt sensitive data using a centralized key management system.
</Card>
</Columns>
## Resources
<Columns cols="2">
<Card
title="CLI Reference"
href="/cli/overview"
>
Explore Infisicals command-line interface for managing secrets,
certificates, and system operations via terminal.
</Card>
<Card
title="API Reference"
href="/api-reference/overview/introduction"
>
Browse Infisicals API documentation to programmatically interact with
secrets, access controls, and certificate workflows.
</Card>
</Columns>
<Columns cols="1">
<Card title="Self-Hosting" href="/self-hosting/overview">
Learn how to deploy and operate Infisical on your own infrastructure with full
control and data ownership.
</Card>
</Columns>

View File

@@ -0,0 +1,96 @@
---
title: "Machine Identity Auth Templates"
description: "Learn how to use auth templates to standardize authentication configurations for machine identities."
---
## Concept
Machine Identity Auth Templates allow you to create reusable authentication configurations that can be applied across multiple machine identities. This feature helps standardize authentication setups, reduces configuration drift, and simplifies identity management at scale.
Instead of manually configuring authentication settings for each identity, you can create templates with predefined authentication parameters and apply them to multiple identities. This ensures consistency and reduces the likelihood of configuration errors.
Key Benefits:
- **Standardization**: Ensure consistent authentication configurations across identities
- **Efficiency**: Reduce time spent configuring individual identities
- **Governance**: Centrally manage and update authentication parameters
- **Scalability**: Easily apply proven configurations to new identities
## Managing Auth Templates
Auth templates are managed in **Organization Settings > Access Control > Identities** under the **Identity Auth Templates** section.
![Identity Auth Templates Section](/images/platform/identities/auth-templates/templates-section.png)
### Creating a Template
<Steps>
<Step title="Navigate to Auth Templates">
In your organization settings, go to **Access Control > Identities** and scroll down to the **Identity Auth Templates** section.
</Step>
<Step title="Create a new template">
Click **Create Template** to open the template creation modal.
![Create Template Button](/images/platform/identities/auth-templates/create-template-button.png)
Select the authentication method you want to create a template for (currently supports LDAP Auth).
</Step>
<Step title="Configure template settings">
Fill in the template configuration based on your chosen authentication method.
<Tabs>
<Tab title="LDAP Auth Template">
**For LDAP Auth templates**, configure the following fields:
![LDAP Auth Template](/images/platform/identities/auth-templates/ldap-template.png)
- **Template Name**: A descriptive name for your template
- **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`.
- **CA Certificate**: The CA certificate to use when verifying the LDAP server certificate. This field is optional but recommended.
<Note>
You can read more about LDAP Auth configuration in the [LDAP Auth documentation](/documentation/platform/identities/ldap-auth/general).
</Note>
</Tab>
</Tabs>
</Step>
</Steps>
### Using Templates
Once created, templates can be applied when configuring authentication methods for machine identities. When adding an auth method to an identity, you'll have the option to select from available templates or configure manually.
![Attach Template](/images/platform/identities/auth-templates/machine-identity-page.png)
![Attach Template Form](/images/platform/identities/auth-templates/attach-template-form.png)
### Managing Template Usage
You can view which identities are using a specific template by clicking **View Usages** in the template's dropdown menu.
![Template Usages](/images/platform/identities/auth-templates/template-usages.png)
![Template Usages Modal](/images/platform/identities/auth-templates/template-usages-modal.png)
## FAQ
<AccordionGroup>
<Accordion title="Can I modify a template after it's been applied to identities?">
Yes, you can edit existing templates. After editing a template, changes to templates will automatically update identities that are already using them.
</Accordion>
<Accordion title="What happens if I delete a template that's in use?">
If you delete a template that's currently being used by identities, those identities will continue to function with their existing configuration. However, the link to the template will be broken, and you won't be able to use the template for new identities.
</Accordion>
<Accordion title="Can I see which identities are using a specific template?">
Yes, click **View Usages** in the template's dropdown menu to see all identities currently using that template.
</Accordion>
<Accordion title="Do templates support all authentication methods?">
Currently, auth templates support LDAP Auth. Support for additional authentication methods will be added in future releases.
</Accordion>
</AccordionGroup>

View File

@@ -5,6 +5,12 @@ description: "Learn how to authenticate with Infisical using LDAP."
**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.
## Templates
You can create reusable LDAP authentication templates to standardize configurations across multiple machine identities. Templates help ensure consistency, reduce configuration errors, and simplify identity management at scale.
To create and manage LDAP auth templates, see our [Machine Identity Auth Templates documentation](/documentation/platform/identities/auth-templates). Once you've created a template, you can apply it when configuring LDAP auth for your identities in the guide below.
## Guide
<Steps>
<Step title="Creating an identity">

View File

@@ -5,18 +5,19 @@ description: "Read more about the concept of user identities in Infisical."
## Concept
A **user identity** (also known as **user**) represents a developer, admin, or any other human entity interacting with resources in Infisical.
A **user identity** (also known as **user**) represents a developer, admin, or any other human entity interacting with resources in Infisical.
Users can be added manually (through Web UI) or programmatically (e.g., API) to [organizations](../organization) and [projects](../projects).
Users can be added manually (through Web UI) or programmatically (e.g., API) to [organizations](../organization) and [projects](../projects).
Upon being added to an organization and projects, users assume a certain set of roles and permissions that represents their identity.
Upon being added to an organization and projects, users assume a certain set of roles and permissions that represents their identity.
![organization members](../../../images/platform/organization/organization-members.png)
![organization users](/images/platform/organization/organization-users.png)
## Authentication methods
To interact with various resources in Infisical, users are able to utilize a number of authentication methods:
- **Email & Password**: the most common authentication method that is used for authentication into Web Dashboard and Infisical CLI. It is recommended to utilize [Multi-factor Authentication](/documentation/platform/mfa) in addition to it.
- **SSO**: Infisical natively integrates with a number of SSO identity providers like [Google](/documentation/platform/sso/google), [GitHub](/documentation/platform/sso/github), and [GitLab](/documentation/platform/sso/gitlab).
- **SAML SSO**: It is also possible to set up SAML SSO integration with identity providers like [Okta](/documentation/platform/sso/okta), [Microsoft Entra ID](/documentation/platform/sso/azure) (formerly known as Azure AD), [JumpCloud](/documentation/platform/sso/jumpcloud), [Google](/documentation/platform/sso/google-saml), and more.
To interact with various resources in Infisical, users are able to utilize a number of authentication methods:
- **Email & Password**: the most common authentication method that is used for authentication into Web Dashboard and Infisical CLI. It is recommended to utilize [Multi-factor Authentication](/documentation/platform/mfa) in addition to it.
- **SSO**: Infisical natively integrates with a number of SSO identity providers like [Google](/documentation/platform/sso/google), [GitHub](/documentation/platform/sso/github), and [GitLab](/documentation/platform/sso/gitlab).
- **SAML SSO**: It is also possible to set up SAML SSO integration with identity providers like [Okta](/documentation/platform/sso/okta), [Microsoft Entra ID](/documentation/platform/sso/azure) (formerly known as Azure AD), [JumpCloud](/documentation/platform/sso/jumpcloud), [Google](/documentation/platform/sso/google-saml), and more.
- **LDAP**: For organizations with more advanced needs, Infisical also provides user authentication with [LDAP](/documentation/platform/ldap/overview) that includes a number of LDAP providers.

View File

@@ -3,74 +3,94 @@ title: "Organizations"
description: "Learn more and understand the concept of Infisical organizations."
---
An Infisical organization is a set of [projects](./project) that use the same billing. Organizations allow one or more users to control billing and project permissions for all of the projects belonging to the organization. Each project belongs to an organization.
Infisical is structured around organizations and [projects](/documentation/platform/project).
## Organizations
An organization represents a company or high-level entity (e.g. Acme Corp) and acts as the root scope for managing members and machine identities, projects, usage and billing, global integrations and configuration (such as single sign-on, provisioning, etc), and more.
Within an organization, you can create any number of projects—each tied to a specific product type such as Secrets Management or PKI that determines the functionality available.
![organization](/images/platform/organization/organization.png)
## Projects
The **Projects** page is where you can view the projects that you have access to within your organization
as well as create a new project.
The _Projects_ tab shows a list of projects that you have access to.
![organization](../../images/platform/organization/organization-projects.png)
If you're an organization admin, you also have the option to view _All Projects_—a complete view of every project within the organization, including those you are not currently a member of— and gain access to any project.
## Settings
Admins can gain access to any project in the organization by opening the options menu (⋮) next to a project and selecting Access. This will add you to the project as an admin and allow full visibility and control.
The **Settings** page lets you manage information about your organization including:
![organization projects](/images/platform/organization/organization-projects.png)
- **Name**: The name of your organization.
- **Slug**: The slug of your organization.
- **Default Organization Member Role**: The role assigned to users when joining your organization unless otherwise specified.
- **Incident Contacts**: Emails that should be alerted if anything abnormal is detected within the organization.
- **Enabled Products**: Products which are enabled for your organization. This setting strictly affects the sidebar UI; disabling a product does not disable its API or routes.
## Roles and Access Control
![organization settings general](../../images/platform/organization/organization-settings-general.png)
The _Access Control_ tab lets you view and manage roles and permissions for users, machine identities, and groups across your organization.
- Security and Authentication: A set of setting to enforce or manage [SAML](/documentation/platform/sso/overview), [OIDC](/documentation/platform/sso/overview), [SCIM](/documentation/platform/scim/overview), [LDAP](/documentation/platform/ldap/overview), and other authentication configurations.
Users are invited to an organization and assigned organization-level roles such as `Admin` or `Member`. You can also define [custom roles](/documentation/platform/access-controls/role-based-access-controls#creating-custom-roles) at the organization level to fit your permission model.
![organization settings auth](../../images/platform/organization/organization-settings-auth.png)
![organization users](/images/platform/organization/organization-users.png)
<Tip>
You can adjust the maximum time a user token will remain valid for your organization. After this period, users will be required to re-authenticate. This helps improve security by enforcing regular sign-ins.
</Tip>
Infisical supports [user identities](/documentation/platform/identities/user-identities) (representing people) and [machine identities](/documentation/platform/identities/machine-identities) (representing services, CI/CD pipelines, or agents). The same roles and permissions can be applied to either type of identity.
## Access Control
To manage access at scale, Infisical also supports [user groups](/documentation/platform/groups) — roles assigned to a group apply to all of its members automatically.
The **Access Control** page is where you can manage identities (both people and machines) that are part of your organization.
You can add or remove additional members as well as modify their permissions.
Note that Infisical distinguishes between organization-level and project-level access control:
![organization members](../../images/platform/organization/organization-members.png)
![organization identities](../../images/platform/organization/organization-machine-identities.png)
- [Organization-level access control](/documentation/platform/access-controls/role-based-access-controls#organization-level-access-controls): Roles and permissions governing access to organization-level resources and controls such as billing, member management, and identity provider configuration.
- [Project-level access control](/documentation/platform/access-controls/role-based-access-controls#project-level-access-controls): Roles and permissions governing access to resources and workflows within a specific project (e.g., secrets, certificates, SSH hosts).
In the **Organization Roles** tab, you can edit current or create new custom roles for members within the organization.
![organization roles](/images/platform/organization/organization-roles.png)
To learn more about how permissions work in detail, refer to the [access control documentation](/documentation/platform/access-controls/overview).
<Info>
Note that Role-Based Access Management (RBAC) is partly a paid feature.
Infisical provides immutable roles like `admin`, `member`, etc.
at the organization and project level for free.
Infisical provides immutable roles such as `admin` and `member` for free.
If you're using Infisical Cloud, the ability to create custom roles is available under the **Pro Tier**.
If you're self-hosting Infisical, then you should contact sales@infisical.com to purchase an enterprise license to use it.
</Info>
![organization roles](../../images/platform/organization/organization-members-roles.png)
As you can see next, Infisical supports granular permissions that you can tailor to each role.
If you need certain members to only be able to access billing details, for example, then you can
assign them that permission only.
![organization role permissions](../../images/platform/organization/organization-members-roles-add-perm.png)
## Usage & Billing
The **Usage & Billing** page applies only to [Infisical Cloud](https://app.infisical.com) and is where you can
manage your plan and billing information.
The _Usage & Billing_ tab provides an overview of your organization's billing information and platform usage.
This includes the following items:
Infisical calculates usage at the organization level—aggregating activity across all projects and product types (e.g., Secrets Management, SSH, PKI). From this tab, you can track usage, view billing details, and manage your Infisical Cloud subscription.
- Current plan: The current plan information such as what tier your organization is on and what features/limits apply to this tier.
- Licenses: The license keys for self-hosted instances of Infisical (if applicable).
- Receipts: The receipts of monthly/annual invoices.
- Billing: The billing details of your organization including payment methods on file, tax IDs (if applicable), etc.
![organization billing](/images/platform/organization/organization-billing.png)
![organization usage and billing](../../images/platform/organization/organization-usage-billing.png)
## Audit Logs
Infisical provides a unified view of [audit logs](/documentation/platform/audit-logs) at the organization level. All platform activity—including secret access, certificate issuance, platform logins across the organization —is recorded and searchable in a central log view.
Audit logs are also viewable at the project level, where they are scoped to show only events relevant to that specific project. This allows project administrators to monitor activity and investigate changes without requiring organization-wide access.
## App Connections
Infisical supports [app connections](/integrations/app-connections/overview) — integrations configured at the organization level with third-party platforms such as AWS, GCP, GitHub, and many others.
Once configured, these connections can be reused across multiple projects as part of any feature that requires third-party integrations—such as [secret syncing](/integrations/secret-syncs/overview) or [dynamic credential generation](/documentation/platform/dynamic-secrets/overview).
![organization app connections](/images/platform/organization/organization-app-connections.png)
To learn more, refer to the [app connections documentation](/integrations/app-connections/overview).
## Organization Settings
The _Organization Settings_ tab lets you configure global behavior and security controls for the organization.
Key configuration areas include:
- General: Manage the organizations name, slug, and default role for newly invited members.
- Single Sign-On (SSO): Enable [SAML](/documentation/platform/sso/overview), [LDAP](/documentation/platform/ldap/overview), or [OIDC-based](/documentation/platform/sso/general-oidc/overview) authentication for user login.
- Provisioning: Enable [SCIM](/documentation/platform/scim/overview) to automatically provision and deprovision users and groups from an identity provider.
- Security Policies: Enforce MFA and configure session duration limits.
- Encryption: Integrate with external KMS systems or bring your own encryption keys (BYOK).
- [Audit Log Streaming](/documentation/platform/audit-log-streams/audit-log-streams): Forward audit events to third-party logging tools like SIEMs or cloud storage.
- Workflow Integrations: Trigger [Slack](/documentation/platform/workflow-integrations/slack-integration) or [Microsoft Teams](/documentation/platform/workflow-integrations/microsoft-teams-integration) notifications for events like access requests.
- [Project Templates](/documentation/platform/project-templates): Define default environments, roles, and settings to standardize project creation.
- KMIP (Enterprise): Connect to KMIP-compatible HSMs for hardware-backed key storage and operations.
![organization settings](/images/platform/organization/organization-settings.png)

View File

@@ -1,13 +1,13 @@
---
title: "Internal PKI"
title: "Infisical PKI"
sidebarTitle: "Overview"
description: "Learn how to create a Private CA hierarchy and issue X.509 certificates."
---
Infisical can be used to create a Private Certificate Authority (CA) hierarchy and issue X.509 certificates for internal use. This allows you to manage your own PKI infrastructure and issue digital certificates for subscribers such as services, applications, and devices.
Infisical can be used to create and manage Certificate Authorities (CAs) and issue X.509 certificates. This allows you to manage PKI infrastructure and issue digital certificates for subscribers such as services, applications, and devices.
Infisical's PKI offering is split into three components:
- [Certificate Authorities](/documentation/platform/pki/private-ca): Create and manage private CAs, including root and intermediate CAs.
- [Certificate Authorities](/documentation/platform/pki/private-ca): Create and manage CAs, including root and intermediate CAs.
- [Subscribers](/documentation/platform/pki/subscribers): Define and manage entities that will request X.509 certificates from CAs. This module provides a centralized view of all subscribers, enabling you to issue certificates and monitor their status.
- [Certificates](/documentation/platform/pki/certificates): Track and monitor issued X.509 certificates, maintaining a comprehensive inventory of all active and expired certificates.

View File

@@ -1,116 +1,51 @@
---
title: "Projects"
title: "Overview"
description: "Learn more and understand the concept of Infisical projects."
---
A project in Infisical belongs to an [organization](./organization) and contains a number of environments, folders, and secrets.
Only users and machine identities who belong to a project can access resources inside of it according to predefined permissions.
## Projects
Infisical also allows users to request project access. Refer to the [project access request section](./access-controls/project-access-requests)
A project defines a specific scope of work for a given product line in Infisical.
## Project environments
Projects are created within an [organization](/documentation/platform/organization), and an organization can contain multiple projects across different product types.
For both visual and organizational structure, Infisical allows splitting up secrets into environments (e.g., development, staging, production). In project settings, such environments can be
customized depending on the intended use case.
## Project Types
![project secrets overview](../../images/platform/project/project-environments.png)
Infisical supports project types, each representing a different security product with its own dashboard, workflows, and capabilities.
## Secrets Overview
![project types](/images/platform/project/project-types.png)
The **Secrets Overview** page captures a birds-eye-view of secrets and [folders](./folder) across environments.
This is useful for comparing secrets, identifying if anything is missing, and making quick changes.
The supported project types are:
![project secrets overview](../../images/platform/project/project-secrets-overview-open.png)
- [Secrets Management](/documentation/platform/secrets-mgmt/overview): Securely store, access, and distribute secrets across environments with fine-grained controls, automatic rotation, and audit logging.
- [Secrets Scanning](/documentation/platform/secret-scanning/overview): Detect hardcoded secrets in code, CI pipelines, and infrastructure—integrated with GitHub, GitLab, Bitbucket, and more.
- [Infisical PKI](/documentation/platform/pki/overview): Issue and manage X.509 certificates using protocols like EST, with support for internal and external CAs.
- [Infisical SSH](/documentation/platform/ssh/overview): Provide short-lived SSH access to servers using certificate-based authentication, replacing static keys with policy-driven, time-bound control.
- [Infisical KMS](/documentation/platform/kms/overview): Encrypt and decrypt data using centrally managed keys with enforced access policies and full audit visibility.
## Secrets Dashboard
## Roles and Access Control
The **Secrets Dashboard** page appears when you press to manage the secrets of a specific environment.
[Users](/documentation/platform/identities/user-identities) and [machine identities](/documentation/platform/identities/machine-identities) must be added to a project to access its resources. Each identity is assigned a [project-level role](/documentation/platform/access-controls/role-based-access-controls#project-level-access-controls) that defines what they can manage—such as secrets, certificates, or SSH access. These roles apply to both individuals and [user groups](/documentation/platform/groups), enabling scalable access across teams and environments.
![project dashboard](../../images/dashboard.png)
Project access is strictly scoped: only members of a project can view or manage its resources. If someone needs access but isnt part of the project, they can submit an access request.
### Secrets
Each project in Infisical has its own [access control model](/documentation/platform/access-controls/role-based-access-controls#project-level-access-controls), distinct from [organization-level access control](/documentation/platform/access-controls/role-based-access-controls#organization-level-access-controls). While organization roles govern broader administrative access, project-level roles control what users, groups, and machine identities can do within the boundaries of a specific project—such as managing secrets, issuing certificates, or configuring SSH access.
To add a secret, press **Add Secret** button at the top of the dashboard.
Depending on the project type (e.g. Secrets Management, PKI, SSH), project-level access control supports advanced features like [temporary access](/documentation/platform/access-controls/temporary-access), [access requests](/documentation/platform/access-controls/access-requests), and [additional privileges](/documentation/platform/access-controls/additional-privileges).
![project add secret](../../images/platform/project/project-secrets-add.png)
![project roles](/images/platform/project/project-roles.png)
For a new project, it can be convenient to populate the dashboard by dropping a `.env` file into the provided pane as shown below:
To learn more about how permissions work in detail, refer to the [access control documentation](/documentation/platform/access-controls/overview).
![project drop env file](../../images/platform/project/project-secrets-drop-env.png)
## Audit Logs
To delete a secret, hover over it and press the **X** button that appears on the right side.
Infisical provides [audit logging](/documentation/platform/audit-logs) at the project level to help teams monitor activity and maintain accountability within a specific project. These logs capture all relevant events—such as secret access, certificate issuance, and SSH activity—that occur within the boundaries of that project.
![project delete secret](../../images/platform/project/project-secrets-delete.png)
Unlike the organization-level audit view, which aggregates logs across all projects in one centralized interface, the project-level audit view is scoped to a single project. This enables relevant project admins and contributors to review activity relevant to their work without having broader access to audit logs in other projects that they are not part of.
To delete multiple secrets at once, hover over and select the secrets you'd like to delete
and press the **Delete** button that appears at the top.
## Project Settings
![project delete secret batch](../../images/platform/project/project-secrets-delete-batch.png)
Each project has its own settings panel, with options that vary depending on the selected product type. These may include
setup and configuration for environments, tags, behaviors, encryption strategies, and other options.
### Search
To search for specific secrets by their key name, you can use the search bar.
![project search](../../images/platform/project/project-secrets-search.png)
To assist you with finding secrets, you can also group them by similar prefixes and filter them by tags (if applicable).
![project filter](../../images/platform/project/project-secrets-filter.png)
### Hide/Un-hide
To view/hide all secrets at once, toggle the hide or un-hide button.
![project filter](../../images/platform/project/project-secrets-unhide.png)
### Download as .env
To download/export secrets back into a `.env` file, press the download button.
![project download back env](../../images/platform/project/project-secrets-download-env.png)
### Tags
To better organize similar secrets, hover over them and label them with a tag.
![project tag secret](../../images/platform/project/project-secrets-tag.png)
### Comments
To provide more context about a given secret, especially for your team, hover over it and press the comment button.
![project comment secret](../../images/platform/project/project-secrets-comment.png)
### Personal overrides
Infisical employs the concept of **shared** and **personal** secrets to address the need
for common and custom secret values, or branching, amongst members of a team during software development.
To provide a helpful analogy: A shared value is to a `main` branch as a personal value is to a custom branch.
Consider:
- A team with users A, B, user C.
- A project with an environment containing a shared secret called D with the value E.
Suppose user A overrides the value of secret D with the value F.
Then:
- If user A fetches the secret D back, they get the value F.
- If users B and C fetch the secret D back, they both get the value E.
<Info>
Please keep in mind that secret reminders won't work with personal overrides.
</Info>
![project override secret](../../images/platform/project/project-secrets-override.png)
### Drawer
To view the full details of each secret, you can hover over it and press on the ellipses button.
![project secrets ellipses](../../images/platform/project/project-secrets-ellipses.png)
This opens up a side-drawer:
![project secrets drawer](../../images/platform/project/project-secrets-drawer.png)
Project settings are fully independent and reflect the capabilities of the associated product.

View File

@@ -0,0 +1,17 @@
---
title: "Secrets Management"
sidebarTitle: "Overview"
description: "Learn how to securely store, access, and manage sensitive application secrets."
---
Infisical provides a flexible platform for managing application secrets — such as API keys, database credentials, application configuration, and more — across every stage of the development lifecycle from local development to production.
It helps teams eliminate hardcoded secrets, enforce access controls, and adopt secure workflows like secret rotation, dynamic secrets, and secrets syncs to external platforms.
Core capabilities include:
- Secret Stores: Secure, versioned storage scoped by [project](/documentation/platform/secrets-mgmt/project), [environment](/documentation/platform/secrets-mgmt/project#project-environments), and [path](/documentation/platform/folder).
- [Access Control](/documentation/platform/access-controls/overview): Fine-grained, identity-aware permissions for users and machines
- Secret Delivery: Access secrets via [CLI](/cli/overview), [SDKs](/sdks/overview) (Go, Node.js, Python, etc.), [HTTP API](/api-reference/overview/introduction), [agents](/integrations/platforms/infisical-agent), [Kubernetes Operator](/integrations/platforms/kubernetes/overview), [External Secrets Operator (ESO)](https://external-secrets.io/latest/provider/infisical), and more.
- Lifecycle Automation: Automate [secret rotation](/documentation/platform/secret-rotation/overview), generate [dynamic secrets](/documentation/platform/dynamic-secrets/overview), and enforce [approval-based workflows](/documentation/platform/pr-workflows).
- [Secrets Syncs](/integrations/secret-syncs/overview): Push secrets to external services like [GitHub](/integrations/secret-syncs/github), [GitLab](/integrations/secret-syncs/gitlab), [AWS Secrets Manager](/integrations/secret-syncs/aws-secrets-manager), [Vercel](/integrations/secret-syncs/vercel), and more.

View File

@@ -0,0 +1,115 @@
---
title: "Projects"
description: "Learn more and understand the concept of Infisical projects."
---
A secrets management project in Infisical is a dedicated workspace for managing application secrets such as API keys, database credentials, configuration, etc. used by your applications.
Secrets are organized into a clear hierarchy of environments, folders, and individual secrets, making it easy to manage values across different stages of your development lifecycle (e.g., development, staging, production).
## Project environments
For both visual and organizational structure, Infisical allows splitting up secrets into environments (e.g., development, staging, production). In project settings, such environments can be
customized depending on the intended use case.
![project secrets overview](/images/platform/project/project-environments.png)
## Secrets Overview
The **Secrets Overview** page captures a birds-eye-view of secrets and [folders](./folder) across environments.
This is useful for comparing secrets, identifying if anything is missing, and making quick changes.
![project secrets overview](/images/platform/project/project-secrets-overview-open.png)
## Secrets Dashboard
The **Secrets Dashboard** page appears when you press to manage the secrets of a specific environment.
![project dashboard](/images/dashboard.png)
### Secrets
To add a secret, press **Add Secret** button at the top of the dashboard.
![project add secret](/images/platform/project/project-secrets-add.png)
For a new project, it can be convenient to populate the dashboard by dropping a `.env` file into the provided pane as shown below:
![project drop env file](/images/platform/project/project-secrets-drop-env.png)
To delete a secret, hover over it and press the **X** button that appears on the right side.
![project delete secret](/images/platform/project/project-secrets-delete.png)
To delete multiple secrets at once, hover over and select the secrets you'd like to delete
and press the **Delete** button that appears at the top.
![project delete secret batch](/images/platform/project/project-secrets-delete-batch.png)
### Search
To search for specific secrets by their key name, you can use the search bar.
![project search](/images/platform/project/project-secrets-search.png)
To assist you with finding secrets, you can also group them by similar prefixes and filter them by tags (if applicable).
![project filter](/images/platform/project/project-secrets-filter.png)
### Hide/Un-hide
To view/hide all secrets at once, toggle the hide or un-hide button.
![project filter](/images/platform/project/project-secrets-unhide.png)
### Download as .env
To download/export secrets back into a `.env` file, press the download button.
![project download back env](/images/platform/project/project-secrets-download-env.png)
### Tags
To better organize similar secrets, hover over them and label them with a tag.
![project tag secret](/images/platform/project/project-secrets-tag.png)
### Comments
To provide more context about a given secret, especially for your team, hover over it and press the comment button.
![project comment secret](/images/platform/project/project-secrets-comment.png)
### Personal overrides
Infisical employs the concept of **shared** and **personal** secrets to address the need
for common and custom secret values, or branching, amongst members of a team during software development.
To provide a helpful analogy: A shared value is to a `main` branch as a personal value is to a custom branch.
Consider:
- A team with users A, B, user C.
- A project with an environment containing a shared secret called D with the value E.
Suppose user A overrides the value of secret D with the value F.
Then:
- If user A fetches the secret D back, they get the value F.
- If users B and C fetch the secret D back, they both get the value E.
<Info>
Please keep in mind that secret reminders won't work with personal overrides.
</Info>
![project override secret](/images/platform/project/project-secrets-override.png)
### Drawer
To view the full details of each secret, you can hover over it and press on the ellipses button.
![project secrets ellipses](/images/platform/project/project-secrets-ellipses.png)
This opens up a side-drawer:
![project secrets drawer](/images/platform/project/project-secrets-drawer.png)

View File

@@ -1,5 +1,5 @@
---
title: "Infisical SSH"
title: "Host Groups"
sidebarTitle: "Host Groups"
description: "Learn how to organize SSH hosts into groups and manage access policies at scale."
---

View File

@@ -1,5 +1,5 @@
---
title: "Infisical SSH"
title: "Overview"
sidebarTitle: "Overview"
description: "Learn how to securely provision user SSH access to your infrastructure using SSH certificates."
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 513 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 993 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

View File

@@ -105,44 +105,93 @@ The templates hold an array of templates that will be rendered and injected into
### Authentication
The Infisical Agent Injector only supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) authentication at the moment.
The Infisical Agent Injector supports Machine Identity [Kubernetes Auth](/documentation/platform/identities/kubernetes-auth) and [LDAP Auth](/documentation/platform/identities/ldap-auth) authentication.
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
```yaml
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
```
<AccordionGroup>
<Accordion title="Kubernetes Auth">
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
To configure Kubernetes Auth, you need to set the `auth.type` field to `kubernetes` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication.
```yaml
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
```
```bash
kubectl apply -f config-map.yaml
```
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "kubernetes"
config:
identity-id: "<your-infisical-machine-identity-id>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash
kubectl apply -f config-map.yaml
```
</Accordion>
<Accordion title="LDAP Auth">
To configure LDAP Auth, you need to set the `auth.type` field to `ldap-auth` and set the `auth.config.identity-id` to the ID of the machine identity you wish to use for authentication. Configure the `auth.config.username` and `auth.config.password` to the username and password of the LDAP user to authenticate with.
```yaml
auth:
type: "ldap-auth"
config:
identity-id: "<your-infisical-machine-identity-id>"
username: "<your-ldap-username>"
password: "<your-ldap-password>"
```
### Example ConfigMap
```yaml config-map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-config-map
data:
config.yaml: |
infisical:
address: "https://app.infisical.com"
auth:
type: "ldap-auth"
config:
identity-id: "<your-infisical-machine-identity-id>"
username: "<your-ldap-username>"
password: "<your-ldap-password>"
templates:
- destination-path: "/path/to/save/secrets/file.txt"
template-content: |
{{- with secret "<your-project-id>" "dev" "/" }}
{{- range . }}
{{ .Key }}={{ .Value }}
{{- end }}
{{- end }}
```
```bash
kubectl apply -f config-map.yaml
```
</Accordion>
</AccordionGroup>
To use the config map in your pod, you will need to add the `org.infisical.com/agent-config-map` annotation to your pod's deployment. The value of the annotation is the name of the config map you created above.
```yaml

View File

@@ -68,6 +68,11 @@ spec:
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
gcpIdTokenAuth:
identityId: <machine-identity-id>
ldapAuth:
identityId: <machine-identity-id>
credentialsRef:
secretName: <secret-name> # ldap-auth-credentials
secretNamespace: <secret-namespace> # default
kubernetesAuth:
identityId: <machine-identity-id>
serviceAccountRef:
@@ -105,15 +110,15 @@ kind: Secret
### InfisicalDynamicSecret CRD properties
<Accordion title="hostAPI">
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
` https://your-self-hosted-instace.com/api`
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
`https://your-self-hosted-instace.com/api`
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
<Accordion title="Advanced use case">
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
To achieve this, use the following address for the hostAPI field:
``` bash
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
```
@@ -126,18 +131,19 @@ When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
<Accordion title="leaseTTL">
The `leaseTTL` is a string-formatted duration that defines the time the lease should last for the dynamic secret.
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
The following units are supported:
The following units are supported:
- `s` for seconds (must be at least 5 seconds)
- `m` for minutes
- `h` for hours
- `d` for days
- `s` for seconds (must be at least 5 seconds)
- `m` for minutes
- `h` for hours
- `d` for days
<Note>
The lease duration at most be 1 day (24 hours). And the TTL must be less than the max TTL defined on the dynamic secret.
</Note>
<Note>
The lease duration at most be 1 day (24 hours). And the TTL must be less than the max TTL defined on the dynamic secret.
</Note>
</Accordion>
<Accordion title="managedSecretReference">
@@ -212,7 +218,7 @@ spec:
<Accordion title="dynamicSecret">
The `dynamicSecret` field is used to specify which dynamic secret to create leases for. The required fields are `secretName`, `projectId`, `secretsPath`, and `environmentSlug`.
```yaml
spec:
dynamicSecret:
@@ -300,7 +306,6 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
- `autoCreateServiceAccountToken`: If set to `true`, the operator will automatically create a short-lived service account token on-demand for the service account. Defaults to `false`.
- `serviceAccountTokenAudiences`: Optionally specify audience for the service account token. This field is only relevant if you have set `autoCreateServiceAccountToken` to `true`. No audience is specified by default.
Example:
```yaml
@@ -316,7 +321,40 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
```
</Accordion>
<Accordion title="ldapAuth">
The LDAP machine identity authentication method is used to authenticate with a configured LDAP directory. [Read more about LDAP Auth](/documentation/platform/identities/ldap-auth).
Valid fields:
- `identityId`: The identity ID of the machine identity you created.
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the LDAP credentials.
- `credentialsRef.secretName`: The name of the Kubernetes secret.
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
Example:
```yaml
# infisical-push-secret.yaml
spec:
ldapAuth:
identityId: <machine-identity-id>
credentialsRef:
secretName: <secret-name>
secretNamespace: <secret-namespace>
```
```yaml
# machine-identity-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: ldap-auth-credentials
type: Opaque
stringData:
username: <ldap-username>
password: <ldap-password>
```
</Accordion>
<Accordion title="awsIamAuth">
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
@@ -391,7 +429,7 @@ The available authentication methods are `universalAuth`, `kubernetesAuth`, `aws
<Accordion title="tls">
This block defines the TLS settings to use for connecting to the Infisical
instance.
Fields:
<Accordion title="caRef">
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
@@ -444,7 +482,7 @@ metadata:
name: nginx-deployment
labels:
app: nginx
annotations:
annotations:
secrets.infisical.com/auto-reload: "true" # <- redeployment annotation
spec:
replicas: 1
@@ -467,7 +505,7 @@ spec:
```
</Accordion>
<Info>
#### How it works
When the lease changes, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
#### How it works
When the lease changes, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
Then, for each deployment that has this annotation present, a rolling update will be triggered. A redeployment won't happen if the lease is renewed, only if it's recreated.
</Info>

View File

@@ -5,9 +5,9 @@ description: "Learn how to use the InfisicalPushSecret CRD to push and manage se
---
## Overview
## Overview
The **InfisicalPushSecret** CRD allows you to create secrets in your Kubernetes cluster and push them to Infisical.
The **InfisicalPushSecret** CRD allows you to create secrets in your Kubernetes cluster and push them to Infisical.
This CRD offers the following features:
@@ -70,6 +70,11 @@ Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes sec
serviceAccountRef:
name: <secret-name>
namespace: <secret-namespace>
ldapAuth:
identityId: <machine-identity-id>
credentialsRef:
secretName: <secret-name> # ldap-auth-credentials
secretNamespace: <secret-namespace> # default
universalAuth:
credentialsRef:
secretName: <secret-name> # universal-auth-credentials
@@ -104,15 +109,15 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
## InfisicalPushSecret CRD properties
<Accordion title="hostAPI">
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
` https://your-self-hosted-instace.com/api`
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
`https://your-self-hosted-instace.com/api`
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
<Accordion title="Advanced use case">
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
To achieve this, use the following address for the hostAPI field:
``` bash
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
```
@@ -187,7 +192,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
<Accordion title="destination">
The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`.
```yaml
spec:
destination:
@@ -212,7 +217,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
<Accordion title="push">
The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below.
<Accordion title="secret">
@@ -220,7 +225,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
Example usage of the `push.secret` field:
Example usage of the `push.secret` field:
```yaml infisical-push-secret.yaml
push:
@@ -282,7 +287,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
spec:
universalAuth:
credentialsRef:
secretName: <secret-name>
secretName: <secret-name>
secretNamespace: <secret-namespace>
```
@@ -324,7 +329,39 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
namespace: <secret-namespace>
```
</Accordion>
<Accordion title="ldapAuth">
The LDAP machine identity authentication method is used to authenticate with a configured LDAP directory. [Read more about LDAP Auth](/documentation/platform/identities/ldap-auth).
Valid fields:
- `identityId`: The identity ID of the machine identity you created.
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the LDAP credentials.
- `credentialsRef.secretName`: The name of the Kubernetes secret.
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
Example:
```yaml
# infisical-push-secret.yaml
spec:
ldapAuth:
identityId: <machine-identity-id>
credentialsRef:
secretName: <secret-name>
secretNamespace: <secret-namespace>
```
```yaml
# machine-identity-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: ldap-auth-credentials
type: Opaque
stringData:
username: <ldap-username>
password: <ldap-password>
```
</Accordion>
<Accordion title="awsIamAuth">
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
@@ -398,7 +435,7 @@ After applying the InfisicalPushSecret CRD, you should notice that the secrets y
<Accordion title="tls">
This block defines the TLS settings to use for connecting to the Infisical
instance.
Fields:
<Accordion title="caRef">
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
@@ -438,7 +475,7 @@ Using Go templates, you can format, combine, and create new key-value pairs of s
Use this option when you would like to push **only** a subset of secrets from the Kubernetes secret to Infisical.
</Accordion>
<Accordion title="push.secret.template.data">
Define secret keys and their corresponding templates.
Define secret keys and their corresponding templates.
Each data value uses a Golang template with access to all secrets defined in the `push.secret.secretName` Kubernetes secret.
Secrets are structured as follows:
@@ -483,7 +520,7 @@ A generator is defined as a custom resource (`ClusterGenerator`) within the clus
Because of this behavior, you may want to disable automatic syncing for the `InfisicalPushSecret` resource to avoid continuous regeneration of secrets. This can be done by omitting the `resyncInterval` field from the InfisicalPushSecret CRD.
### Example usage
### Example usage
```yaml
push:
secret:
@@ -625,4 +662,4 @@ After applying, you should notice that the secrets have been pushed to Infisical
```bash
kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field
kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself
```
```

View File

@@ -44,15 +44,15 @@ spec:
The following properties help define what instance of Infisical the operator will interact with, the interval it will sync secrets and any CA certificates that may be required to connect.
<Accordion title="hostAPI">
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
` https://your-self-hosted-instace.com/api`
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
<Accordion title="Advanced use case">
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
To achieve this, use the following address for the hostAPI field:
``` bash
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
```
@@ -110,7 +110,7 @@ The list of available authentication methods are shown below.
<Step title="Create Kubernetes secret containing machine identity credentials">
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
To quickly create a Kubernetes secret containing the identity credentials, you can run the command below.
Make sure you replace `<your-identity-client-id>` with the identity client ID and `<your-identity-client-secret>` with the identity client secret.
``` bash
@@ -525,49 +525,6 @@ spec:
...
```
</Tab>
</Tabs>
@@ -747,6 +704,59 @@ spec:
</Accordion>
<Accordion title="authentication.ldapAuth">
The LDAP machine identity authentication method is used to authenticate with Infisical using the configured LDAP directory. The username and password needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores these credentials.
<Steps>
<Step title="Create a machine identity">
You need to create a machine identity, and give it access to the project(s) you want to interact with. You can [read more about machine identities here](/documentation/platform/identities/universal-auth).
</Step>
<Step title="Create Kubernetes secret containing machine identity credentials">
Once you have created your machine identity and added it to your project(s), you will need to create a Kubernetes secret containing the identity credentials.
To quickly create a Kubernetes secret containing the identity credentials, you can run the command below.
Make sure you replace `<your-identity-ldap-username>` with the identity LDAP username and `<your-identity-ldap-password>` with the identity LDAP password.
``` bash
kubectl create secret generic ldap-auth-credentials --from-literal=username="<your-identity-ldap-username>" --from-literal=password="<your-identity-ldap-password>"
```
</Step>
<Step title="Add reference for the Kubernetes secret containing the identity credentials">
Once the secret is created, add the `secretName` and `secretNamespace` of the secret that was just created under `authentication.ldapAuth.credentialsRef` field in the InfisicalSecret resource.
</Step>
</Steps>
<Info>
Make sure to also populate the `secretsScope` field with the project slug
_`projectSlug`_, environment slug _`envSlug`_, and secrets path
_`secretsPath`_ that you want to fetch secrets from. Please see the example
below.
</Info>
## Example
```yaml
apiVersion: secrets.infisical.com/v1alpha1
kind: InfisicalSecret
metadata:
name: infisicalsecret-sample-crd
spec:
authentication:
ldapAuth:
secretsScope:
projectSlug: <project-slug> # <-- project slug
envSlug: <env-slug> # "dev", "staging", "prod", etc..
secretsPath: "<secrets-path>" # Root is "/"
identityId: <machine-identity-id>
credentialsRef:
secretName: ldap-auth-credentials # <-- name of the Kubernetes secret that stores our machine identity credentials
secretNamespace: default # <-- namespace of the Kubernetes secret that stores our machine identity credentials
```
</Accordion>
<Accordion title="authentication.serviceToken">
The service token required to authenticate with Infisical needs to be stored in a Kubernetes secret. This block defines the reference to the name and namespace of secret that stores this service token.
@@ -826,7 +836,7 @@ managedKubeSecretReferences:
The name of the managed Kubernetes secret to be created
</Accordion>
<Accordion title="managedKubeSecretReferences[].secretNamespace">
The namespace of the managed Kubernetes secret to be created.
The namespace of the managed Kubernetes secret to be created.
</Accordion>
<Accordion title="managedKubeSecretReferences[].secretType">
Override the default Opaque type for managed secrets with this field. Useful for creating kubernetes.io/dockerconfigjson secrets.
@@ -855,8 +865,8 @@ Using Go templates, you can format, combine, and create new key-value pairs from
<Accordion title="managedKubeSecretReferences[].template">
</Accordion>
<Accordion title="managedKubeSecretReferences[].template.includeAllSecrets">
This property controls what secrets are included in your managed secret when using templates.
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
This property controls what secrets are included in your managed secret when using templates.
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes secret resource.
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
When set to `false`, only secrets defined in the `managedKubeSecretReferences[].template.data` field of the template will be included in the managed secret.
@@ -864,7 +874,7 @@ Use this option when you would like to sync **only** a subset of secrets from In
</Accordion>
<Accordion title="managedKubeSecretReferences[].template.data">
Define secret keys and their corresponding templates.
Define secret keys and their corresponding templates.
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
Secrets are structured as follows:
@@ -928,7 +938,9 @@ The properties includes defining the name and namespace of the Kubernetes config
The Infisical operator will automatically create the Kubernetes config map in the specified name/namespace and ensure it stays up-to-date. If a config map already exists in the specified namespace, the operator will update the existing config map with the new data.
<Warning>
The usage of config maps is only intended for storing non-sensitive data. If you are looking to store sensitive data, please use the [managed secret](#operator-managed-secrets) property instead.
The usage of config maps is only intended for storing non-sensitive data. If
you are looking to store sensitive data, please use the [managed
secret](#operator-managed-secrets) property instead.
</Warning>
<Accordion title="managedKubeConfigMapReferences">
@@ -937,25 +949,24 @@ The Infisical operator will automatically create the Kubernetes config map in th
The name of the managed Kubernetes config map that your Infisical data will be stored in.
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].configMapNamespace">
The namespace of the managed Kubernetes config map that your Infisical data will be stored in.
The namespace of the managed Kubernetes config map that your Infisical data will be stored in.
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].creationPolicy">
Creation policies allow you to control whether or not owner references should be added to the managed Kubernetes config map that is generated by the Infisical operator.
This is useful for tools such as ArgoCD, where every resource requires an owner reference; otherwise, it will be pruned automatically.
#### Available options
#### Available options
- `Orphan` (default)
- `Owner`
- `Orphan` (default)
- `Owner`
<Tip>
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in
the same namespace as where the managed kubernetes config map.
</Tip>
<Tip>
When creation policy is set to `Owner`, the `InfisicalSecret` CRD must be in
the same namespace as where the managed kubernetes config map.
</Tip>
</Accordion>
#### Managed ConfigMap Templating
Fetching secrets from Infisical as is via the operator may not be enough. This is where templating functionality may be helpful.
@@ -964,67 +975,68 @@ Using Go templates, you can format, combine, and create new key-value pairs from
<Accordion title="managedKubeConfigMapReferences[].template">
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].template.includeAllSecrets">
This property controls what secrets are included in your managed config map when using templates.
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes config map resource.
This property controls what secrets are included in your managed config map when using templates.
When set to `true`, all secrets fetched from your Infisical project will be added into your managed Kubernetes config map resource.
**Use this option when you would like to sync all secrets from Infisical to Kubernetes but want to template a subset of them.**
When set to `false`, only secrets defined in the `managedKubeConfigMapReferences[].template.data` field of the template will be included in the managed config map.
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
When set to `false`, only secrets defined in the `managedKubeConfigMapReferences[].template.data` field of the template will be included in the managed config map.
Use this option when you would like to sync **only** a subset of secrets from Infisical to Kubernetes.
</Accordion>
<Accordion title="managedKubeConfigMapReferences[].template.data">
Define secret keys and their corresponding templates.
Define secret keys and their corresponding templates.
Each data value uses a Golang template with access to all secrets retrieved from the specified scope.
Secrets are structured as follows:
Secrets are structured as follows:
```golang
type TemplateSecret struct {
Value string `json:"value"`
SecretPath string `json:"secretPath"`
}
```
```golang
type TemplateSecret struct {
Value string `json:"value"`
SecretPath string `json:"secretPath"`
}
```
#### Example template configuration:
#### Example template configuration:
```yaml
managedKubeConfigMapReferences:
- configMapName: managed-configmap
configMapNamespace: default
template:
includeAllSecrets: true
data:
# Create new key that doesn't exist in your Infisical project using values of other secrets
SITE_URL: "{{ .SITE_URL.Value }}"
# Override an existing key in Infisical project with a new value using values of other secrets
API_URL: "https://api.{{.SITE_URL.Value}}.{{.REGION.Value}}.com"
```
```yaml
managedKubeConfigMapReferences:
- configMapName: managed-configmap
configMapNamespace: default
template:
includeAllSecrets: true
data:
# Create new key that doesn't exist in your Infisical project using values of other secrets
SITE_URL: "{{ .SITE_URL.Value }}"
# Override an existing key in Infisical project with a new value using values of other secrets
API_URL: "https://api.{{.SITE_URL.Value}}.{{.REGION.Value}}.com"
```
For this example, let's assume the following secrets exist in your Infisical project:
For this example, let's assume the following secrets exist in your Infisical project:
```
SITE_URL = "https://example.com"
REGION = "us-east-1"
API_URL = "old-url" # This will be overridden
```
```
SITE_URL = "https://example.com"
REGION = "us-east-1"
API_URL = "old-url" # This will be overridden
```
The resulting managed Kubernetes config map will then contain:
The resulting managed Kubernetes config map will then contain:
```
# Original config map data (from includeAllSecrets: true)
SITE_URL = "https://example.com"
REGION = "us-east-1"
```
# Original config map data (from includeAllSecrets: true)
SITE_URL = "https://example.com"
REGION = "us-east-1"
# New and overridden config map data
SITE_URL = "https://example.com"
API_URL = "https://api.example.com.us-east-1.com" # Existing secret overridden by template
```
# New and overridden config map data
SITE_URL = "https://example.com"
API_URL = "https://api.example.com.us-east-1.com" # Existing secret overridden by template
```
To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates.
To help transform your config map data further, the operator provides a set of built-in functions that you can use in your templates.
### Available templating functions
Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information.
### Available templating functions
Please refer to the [templating functions documentation](/integrations/platforms/kubernetes/overview#available-helper-functions) for more information.
</Accordion>
## Applying CRD
@@ -1061,8 +1073,6 @@ To verify that the operator has successfully created the managed secret, you can
</Tab>
</Tabs>
## Using Managed Secret In Your Deployment
To make use of the managed secret created by the operator into your deployment can be achieved through several methods.
@@ -1071,45 +1081,45 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
<Accordion title="envFrom">
This will take all the secrets from your managed secret and expose them to your container
````yaml
envFrom:
- secretRef:
name: managed-secret # managed secret name
```
````yaml
envFrom:
- secretRef:
name: managed-secret # managed secret name
```
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
envFrom:
- secretRef:
name: managed-secret # <- name of managed secret
ports:
- containerPort: 80
````
spec:
containers:
- name: nginx
image: nginx:1.14.2
envFrom:
- secretRef:
name: managed-secret # <- name of managed secret
ports:
- containerPort: 80
````
</Accordion>
<Accordion title="env">
This will allow you to select individual secrets by key name from your managed secret and expose them to your container
<Accordion title="env">
This will allow you to select individual secrets by key name from your managed secret and expose them to your container
```yaml
env:
- name: SECRET_NAME # The environment variable's name which is made available in the container
@@ -1119,37 +1129,38 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
key: SOME_SECRET_KEY # The name of the key which exists in the managed secret
```
Example usage in a deployment
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
env:
- name: STRIPE_API_SECRET
valueFrom:
secretKeyRef:
name: managed-secret # <- name of managed secret
key: STRIPE_API_SECRET
ports:
- containerPort: 80
```
spec:
containers:
- name: nginx
image: nginx:1.14.2
env:
- name: STRIPE_API_SECRET
valueFrom:
secretKeyRef:
name: managed-secret # <- name of managed secret
key: STRIPE_API_SECRET
ports:
- containerPort: 80
```
</Accordion>
<Accordion title="volumes">
@@ -1161,48 +1172,48 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
secretName: managed-secret # managed secret name
````
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
```yaml
volumeMounts:
- name: secrets-volume-name
mountPath: /etc/secrets
readOnly: true
```
```yaml
volumeMounts:
- name: secrets-volume-name
mountPath: /etc/secrets
readOnly: true
```
Example usage in a deployment
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
volumeMounts:
- name: secrets-volume-name
mountPath: /etc/secrets
readOnly: true
ports:
- containerPort: 80
volumes:
- name: secrets-volume-name
secret:
secretName: managed-secret # <- managed secrets
```
spec:
containers:
- name: nginx
image: nginx:1.14.2
volumeMounts:
- name: secrets-volume-name
mountPath: /etc/secrets
readOnly: true
ports:
- containerPort: 80
volumes:
- name: secrets-volume-name
secret:
secretName: managed-secret # <- managed secrets
```
</Accordion>
@@ -1244,7 +1255,7 @@ secrets.infisical.com/auto-reload: "true"
name: nginx-deployment
labels:
app: nginx
annotations:
annotations:
secrets.infisical.com/auto-reload: "true" # <- redeployment annotation
spec:
replicas: 1
@@ -1339,9 +1350,11 @@ secrets.infisical.com/auto-reload: "true"
</Accordion>
<Info>
#### How it works
When a managed secret is updated, the operator checks for any Deployments, DaemonSets, or StatefulSets that consume the updated secret and have the annotation
`secrets.infisical.com/auto-reload: "true"`. For each matching workload, the operator triggers a rolling restart to ensure it picks up the latest secret values.
#### How it works When a managed secret is updated, the operator checks for
any Deployments, DaemonSets, or StatefulSets that consume the updated secret
and have the annotation `secrets.infisical.com/auto-reload: "true"`. For each
matching workload, the operator triggers a rolling restart to ensure it picks
up the latest secret values.
</Info>
## Using Managed ConfigMap In Your Deployment
@@ -1350,52 +1363,52 @@ To make use of the managed ConfigMap created by the operator into your deploymen
Here, we will highlight three of the most common ways to utilize it. Learn more about Kubernetes ConfigMaps [here](https://kubernetes.io/docs/concepts/configuration/configmap/)
<Tip>
Automatic redeployment of deployments using managed ConfigMaps is not yet supported.
Automatic redeployment of deployments using managed ConfigMaps is not yet
supported.
</Tip>
<Accordion title="envFrom">
This will take all the secrets from your managed ConfigMap and expose them to your container
````yaml
envFrom:
- configMapRef:
name: managed-configmap # managed configmap name
```
````yaml
envFrom:
- configMapRef:
name: managed-configmap # managed configmap name
```
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
envFrom:
- configMapRef:
name: managed-configmap # <- name of managed configmap
ports:
- containerPort: 80
````
spec:
containers:
- name: nginx
image: nginx:1.14.2
envFrom:
- configMapRef:
name: managed-configmap # <- name of managed configmap
ports:
- containerPort: 80
````
</Accordion>
<Accordion title="env">
This will allow you to select individual secrets by key name from your managed ConfigMap and expose them to your container
<Accordion title="env">
This will allow you to select individual secrets by key name from your managed ConfigMap and expose them to your container
```yaml
env:
- name: CONFIG_NAME # The environment variable's name which is made available in the container
@@ -1405,37 +1418,37 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
key: SOME_CONFIG_KEY # The name of the key which exists in the managed configmap
```
Example usage in a deployment
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
env:
- name: STRIPE_API_SECRET
valueFrom:
configMapKeyRef:
name: managed-configmap # <- name of managed configmap
key: STRIPE_API_SECRET
ports:
- containerPort: 80
```
spec:
containers:
- name: nginx
image: nginx:1.14.2
env:
- name: STRIPE_API_SECRET
valueFrom:
configMapKeyRef:
name: managed-configmap # <- name of managed configmap
key: STRIPE_API_SECRET
ports:
- containerPort: 80
```
</Accordion>
@@ -1448,48 +1461,49 @@ Here, we will highlight three of the most common ways to utilize it. Learn more
name: managed-configmap # managed configmap name
````
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
You can then mount this volume to the container's filesystem so that your deployment can access the files containing the managed secrets
```yaml
volumeMounts:
- name: configmaps-volume-name
mountPath: /etc/config
readOnly: true
```
```yaml
volumeMounts:
- name: configmaps-volume-name
mountPath: /etc/config
readOnly: true
```
Example usage in a deployment
Example usage in a deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
template:
metadata:
labels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
volumeMounts:
- name: configmaps-volume-name
mountPath: /etc/config
readOnly: true
ports:
- containerPort: 80
volumes:
- name: configmaps-volume-name
configMap:
name: managed-configmap # <- managed configmap
```
spec:
containers:
- name: nginx
image: nginx:1.14.2
volumeMounts:
- name: configmaps-volume-name
mountPath: /etc/config
readOnly: true
ports:
- containerPort: 80
volumes:
- name: configmaps-volume-name
configMap:
name: managed-configmap # <- managed configmap
```
</Accordion>
The definition file of the Kubernetes secret for the CA certificate can be structured like the following:
@@ -1532,20 +1546,21 @@ Thus, if a specific label is required on the resulting secret, it can be applied
...
```
This would result in the following managed secret to be created:
This would result in the following managed secret to be created:
```yaml
apiVersion: v1
data: ...
kind: Secret
metadata:
annotations:
example.com/annotation-to-be-passed-to-managed-secret: sample-value
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
labels:
label-to-be-passed-to-managed-secret: sample-value
name: managed-token
namespace: default
type: Opaque
```
```yaml
apiVersion: v1
data: ...
kind: Secret
metadata:
annotations:
example.com/annotation-to-be-passed-to-managed-secret: sample-value
secrets.infisical.com/version: W/"3f1-ZyOSsrCLGSkAhhCkY2USPu2ivRw"
labels:
label-to-be-passed-to-managed-secret: sample-value
name: managed-token
namespace: default
type: Opaque
```
</Accordion>

View File

@@ -217,3 +217,14 @@ Supports conditions and permission inversion
| `edit-gateways` | Modify existing gateway settings |
| `delete-gateways` | Remove gateways from organization |
| `attach-gateways` | Attach gateways to resources |
#### Subject: `machine-identity-auth-template`
| Action | Description |
| ------------------ | ---------------------------------------------- |
| `list-templates` | View identity auth templates |
| `create-templates` | Create new identity auth templates |
| `edit-templates` | Modify existing identity auth templates |
| `delete-templates` | Remove identity auth templates |
| `unlink-templates` | Unlink identity auth templates from identities |
| `attach-templates` | Attach identity auth templates to identities |

View File

@@ -21,6 +21,15 @@ export enum OrgGatewayPermissionActions {
AttachGateways = "attach-gateways"
}
export enum OrgPermissionMachineIdentityAuthTemplateActions {
ListTemplates = "list-templates",
CreateTemplates = "create-templates",
EditTemplates = "edit-templates",
DeleteTemplates = "delete-templates",
UnlinkTemplates = "unlink-templates",
AttachTemplates = "attach-templates"
}
export enum OrgPermissionSubjects {
Workspace = "workspace",
Role = "role",
@@ -42,7 +51,8 @@ export enum OrgPermissionSubjects {
Kmip = "kmip",
Gateway = "gateway",
SecretShare = "secret-share",
GithubOrgSync = "github-org-sync"
GithubOrgSync = "github-org-sync",
MachineIdentityAuthTemplate = "machine-identity-auth-template"
}
export enum OrgPermissionAdminConsoleAction {
@@ -113,6 +123,10 @@ export type OrgPermissionSet =
| [OrgPermissionAppConnectionActions, OrgPermissionSubjects.AppConnections]
| [OrgPermissionIdentityActions, OrgPermissionSubjects.Identity]
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
| [
OrgPermissionMachineIdentityAuthTemplateActions,
OrgPermissionSubjects.MachineIdentityAuthTemplate
]
| [OrgGatewayPermissionActions, OrgPermissionSubjects.Gateway]
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
// TODO(scott): add back once org UI refactored

View File

@@ -1385,6 +1385,7 @@ export const useAddIdentityLdapAuth = () => {
return useMutation<IdentityLdapAuth, object, AddIdentityLdapAuthDTO>({
mutationFn: async ({
identityId,
templateId,
url,
bindDN,
bindPass,
@@ -1400,6 +1401,7 @@ export const useAddIdentityLdapAuth = () => {
const { data } = await apiRequest.post<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`,
{
templateId,
url,
bindDN,
bindPass,
@@ -1432,6 +1434,7 @@ export const useUpdateIdentityLdapAuth = () => {
return useMutation<IdentityLdapAuth, object, UpdateIdentityLdapAuthDTO>({
mutationFn: async ({
identityId,
templateId,
url,
bindDN,
bindPass,
@@ -1447,6 +1450,7 @@ export const useUpdateIdentityLdapAuth = () => {
const { data } = await apiRequest.patch<{ identityLdapAuth: IdentityLdapAuth }>(
`/api/v1/auth/ldap-auth/identities/${identityId}`,
{
templateId,
url,
bindDN,
bindPass,

View File

@@ -567,10 +567,11 @@ export type IdentityTokenAuth = {
export type AddIdentityLdapAuthDTO = {
organizationId: string;
identityId: string;
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
templateId?: string;
url?: string;
bindDN?: string;
bindPass?: string;
searchBase?: string;
searchFilter: string;
ldapCaCertificate?: string;
allowedFields?: {
@@ -588,6 +589,7 @@ export type AddIdentityLdapAuthDTO = {
export type UpdateIdentityLdapAuthDTO = {
identityId: string;
organizationId: string;
templateId?: string;
url?: string;
bindDN?: string;
bindPass?: string;
@@ -612,10 +614,11 @@ export type DeleteIdentityLdapAuthDTO = {
};
export type IdentityLdapAuth = {
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
url?: string;
bindDN?: string;
templateId?: string;
bindPass?: string;
searchBase?: string;
searchFilter: string;
ldapCaCertificate?: string;
allowedFields?: {

View File

@@ -0,0 +1,3 @@
export * from "./mutations";
export * from "./queries";
export * from "./types";

View File

@@ -0,0 +1,97 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { identityAuthTemplatesKeys } from "./queries";
import {
CreateIdentityAuthTemplateDTO,
DeleteIdentityAuthTemplateDTO,
IdentityAuthTemplate,
MachineAuthTemplateUsage,
UnlinkTemplateUsageDTO,
UpdateIdentityAuthTemplateDTO
} from "./types";
export const useCreateIdentityAuthTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (dto: CreateIdentityAuthTemplateDTO) => {
const { data } = await apiRequest.post<{ template: IdentityAuthTemplate }>(
"/api/v1/identity-templates",
dto
);
return data.template;
},
onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplates({ organizationId })
});
}
});
};
export const useUpdateIdentityAuthTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (dto: UpdateIdentityAuthTemplateDTO) => {
const { data } = await apiRequest.patch<{ template: IdentityAuthTemplate }>(
`/api/v1/identity-templates/${dto.templateId}`,
dto
);
return data.template;
},
onSuccess: (_, { organizationId, templateId }) => {
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplates({ organizationId })
});
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplate(templateId)
});
}
});
};
export const useDeleteIdentityAuthTemplate = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (dto: DeleteIdentityAuthTemplateDTO) => {
await apiRequest.delete(`/api/v1/identity-templates/${dto.templateId}`, {
params: { organizationId: dto.organizationId }
});
},
onSuccess: (_, { organizationId, templateId }) => {
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplates({ organizationId })
});
queryClient.removeQueries({
queryKey: identityAuthTemplatesKeys.getTemplate(templateId)
});
}
});
};
export const useUnlinkTemplateUsage = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (dto: UnlinkTemplateUsageDTO) => {
const { data } = await apiRequest.post<MachineAuthTemplateUsage[]>(
`/api/v1/identity-templates/${dto.templateId}/delete-usage`,
{ identityIds: dto.identityIds },
{ params: { organizationId: dto.organizationId } }
);
return data;
},
onSuccess: (_, { templateId, organizationId }) => {
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplateUsages(templateId)
});
queryClient.invalidateQueries({
queryKey: identityAuthTemplatesKeys.getTemplates({ organizationId })
});
}
});
};

View File

@@ -0,0 +1,89 @@
import { useQuery } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import {
GetIdentityAuthTemplatesDTO,
GetTemplateUsagesDTO,
IdentityAuthTemplate,
MachineAuthTemplateUsage,
MachineIdentityAuthMethod
} from "./types";
export const identityAuthTemplatesKeys = {
all: ["identity-auth-templates"] as const,
getTemplates: (dto: GetIdentityAuthTemplatesDTO) =>
[...identityAuthTemplatesKeys.all, "list", dto] as const,
getTemplate: (templateId: string) =>
[...identityAuthTemplatesKeys.all, "single", templateId] as const,
getAvailableTemplates: (authMethod: MachineIdentityAuthMethod) =>
[...identityAuthTemplatesKeys.all, "available", authMethod] as const,
getTemplateUsages: (templateId: string) =>
[...identityAuthTemplatesKeys.all, "usages", templateId] as const
};
export const useGetIdentityAuthTemplates = (dto: GetIdentityAuthTemplatesDTO) => {
return useQuery({
queryKey: identityAuthTemplatesKeys.getTemplates(dto),
queryFn: async () => {
const { data } = await apiRequest.get<{
templates: IdentityAuthTemplate[];
totalCount: number;
}>("/api/v1/identity-templates/search", {
params: {
organizationId: dto.organizationId,
limit: dto.limit || 50,
offset: dto.offset || 0,
...(dto.search && { search: dto.search })
}
});
return data;
},
enabled: Boolean(dto.organizationId)
});
};
export const useGetIdentityAuthTemplate = (templateId: string, organizationId: string) => {
return useQuery({
queryKey: identityAuthTemplatesKeys.getTemplate(templateId),
queryFn: async () => {
const { data } = await apiRequest.get<IdentityAuthTemplate>(
`/api/v1/identity-templates/${templateId}`,
{
params: { organizationId }
}
);
return data;
},
enabled: Boolean(templateId) && Boolean(organizationId)
});
};
export const useGetAvailableTemplates = (authMethod: MachineIdentityAuthMethod) => {
return useQuery({
queryKey: identityAuthTemplatesKeys.getAvailableTemplates(authMethod),
queryFn: async () => {
const { data } = await apiRequest.get<IdentityAuthTemplate[]>("/api/v1/identity-templates", {
params: { authMethod }
});
return data;
},
enabled: Boolean(authMethod)
});
};
export const useGetTemplateUsages = (dto: GetTemplateUsagesDTO) => {
return useQuery({
queryKey: identityAuthTemplatesKeys.getTemplateUsages(dto.templateId),
queryFn: async () => {
const { data } = await apiRequest.get<MachineAuthTemplateUsage[]>(
`/api/v1/identity-templates/${dto.templateId}/usage`,
{
params: { organizationId: dto.organizationId }
}
);
return data;
},
enabled: Boolean(dto.templateId) && Boolean(dto.organizationId)
});
};

View File

@@ -0,0 +1,78 @@
export enum MachineIdentityAuthMethod {
LDAP = "ldap"
}
export interface LdapTemplateFields {
url: string;
bindDN: string;
bindPass: string;
searchBase: string;
ldapCaCertificate?: string;
}
export interface IdentityAuthTemplate {
id: string;
name: string;
authMethod: MachineIdentityAuthMethod;
organizationId: string;
templateFields: LdapTemplateFields;
createdAt: string;
updatedAt: string;
}
export interface CreateIdentityAuthTemplateDTO {
organizationId: string;
name: string;
authMethod: MachineIdentityAuthMethod;
templateFields: LdapTemplateFields;
}
export interface UpdateIdentityAuthTemplateDTO {
templateId: string;
organizationId: string;
name?: string;
templateFields?: Partial<LdapTemplateFields>;
}
export interface DeleteIdentityAuthTemplateDTO {
templateId: string;
organizationId: string;
}
export interface GetIdentityAuthTemplatesDTO {
organizationId: string;
limit?: number;
offset?: number;
search?: string;
}
export interface MachineAuthTemplateUsage {
identityId: string;
identityName: string;
}
export interface GetTemplateUsagesDTO {
templateId: string;
organizationId: string;
}
export interface UnlinkTemplateUsageDTO {
templateId: string;
identityIds: string[];
organizationId: string;
}
export const TEMPLATE_ERROR_MESSAGES = {
UNLINK_SUCCESS: "Successfully unlinked template usages",
UNLINK_FAILED: "Failed to unlink template usages",
SINGLE_UNLINK_SUCCESS: "Successfully unlinked template usage",
SINGLE_UNLINK_FAILED: "Failed to unlink template usage"
} as const;
export const TEMPLATE_UI_LABELS = {
VIEW_USAGES: "View Usages",
EDIT_TEMPLATE: "Edit Template",
DELETE_TEMPLATE: "Delete Template",
UNLINK: "Unlink",
UNSELECT_ALL: "Unselect All"
} as const;

View File

@@ -15,6 +15,7 @@ export * from "./gateways";
export * from "./githubOrgSyncConfig";
export * from "./groups";
export * from "./identities";
export * from "./identityAuthTemplates";
export * from "./identityProjectAdditionalPrivilege";
export * from "./incidentContacts";
export * from "./integrationAuth";

View File

@@ -12,14 +12,15 @@ export const useCreateReminder = (secretId: string) => {
const queryClient = useQueryClient();
return useMutation<Reminder, object, CreateReminderDTO>({
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients }) => {
mutationFn: async ({ message, repeatDays, nextReminderDate, recipients, fromDate }) => {
const { data } = await apiRequest.post<{ reminder: Reminder }>(
`/api/v1/reminders/secrets/${secretId}`,
{
message,
repeatDays,
nextReminderDate,
recipients
recipients,
fromDate
}
);
return data.reminder;

View File

@@ -2,6 +2,7 @@ export type CreateReminderDTO = {
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: Date | null;
fromDate?: Date | null;
secretId: string;
recipients?: string[];
};

View File

@@ -53,4 +53,5 @@ export type SubscriptionPlan = {
secretScanning: boolean;
enterpriseSecretSyncs: boolean;
enterpriseAppConnections: boolean;
machineIdentityAuthTemplates: boolean;
};

View File

@@ -0,0 +1,317 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
FormControl,
Input,
Modal,
ModalContent,
Select,
SelectItem,
TextArea
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
MachineIdentityAuthMethod,
useCreateIdentityAuthTemplate,
useUpdateIdentityAuthTemplate
} from "@app/hooks/api/identityAuthTemplates";
import { UsePopUpState } from "@app/hooks/usePopUp";
const authMethods = [{ label: "LDAP Auth", value: MachineIdentityAuthMethod.LDAP }];
const schema = z.object({
name: z.string().min(1, "Template name is required"),
method: z.nativeEnum(MachineIdentityAuthMethod),
url: z.string().min(1, "LDAP URL is required"),
bindDN: z.string().min(1, "Bind DN is required"),
bindPass: z.string().min(1, "Bind Pass is required"),
searchBase: z.string().min(1, "Search Base / DN is required"),
ldapCaCertificate: z
.string()
.optional()
.transform((val) => val || undefined)
});
export type FormData = z.infer<typeof schema>;
type Props = {
popUp: UsePopUpState<["createTemplate", "editTemplate"]>;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["createTemplate", "editTemplate"]>,
state?: boolean
) => void;
};
export const IdentityAuthTemplateModal = ({ popUp, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
const orgId = currentOrg?.id || "";
const { mutateAsync: createTemplate } = useCreateIdentityAuthTemplate();
const { mutateAsync: updateTemplate } = useUpdateIdentityAuthTemplate();
const isEdit = popUp.editTemplate.isOpen;
const template = popUp.editTemplate?.data?.template;
const {
control,
handleSubmit,
reset,
watch,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
name: "",
method: MachineIdentityAuthMethod.LDAP,
url: "",
bindDN: "",
bindPass: "",
searchBase: "",
ldapCaCertificate: ""
}
});
useEffect(() => {
if (isEdit && template) {
reset({
name: template.name || "",
method: MachineIdentityAuthMethod.LDAP,
url: template.templateFields?.url || "",
bindDN: template.templateFields?.bindDN || "",
bindPass: template.templateFields?.bindPass || "",
searchBase: template.templateFields?.searchBase || "",
ldapCaCertificate: template.templateFields?.ldapCaCertificate || ""
});
} else {
reset({
name: "",
method: MachineIdentityAuthMethod.LDAP,
url: "",
bindDN: "",
bindPass: "",
searchBase: "",
ldapCaCertificate: ""
});
}
}, [isEdit, template, reset]);
const selectedMethod = watch("method");
const onFormSubmit = async (data: FormData) => {
try {
if (isEdit && template) {
await updateTemplate({
templateId: template.id,
organizationId: orgId,
name: data.name,
templateFields: {
url: data.url,
bindDN: data.bindDN,
bindPass: data.bindPass,
searchBase: data.searchBase,
ldapCaCertificate: data.ldapCaCertificate
}
});
createNotification({
text: "Successfully updated auth template",
type: "success"
});
} else {
await createTemplate({
organizationId: orgId,
name: data.name,
authMethod: data.method,
templateFields: {
url: data.url,
bindDN: data.bindDN,
bindPass: data.bindPass,
searchBase: data.searchBase,
ldapCaCertificate: data.ldapCaCertificate
}
});
createNotification({
text: "Successfully created auth template",
type: "success"
});
}
handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false);
reset();
} catch (err) {
console.error(err);
const error = err as any;
const text =
error?.response?.data?.message ?? `Failed to ${isEdit ? "update" : "create"} auth template`;
createNotification({
text,
type: "error"
});
}
};
const handleClose = () => {
handlePopUpToggle(isEdit ? "editTemplate" : "createTemplate", false);
reset();
};
return (
<Modal
isOpen={popUp.createTemplate.isOpen || popUp.editTemplate.isOpen}
onOpenChange={handleClose}
>
<ModalContent
title={isEdit ? "Edit Identity Auth Template" : "Create Identity Auth Template"}
subTitle={
isEdit ? "Update the authentication template" : "Create a new authentication template"
}
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
name="name"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Template Name"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="My Template" />
</FormControl>
)}
/>
<Controller
control={control}
name="method"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Authentication Method"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Select
{...field}
className="w-full"
position="popper"
placeholder="Select auth method..."
dropdownContainerClassName="max-w-none"
onValueChange={(value) => field.onChange(value)}
>
{authMethods.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
{/* LDAP Configuration Fields */}
{selectedMethod === "ldap" && (
<>
<Controller
control={control}
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
label="Bind DN"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
</FormControl>
)}
/>
<Controller
control={control}
name="bindPass"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Bind Pass"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="********" type="password" />
</FormControl>
)}
/>
<Controller
control={control}
name="searchBase"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Search Base / DN"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="ou=machines,dc=acme,dc=com" />
</FormControl>
)}
/>
<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>
)}
/>
</>
)}
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
{isEdit ? "Update Template" : "Create Template"}
</Button>
<Button colorSchema="secondary" variant="plain" onClick={handleClose}>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};

View File

@@ -0,0 +1,281 @@
import {
faArrowDown,
faArrowUp,
faEdit,
faEllipsisV,
faEye,
faMagnifyingGlass,
faServer,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyState,
IconButton,
Input,
Pagination,
Spinner,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { OrgPermissionSubjects, useOrganization } from "@app/context";
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, useResetPageHelper } from "@app/hooks";
import { OrderByDirection } from "@app/hooks/api/generic/types";
import {
TEMPLATE_UI_LABELS,
useGetIdentityAuthTemplates
} from "@app/hooks/api/identityAuthTemplates";
import { UsePopUpState } from "@app/hooks/usePopUp";
enum TemplatesOrderBy {
Name = "name",
AuthMethod = "authMethod"
}
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<
["deleteTemplate", "createTemplate", "editTemplate", "viewUsages"]
>,
data?: any
) => void;
};
export const IdentityAuthTemplatesTable = ({ handlePopUpOpen }: Props) => {
const { currentOrg } = useOrganization();
const {
offset,
limit,
orderBy,
setOrderBy,
orderDirection,
setOrderDirection,
search,
debouncedSearch,
setPage,
setSearch,
perPage,
page,
setPerPage
} = usePagination<TemplatesOrderBy>(TemplatesOrderBy.Name, {
initPerPage: getUserTablePreference("templatesTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("templatesTable", PreferenceKey.PerPage, newPerPage);
};
const organizationId = currentOrg?.id || "";
const { data, isPending, isFetching } = useGetIdentityAuthTemplates({
organizationId,
limit,
offset,
search: debouncedSearch
});
const { templates = [], totalCount = 0 } = data ?? {};
useResetPageHelper({
totalCount,
offset,
setPage
});
const handleSort = (column: TemplatesOrderBy) => {
if (column === orderBy) {
setOrderDirection((prev) =>
prev === OrderByDirection.ASC ? OrderByDirection.DESC : OrderByDirection.ASC
);
return;
}
setOrderBy(column);
setOrderDirection(OrderByDirection.ASC);
};
return (
<div>
<div className="mb-4 flex items-center space-x-2">
<Input
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search templates by name..."
/>
</div>
<TableContainer>
<Table>
<THead>
<Tr className="h-14">
<Th className="w-1/6">
<div className="flex items-center">
Name
<IconButton
variant="plain"
className={`ml-2 ${orderBy === TemplatesOrderBy.Name ? "" : "opacity-30"}`}
ariaLabel="sort"
onClick={() => handleSort(TemplatesOrderBy.Name)}
>
<FontAwesomeIcon
icon={
orderDirection === OrderByDirection.DESC &&
orderBy === TemplatesOrderBy.Name
? faArrowUp
: faArrowDown
}
/>
</IconButton>
</div>
</Th>
<Th className="w-1/6">
<div className="flex items-center">
Method
<IconButton
variant="plain"
className={`ml-2 ${orderBy === TemplatesOrderBy.AuthMethod ? "" : "opacity-30"}`}
ariaLabel="sort"
onClick={() => handleSort(TemplatesOrderBy.AuthMethod)}
>
<FontAwesomeIcon
icon={
orderDirection === OrderByDirection.DESC &&
orderBy === TemplatesOrderBy.AuthMethod
? faArrowUp
: faArrowDown
}
/>
</IconButton>
</div>
</Th>
<Th className="w-2/3">URL</Th>
<Th className="w-16">{isFetching ? <Spinner size="xs" /> : null}</Th>
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={4} innerKey="identity-auth-templates" />}
{!isPending &&
templates?.map((template) => (
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
key={`template-${template.id}`}
>
<Td>{template.name}</Td>
<Td>
<div className="flex items-center">
<span className="uppercase">{template.authMethod}</span>
</div>
</Td>
<Td>
<span className="text-sm text-mineshaft-400">
{template.templateFields.url}
</span>
</Td>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<IconButton
ariaLabel="Options"
className="w-6"
colorSchema="secondary"
variant="plain"
>
<FontAwesomeIcon icon={faEllipsisV} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent sideOffset={2} align="end">
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faEye} />}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("viewUsages", { template });
}}
>
{TEMPLATE_UI_LABELS.VIEW_USAGES}
</DropdownMenuItem>
<OrgPermissionCan
I={OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates}
a={OrgPermissionSubjects.MachineIdentityAuthTemplate}
>
{(isAllowed) => (
<DropdownMenuItem
icon={<FontAwesomeIcon icon={faEdit} />}
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("editTemplate", { template });
}}
isDisabled={!isAllowed}
>
{TEMPLATE_UI_LABELS.EDIT_TEMPLATE}
</DropdownMenuItem>
)}
</OrgPermissionCan>
<OrgPermissionCan
I={OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates}
a={OrgPermissionSubjects.MachineIdentityAuthTemplate}
>
{(isAllowed) => (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("deleteTemplate", {
templateId: template.id,
name: template.name
});
}}
isDisabled={!isAllowed}
icon={<FontAwesomeIcon icon={faTrash} />}
>
{TEMPLATE_UI_LABELS.DELETE_TEMPLATE}
</DropdownMenuItem>
)}
</OrgPermissionCan>
</DropdownMenuContent>
</DropdownMenu>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isPending && data && totalCount > 0 && (
<Pagination
count={totalCount}
page={page}
perPage={perPage}
onChangePage={(newPage) => setPage(newPage)}
onChangePerPage={handlePerPageChange}
/>
)}
{!isPending && data && templates.length === 0 && (
<EmptyState
title={
debouncedSearch.trim().length > 0
? "No templates match search filter"
: "No identity auth templates have been created"
}
icon={faServer}
/>
)}
</TableContainer>
</div>
);
};

View File

@@ -11,6 +11,8 @@ import {
FormControl,
IconButton,
Input,
Select,
SelectItem,
Tab,
TabList,
TabPanel,
@@ -18,23 +20,31 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { useOrganization, useSubscription } from "@app/context";
import { useOrganization, useOrgPermission, useSubscription } from "@app/context";
import {
OrgPermissionMachineIdentityAuthTemplateActions,
OrgPermissionSubjects
} from "@app/context/OrgPermissionContext/types";
import {
MachineIdentityAuthMethod,
useAddIdentityLdapAuth,
useGetIdentityLdapAuth,
useUpdateIdentityLdapAuth
} from "@app/hooks/api";
import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
import { useGetAvailableTemplates } from "@app/hooks/api/identityAuthTemplates/queries";
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(),
scope: z.enum(["template", "custom"]),
templateId: z.string().optional(),
url: z.string().optional(),
bindDN: z.string().optional(),
bindPass: z.string().optional(),
searchBase: z.string().optional(),
searchFilter: z.string(), // defaults to (uid={{username}})
ldapCaCertificate: z
.string()
@@ -66,7 +76,50 @@ const schema = z
)
.min(1)
})
.required();
.superRefine((data, ctx) => {
// Validation based on scope
if (data.scope === "template") {
if (!data.templateId) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Template is required when using template scope",
path: ["templateId"]
});
}
return;
}
if (data.scope === "custom") {
if (!data.url) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "LDAP URL is required when using custom scope",
path: ["url"]
});
}
if (!data.bindDN) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Bind DN is required when using custom scope",
path: ["bindDN"]
});
}
if (!data.bindPass) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Bind Pass is required when using custom scope",
path: ["bindPass"]
});
}
if (!data.searchBase) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Search Base is required when using custom scope",
path: ["searchBase"]
});
}
}
});
export type FormData = z.infer<typeof schema>;
@@ -93,6 +146,13 @@ export const IdentityLdapAuthForm = ({
const { mutateAsync: addMutateAsync } = useAddIdentityLdapAuth();
const { mutateAsync: updateMutateAsync } = useUpdateIdentityLdapAuth();
const [tabValue, setTabValue] = useState<IdentityFormTab>(IdentityFormTab.Configuration);
const { data: templates } = useGetAvailableTemplates(MachineIdentityAuthMethod.LDAP);
const { permission } = useOrgPermission();
const canAttachTemplates = permission.can(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate
);
const { data } = useGetIdentityLdapAuth(identityId ?? "", {
enabled: isUpdate
@@ -102,11 +162,14 @@ export const IdentityLdapAuthForm = ({
control,
handleSubmit,
reset,
watch,
setValue,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
scope: "custom",
templateId: "",
url: "",
bindDN: "",
bindPass: "",
@@ -119,6 +182,8 @@ export const IdentityLdapAuthForm = ({
}
});
const scope = watch("scope");
const {
fields: accessTokenTrustedIpsFields,
append: appendAccessTokenTrustedIp,
@@ -131,16 +196,30 @@ export const IdentityLdapAuthForm = ({
remove: removeAllowedField
} = useFieldArray({ control, name: "allowedFields" });
// Helper function to determine scope based on existing data
const determineScope = (authData: any) => {
// If templateId exists in the data, it's template scope
if (authData.templateId) {
return "template";
}
// Default to custom if we can't determine
return "custom";
};
useEffect(() => {
if (data) {
const detectedScope = determineScope(data);
reset({
url: data.url,
bindDN: data.bindDN,
bindPass: data.bindPass,
searchBase: data.searchBase,
scope: detectedScope,
templateId: data.templateId || "",
url: data.url || "",
bindDN: data.bindDN || "",
bindPass: data.bindPass || "",
searchBase: data.searchBase || "",
searchFilter: data.searchFilter,
ldapCaCertificate: data.ldapCaCertificate || undefined,
allowedFields: data.allowedFields,
allowedFields: data.allowedFields || [],
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
accessTokenNumUsesLimit: String(data.accessTokenNumUsesLimit),
@@ -152,78 +231,81 @@ export const IdentityLdapAuthForm = ({
}
)
});
} 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" }]
});
return;
}
}, [data]);
reset({
scope: "custom",
templateId: "",
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, reset]);
useEffect(() => {
if (!subscription?.ldap) {
handlePopUpOpen("upgradePlan");
handlePopUpToggle("identityAuthMethod", false);
}
}, [subscription]);
}, [subscription, handlePopUpOpen, handlePopUpToggle]);
const onFormSubmit = async ({
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
}: FormData) => {
const onFormSubmit = async (formData: FormData) => {
try {
if (!identityId) return;
const {
scope: submissionScope,
templateId: submissionTemplateId,
url: submissionUrl,
bindDN: submissionBindDN,
bindPass: submissionBindPass,
searchBase: submissionSearchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps
} = formData;
const basePayload = {
organizationId: orgId,
identityId,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
};
// Add scope-specific fields
const payload =
submissionScope === "template"
? { ...basePayload, templateId: submissionTemplateId }
: {
...basePayload,
url: submissionUrl,
bindDN: submissionBindDN,
bindPass: submissionBindPass,
searchBase: submissionSearchBase
};
if (data) {
await updateMutateAsync({
organizationId: orgId,
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
await updateMutateAsync(payload);
} else {
await addMutateAsync({
organizationId: orgId,
identityId,
url,
bindDN,
bindPass,
searchBase,
searchFilter,
ldapCaCertificate,
allowedFields,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
accessTokenNumUsesLimit: Number(accessTokenNumUsesLimit),
accessTokenTrustedIps
});
await addMutateAsync(payload);
}
handlePopUpToggle("identityAuthMethod", false);
@@ -247,6 +329,8 @@ export const IdentityLdapAuthForm = ({
onSubmit={handleSubmit(onFormSubmit, (fields) => {
setTabValue(
[
"scope",
"templateId",
"url",
"bindDN",
"bindPass",
@@ -268,18 +352,102 @@ export const IdentityLdapAuthForm = ({
<Tab value={IdentityFormTab.Advanced}>Advanced</Tab>
</TabList>
<TabPanel value={IdentityFormTab.Configuration}>
{canAttachTemplates && (
<Controller
control={control}
name="scope"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Configuration Type"
isError={Boolean(error)}
errorText={error?.message}
>
<Select
value={value}
onValueChange={(val) => {
onChange(val);
setValue("templateId", data?.templateId || "");
setValue("url", data?.url || "");
setValue("bindDN", data?.bindDN || "");
setValue("bindPass", data?.bindPass || "");
setValue("searchBase", data?.searchBase || "");
setValue("ldapCaCertificate", data?.ldapCaCertificate || "");
}}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value="template">Use Template</SelectItem>
<SelectItem value="custom">Custom Configuration</SelectItem>
</Select>
</FormControl>
)}
/>
)}
{scope === "template" && (
<Controller
control={control}
name="templateId"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Template"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Select
value={value}
onValueChange={(val) => {
onChange(val);
const tmp = templates?.find((t) => t.id === val);
if (!tmp) return;
setValue("url", tmp.templateFields.url);
setValue("bindDN", tmp.templateFields.bindDN);
setValue("bindPass", tmp.templateFields.bindPass);
setValue("searchBase", tmp.templateFields.searchBase);
setValue("ldapCaCertificate", tmp.templateFields.ldapCaCertificate);
}}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
placeholder="Select a template"
>
{templates?.map((template) => {
return (
<SelectItem value={template.id} key={template.id}>
{template.name}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue="2592000"
name="url"
render={({ field, fieldState: { error } }) => (
<FormControl
label="LDAP URL"
isError={Boolean(error)}
errorText={error?.message}
tooltipText={
scope === "template"
? "This field cannot be modified when using a template"
: undefined
}
isRequired
>
<Input {...field} placeholder="ldaps://domain-or-ip:636" type="text" />
<Input
{...field}
placeholder="ldaps://domain-or-ip:636"
type="text"
isDisabled={scope === "template"}
containerClassName={scope === "template" ? "opacity-55" : ""}
/>
</FormControl>
)}
/>
@@ -292,14 +460,23 @@ export const IdentityLdapAuthForm = ({
label="Bind DN"
isError={Boolean(error)}
errorText={error?.message}
tooltipText={
scope === "template"
? "This field cannot be modified when using a template"
: undefined
}
>
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
<Input
{...field}
containerClassName={scope === "template" ? "opacity-55" : ""}
placeholder="cn=infisical,ou=Users,dc=example,dc=com"
isDisabled={scope === "template"}
/>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="bindPass"
render={({ field, fieldState: { error } }) => (
<FormControl
@@ -307,8 +484,19 @@ export const IdentityLdapAuthForm = ({
label="Bind Pass"
isError={Boolean(error)}
errorText={error?.message}
tooltipText={
scope === "template"
? "This field cannot be modified when using a template"
: undefined
}
>
<Input {...field} placeholder="********" type="password" />
<Input
{...field}
placeholder="********"
type="password"
containerClassName={scope === "template" ? "opacity-55" : ""}
isDisabled={scope === "template"}
/>
</FormControl>
)}
/>
@@ -321,8 +509,18 @@ export const IdentityLdapAuthForm = ({
label="Search Base / DN"
isError={Boolean(error)}
errorText={error?.message}
tooltipText={
scope === "template"
? "This field cannot be modified when using a template"
: undefined
}
>
<Input {...field} placeholder="ou=machines,dc=acme,dc=com" />
<Input
{...field}
placeholder="ou=machines,dc=acme,dc=com"
containerClassName={scope === "template" ? "opacity-55" : ""}
isDisabled={scope === "template"}
/>
</FormControl>
)}
/>
@@ -452,7 +650,6 @@ export const IdentityLdapAuthForm = ({
<Controller
control={control}
defaultValue="2592000"
name="accessTokenTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
@@ -467,7 +664,6 @@ export const IdentityLdapAuthForm = ({
/>
<Controller
control={control}
defaultValue="2592000"
name="accessTokenMaxTTL"
render={({ field, fieldState: { error } }) => (
<FormControl
@@ -482,7 +678,6 @@ export const IdentityLdapAuthForm = ({
/>
<Controller
control={control}
defaultValue="0"
name="accessTokenNumUsesLimit"
render={({ field, fieldState: { error } }) => (
<FormControl
@@ -506,9 +701,18 @@ export const IdentityLdapAuthForm = ({
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."
tooltipText={
scope === "template"
? "This field cannot be modified when using a template"
: "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----- ..." />
<TextArea
{...field}
placeholder="-----BEGIN CERTIFICATE----- ..."
className={scope === "template" ? "opacity-55" : ""}
isDisabled={scope === "template"}
/>
</FormControl>
)}
/>

View File

@@ -11,15 +11,18 @@ import {
useOrganization,
useSubscription
} from "@app/context";
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
import { withPermission } from "@app/hoc";
import { useDeleteIdentity } from "@app/hooks/api";
import { useDeleteIdentityAuthTemplate } from "@app/hooks/api/identityAuthTemplates";
import { usePopUp } from "@app/hooks/usePopUp";
// import { IdentityAuthMethodModal } from "./IdentityAuthMethodModal";
import { IdentityAuthTemplateModal } from "./IdentityAuthTemplateModal";
import { IdentityAuthTemplatesTable } from "./IdentityAuthTemplatesTable";
import { IdentityModal } from "./IdentityModal";
import { IdentityTable } from "./IdentityTable";
import { IdentityTokenAuthTokenModal } from "./IdentityTokenAuthTokenModal";
// import { IdentityUniversalAuthClientSecretModal } from "./IdentityUniversalAuthClientSecretModal";
import { MachineAuthTemplateUsagesModal } from "./MachineAuthTemplateUsagesModal";
export const IdentitySection = withPermission(
() => {
@@ -28,6 +31,7 @@ export const IdentitySection = withPermission(
const orgId = currentOrg?.id || "";
const { mutateAsync: deleteMutateAsync } = useDeleteIdentity();
const { mutateAsync: deleteTemplateMutateAsync } = useDeleteIdentityAuthTemplate();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"identity",
"identityAuthMethod",
@@ -35,7 +39,11 @@ export const IdentitySection = withPermission(
"universalAuthClientSecret",
"deleteUniversalAuthClientSecret",
"upgradePlan",
"tokenAuthToken"
"tokenAuthToken",
"createTemplate",
"editTemplate",
"deleteTemplate",
"viewUsages"
] as const);
const isMoreIdentitiesAllowed = subscription?.identityLimit
@@ -69,53 +77,133 @@ export const IdentitySection = withPermission(
}
};
const onDeleteTemplateSubmit = async (templateId: string) => {
try {
await deleteTemplateMutateAsync({
templateId,
organizationId: orgId
});
createNotification({
text: "Successfully deleted template",
type: "success"
});
handlePopUpClose("deleteTemplate");
} catch (err) {
console.error(err);
const error = err as any;
const text = error?.response?.data?.message ?? "Failed to delete template";
createNotification({
text,
type: "error"
});
}
};
return (
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<a
href="https://infisical.com/docs/documentation/platform/identities/overview"
target="_blank"
rel="noopener noreferrer"
>
<div className="ml-1 mt-[0.16rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
</div>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Create}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", {
description: "You can add more identities if you upgrade your Infisical plan."
});
return;
}
handlePopUpOpen("identity");
}}
isDisabled={!isAllowed}
<div>
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<p className="text-xl font-semibold text-mineshaft-100">Identities</p>
<a
href="https://infisical.com/docs/documentation/platform/identities/overview"
target="_blank"
rel="noopener noreferrer"
>
Create Identity
</Button>
)}
</OrgPermissionCan>
<div className="ml-1 mt-[0.16rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
</div>
<OrgPermissionCan
I={OrgPermissionIdentityActions.Create}
a={OrgPermissionSubjects.Identity}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (!isMoreIdentitiesAllowed && !isEnterprise) {
handlePopUpOpen("upgradePlan", {
description:
"You can add more identities if you upgrade your Infisical plan."
});
return;
}
handlePopUpOpen("identity");
}}
isDisabled={!isAllowed}
>
Create Identity
</Button>
)}
</OrgPermissionCan>
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
</div>
{/* Identity Auth Templates Section */}
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<p className="text-xl font-semibold text-mineshaft-100">Identity Auth Templates</p>
<a
href="https://infisical.com/docs/documentation/platform/identities/auth-templates"
target="_blank"
rel="noopener noreferrer"
>
<div className="ml-1 mt-[0.16rem] inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-[10px]"
/>
</div>
</a>
</div>
<OrgPermissionCan
I={OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates}
a={OrgPermissionSubjects.MachineIdentityAuthTemplate}
>
{(isAllowed) => (
<Button
colorSchema="secondary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("createTemplate")}
isDisabled={!isAllowed}
>
Create Template
</Button>
)}
</OrgPermissionCan>
</div>
<IdentityAuthTemplatesTable handlePopUpOpen={handlePopUpOpen} />
</div>
<IdentityTable handlePopUpOpen={handlePopUpOpen} />
<IdentityModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<IdentityAuthTemplateModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
<MachineAuthTemplateUsagesModal
isOpen={popUp.viewUsages.isOpen}
onClose={() => handlePopUpClose("viewUsages")}
templateId={
(popUp?.viewUsages?.data as { template: { id: string; name: string } })?.template?.id ||
""
}
templateName={
(popUp?.viewUsages?.data as { template: { id: string; name: string } })?.template
?.name || ""
}
/>
{/* <IdentityAuthMethodModal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
@@ -140,6 +228,19 @@ export const IdentitySection = withPermission(
)
}
/>
<DeleteActionModal
isOpen={popUp.deleteTemplate.isOpen}
title={`Are you sure you want to delete ${
(popUp?.deleteTemplate?.data as { name: string })?.name || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("deleteTemplate", isOpen)}
deleteKey="confirm"
onDeleteApproved={() =>
onDeleteTemplateSubmit(
(popUp?.deleteTemplate?.data as { templateId: string })?.templateId
)
}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}

View File

@@ -0,0 +1,90 @@
import { faCertificate } from "@fortawesome/free-solid-svg-icons";
import { useNavigate } from "@tanstack/react-router";
import {
EmptyState,
Modal,
ModalContent,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { useGetTemplateUsages } from "@app/hooks/api/identityAuthTemplates";
type Props = {
isOpen: boolean;
onClose: () => void;
templateId: string;
templateName: string;
};
export const MachineAuthTemplateUsagesModal = ({
isOpen,
onClose,
templateId,
templateName
}: Props) => {
const { currentOrg } = useOrganization();
const navigate = useNavigate();
const organizationId = currentOrg?.id || "";
const { data: usages = [], isPending } = useGetTemplateUsages({
templateId,
organizationId
});
return (
<Modal isOpen={isOpen} onOpenChange={onClose}>
<ModalContent title={`Usages for Identity Auth Template: ${templateName}`}>
<div>
<TableContainer>
<Table>
<THead>
<Tr className="h-14">
<Th>Identity Name</Th>
<Th>Identity ID</Th>
</Tr>
</THead>
<TBody>
{isPending && <TableSkeleton columns={3} innerKey="template-usages" />}
{!isPending &&
usages.map((usage) => (
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
key={`usage-${usage.identityId}`}
onClick={() =>
navigate({
to: "/organization/identities/$identityId",
params: {
identityId: usage.identityId
}
})
}
>
<Td>{usage.identityName}</Td>
<Td>
<span className="text-sm text-mineshaft-400">{usage.identityId}</span>
</Td>
</Tr>
))}
</TBody>
</Table>
{!isPending && usages.length === 0 && (
<EmptyState
title="This template is not currently being used by any identities"
icon={faCertificate}
/>
)}
</TableContainer>
</div>
</ModalContent>
</Modal>
);
};

View File

@@ -9,6 +9,7 @@ import {
OrgPermissionGroupActions,
OrgPermissionIdentityActions,
OrgPermissionKmipActions,
OrgPermissionMachineIdentityAuthTemplateActions,
OrgPermissionSecretShareAction
} from "@app/context/OrgPermissionContext/types";
import { TPermission } from "@app/hooks/api/roles/types";
@@ -82,6 +83,17 @@ const orgGatewayPermissionSchema = z
})
.optional();
const machineIdentityAuthTemplatePermissionSchema = z
.object({
[OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates]: z.boolean().optional(),
[OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates]: z.boolean().optional(),
[OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates]: z.boolean().optional(),
[OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates]: z.boolean().optional(),
[OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates]: z.boolean().optional(),
[OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates]: z.boolean().optional()
})
.optional();
const adminConsolePermissionSchmea = z
.object({
"access-all-projects": z.boolean().optional()
@@ -129,6 +141,7 @@ export const formSchema = z.object({
"app-connections": appConnectionsPermissionSchema,
kmip: kmipPermissionSchema,
gateway: orgGatewayPermissionSchema,
"machine-identity-auth-template": machineIdentityAuthTemplatePermissionSchema,
"secret-share": secretSharingPermissionSchema
})
.optional()

View File

@@ -0,0 +1,211 @@
import { useEffect, useMemo } from "react";
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
import { faChevronDown, faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import { Checkbox, Select, SelectItem, Td, Tr } from "@app/components/v2";
import { OrgPermissionMachineIdentityAuthTemplateActions } from "@app/context/OrgPermissionContext/types";
import { useToggle } from "@app/hooks";
import { TFormSchema } from "../OrgRoleModifySection.utils";
type Props = {
isEditable: boolean;
setValue: UseFormSetValue<TFormSchema>;
control: Control<TFormSchema>;
};
enum Permission {
NoAccess = "no-access",
ReadOnly = "read-only",
FullAccess = "full-access",
Custom = "custom"
}
const PERMISSION_ACTIONS = [
{
action: OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
label: "List Templates"
},
{
action: OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
label: "Create Templates"
},
{
action: OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates,
label: "Edit Templates"
},
{
action: OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
label: "Delete Templates"
},
{
action: OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
label: "Unlink Templates"
},
{
action: OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
label: "Attach Templates"
}
] as const;
export const OrgPermissionMachineIdentityAuthTemplateRow = ({
isEditable,
control,
setValue
}: Props) => {
const [isRowExpanded, setIsRowExpanded] = useToggle();
const [isCustom, setIsCustom] = useToggle();
const rule = useWatch({
control,
name: "permissions.machine-identity-auth-template"
});
const selectedPermissionCategory = useMemo(() => {
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
const totalActions = PERMISSION_ACTIONS.length;
const score = actions.map((key) => (rule?.[key] ? 1 : 0)).reduce((a, b) => a + b, 0 as number);
if (isCustom) return Permission.Custom;
if (score === 0) return Permission.NoAccess;
if (score === totalActions) return Permission.FullAccess;
if (score === 1 && rule?.[OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates])
return Permission.ReadOnly;
return Permission.Custom;
}, [rule, isCustom]);
useEffect(() => {
if (selectedPermissionCategory === Permission.Custom) setIsCustom.on();
else setIsCustom.off();
}, [selectedPermissionCategory]);
useEffect(() => {
const isRowCustom = selectedPermissionCategory === Permission.Custom;
if (isRowCustom) {
setIsRowExpanded.on();
}
}, []);
const handlePermissionChange = (val: Permission) => {
if (!val) return;
if (val === Permission.Custom) {
setIsRowExpanded.on();
setIsCustom.on();
return;
}
setIsCustom.off();
switch (val) {
case Permission.FullAccess:
setValue(
"permissions.machine-identity-auth-template",
{
[OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates]: true
},
{ shouldDirty: true }
);
break;
case Permission.ReadOnly:
setValue(
"permissions.machine-identity-auth-template",
{
[OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates]: true,
[OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates]: true
},
{ shouldDirty: true }
);
break;
case Permission.NoAccess:
default:
setValue(
"permissions.machine-identity-auth-template",
{
[OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates]: false,
[OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates]: false
},
{ shouldDirty: true }
);
}
};
return (
<>
<Tr
className="h-10 cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
onClick={() => setIsRowExpanded.toggle()}
>
<Td className="w-4">
<FontAwesomeIcon className="w-4" icon={isRowExpanded ? faChevronDown : faChevronRight} />
</Td>
<Td className="w-full select-none">Machine Identity Auth Templates</Td>
<Td>
<Select
value={selectedPermissionCategory}
className="h-8 w-40 bg-mineshaft-700"
dropdownContainerClassName="border text-left border-mineshaft-600 bg-mineshaft-800"
onValueChange={handlePermissionChange}
isDisabled={!isEditable}
position="popper"
>
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
<SelectItem value={Permission.ReadOnly}>Read Only</SelectItem>
<SelectItem value={Permission.FullAccess}>Full Access</SelectItem>
<SelectItem value={Permission.Custom}>Custom</SelectItem>
</Select>
</Td>
</Tr>
{isRowExpanded && (
<Tr>
<Td colSpan={3} className="border-mineshaft-500 bg-mineshaft-900 p-8">
<div className="flex flex-grow flex-wrap justify-start gap-x-8 gap-y-4">
{PERMISSION_ACTIONS.map(({ action, label }) => {
return (
<Controller
name={`permissions.machine-identity-auth-template.${action}`}
key={`permissions.machine-identity-auth-template.${action}`}
control={control}
render={({ field }) => (
<Checkbox
isChecked={Boolean(field.value)}
onCheckedChange={(e) => {
if (!isEditable) {
createNotification({
type: "error",
text: "Failed to update default role"
});
return;
}
field.onChange(e);
}}
id={`permissions.machine-identity-auth-template.${action}`}
>
{label}
</Checkbox>
)}
/>
);
})}
</div>
</Td>
</Tr>
)}
</>
);
};

View File

@@ -65,7 +65,13 @@ type Props = {
title: string;
formName: keyof Omit<
Exclude<TFormSchema["permissions"], undefined>,
"workspace" | "organization-admin-console" | "kmip" | "gateway" | "secret-share" | "billing"
| "workspace"
| "organization-admin-console"
| "kmip"
| "gateway"
| "secret-share"
| "billing"
| "machine-identity-auth-template"
>;
setValue: UseFormSetValue<TFormSchema>;
control: Control<TFormSchema>;

View File

@@ -21,6 +21,7 @@ import { OrgGatewayPermissionRow } from "./OrgPermissionGatewayRow";
import { OrgPermissionGroupRow } from "./OrgPermissionGroupRow";
import { OrgPermissionIdentityRow } from "./OrgPermissionIdentityRow";
import { OrgPermissionKmipRow } from "./OrgPermissionKmipRow";
import { OrgPermissionMachineIdentityAuthTemplateRow } from "./OrgPermissionMachineIdentityAuthTemplateRow";
import { OrgPermissionSecretShareRow } from "./OrgPermissionSecretShareRow";
import { OrgRoleWorkspaceRow } from "./OrgRoleWorkspaceRow";
import { RolePermissionRow } from "./RolePermissionRow";
@@ -205,6 +206,11 @@ export const RolePermissionsSection = ({ roleId }: Props) => {
setValue={setValue}
isEditable={isCustomRole}
/>
<OrgPermissionMachineIdentityAuthTemplateRow
control={control}
setValue={setValue}
isEditable={isCustomRole}
/>
<OrgPermissionKmipRow
control={control}
setValue={setValue}

View File

@@ -52,8 +52,15 @@ export const SecretSearchInput = ({
if (activeIndex === 0 && e.key === "Enter") setIsOpen(true);
}}
autoComplete="off"
className="input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 pr-8 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50"
placeholder="Search by secret, folder, tag or metadata..."
className={twMerge(
"input text-md h-[2.3rem] w-full rounded-md rounded-l-none bg-mineshaft-800 py-[0.375rem] pl-2.5 text-gray-400 placeholder-mineshaft-50 placeholder-opacity-50 outline-none duration-200 placeholder:text-sm hover:ring-bunker-400/60 focus:bg-mineshaft-700/80 focus:ring-1 focus:ring-primary-400/50",
hasSearch ? "pr-8" : "pr-2.5"
)}
placeholder={
isSingleEnv
? "Search by secret, folder, tag or metadata..."
: "Search by secret or folder name..."
}
value={value}
onChange={(e) => onChange(e.target.value)}
/>

Some files were not shown because too many files have changed in this diff Show More