Compare commits
41 Commits
add-react-
...
infisical/
Author | SHA1 | Date | |
---|---|---|---|
|
dd0f5cebd2 | ||
|
1b29a4564a | ||
|
9e3c0c8583 | ||
|
16ebe0f8e7 | ||
|
e8eb1b5f8b | ||
|
6e37b9f969 | ||
|
899b7fe024 | ||
|
098a8b81be | ||
|
e852cd8b4a | ||
|
830a2f9581 | ||
|
dc4db40936 | ||
|
0beff3cc1c | ||
|
5a3325fc53 | ||
|
3dde786621 | ||
|
da6b233db1 | ||
|
6958f1cfbd | ||
|
adf7a88d67 | ||
|
b8cd836225 | ||
|
6826b1c242 | ||
|
35012fde03 | ||
|
6e14b2f793 | ||
|
5a3aa3d608 | ||
|
95b327de50 | ||
|
a3c36f82f3 | ||
|
42612da57d | ||
|
98a08d136e | ||
|
6c74b875f3 | ||
|
793cd4c144 | ||
|
dc0cc4c29d | ||
|
6dd639be60 | ||
|
ebe05661d3 | ||
|
4f0007faa5 | ||
|
ec0be1166f | ||
|
899d01237c | ||
|
4b9e57ae61 | ||
|
eb27983990 | ||
|
fa311b032c | ||
|
71651f85fe | ||
|
d28d3449de | ||
|
4f26365c21 | ||
|
4704774c63 |
@@ -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"
|
||||
);
|
||||
|
||||
|
2
backend/src/@types/fastify.d.ts
vendored
@@ -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
|
||||
|
10
backend/src/@types/knex.d.ts
vendored
@@ -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,
|
||||
|
@@ -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");
|
||||
});
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
24
backend/src/db/schemas/identity-auth-templates.ts
Normal 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>>;
|
@@ -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>;
|
||||
|
@@ -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",
|
||||
|
@@ -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>;
|
||||
|
391
backend/src/ee/routes/v1/identity-template-router.ts
Normal 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;
|
||||
}
|
||||
});
|
||||
};
|
@@ -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" });
|
||||
|
||||
|
@@ -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) => {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
};
|
||||
};
|
@@ -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;
|
@@ -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
|
||||
};
|
||||
};
|
@@ -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;
|
6
backend/src/ee/services/identity-auth-template/index.ts
Normal 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";
|
@@ -31,7 +31,8 @@ export const getDefaultOnPremFeatures = () => {
|
||||
caCrl: false,
|
||||
sshHostGroups: false,
|
||||
enterpriseSecretSyncs: false,
|
||||
enterpriseAppConnections: false
|
||||
enterpriseAppConnections: false,
|
||||
machineIdentityAuthTemplates: false
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -60,7 +60,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
enterpriseSecretSyncs: false,
|
||||
enterpriseAppConnections: false,
|
||||
fips: false,
|
||||
eventSubscriptions: false
|
||||
eventSubscriptions: false,
|
||||
machineIdentityAuthTemplates: false
|
||||
});
|
||||
|
||||
export const setupLicenseRequestWithStore = (
|
||||
|
@@ -75,6 +75,7 @@ export type TFeatureSet = {
|
||||
secretScanning: false;
|
||||
enterpriseSecretSyncs: false;
|
||||
enterpriseAppConnections: false;
|
||||
machineIdentityAuthTemplates: false;
|
||||
fips: false;
|
||||
eventSubscriptions: false;
|
||||
};
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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."
|
||||
|
@@ -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,
|
||||
|
@@ -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()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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" });
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
);
|
||||
|
@@ -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;
|
||||
|
662
docs/docs.json
@@ -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",
|
||||
|
40
docs/documentation/getting-started/concepts/audit-logs.mdx
Normal 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
|
@@ -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 they’re needed—whether that’s a developer’s 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.
|
@@ -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>
|
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
29
docs/documentation/getting-started/concepts/platform-iam.mdx
Normal 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).
|
@@ -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>
|
@@ -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.
|
||||
|
77
docs/documentation/getting-started/overview.mdx
Normal 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 Infisical’s command-line interface for managing secrets,
|
||||
certificates, and system operations via terminal.
|
||||
</Card>
|
||||
<Card
|
||||
title="API Reference"
|
||||
href="/api-reference/overview/introduction"
|
||||
>
|
||||
Browse Infisical’s 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>
|
96
docs/documentation/platform/identities/auth-templates.mdx
Normal 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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
- **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.
|
||||
|
||||

|
||||

|
||||
|
||||
### Managing Template Usage
|
||||
|
||||
You can view which identities are using a specific template by clicking **View Usages** in the template's dropdown menu.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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>
|
@@ -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">
|
||||
|
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
## 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.
|
||||
|
@@ -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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
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:
|
||||

|
||||
|
||||
- **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
|
||||
|
||||

|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
<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-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.
|
||||

|
||||
|
||||
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>
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||

|
||||
|
||||

|
||||
## 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).
|
||||
|
||||

|
||||
|
||||
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 organization’s 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.
|
||||
|
||||

|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
||||

|
||||
Infisical supports project types, each representing a different security product with its own dashboard, workflows, and capabilities.
|
||||
|
||||
## 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.
|
||||
The supported project types are:
|
||||
|
||||

|
||||
- [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 access is strictly scoped: only members of a project can view or manage its resources. If someone needs access but isn’t 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).
|
||||
|
||||

|
||||

|
||||
|
||||
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).
|
||||
|
||||

|
||||
## 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.
|
||||
|
||||

|
||||
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
|
||||
|
||||

|
||||
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.
|
||||
|
||||

|
||||
|
||||
To assist you with finding secrets, you can also group them by similar prefixes and filter them by tags (if applicable).
|
||||
|
||||

|
||||
|
||||
### Hide/Un-hide
|
||||
|
||||
To view/hide all secrets at once, toggle the hide or un-hide button.
|
||||
|
||||

|
||||
|
||||
### Download as .env
|
||||
|
||||
To download/export secrets back into a `.env` file, press the download button.
|
||||
|
||||

|
||||
|
||||
### Tags
|
||||
|
||||
To better organize similar secrets, hover over them and label them with a tag.
|
||||
|
||||

|
||||
|
||||
### Comments
|
||||
|
||||
To provide more context about a given secret, especially for your team, hover over it and press the comment button.
|
||||
|
||||

|
||||
|
||||
### 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>
|
||||
|
||||

|
||||
|
||||
### Drawer
|
||||
|
||||
To view the full details of each secret, you can hover over it and press on the ellipses button.
|
||||
|
||||

|
||||
|
||||
This opens up a side-drawer:
|
||||
|
||||

|
||||
Project settings are fully independent and reflect the capabilities of the associated product.
|
||||
|
17
docs/documentation/platform/secrets-mgmt/overview.mdx
Normal 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.
|
115
docs/documentation/platform/secrets-mgmt/project.mdx
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## Secrets Dashboard
|
||||
|
||||
The **Secrets Dashboard** page appears when you press to manage the secrets of a specific environment.
|
||||
|
||||

|
||||
|
||||
### Secrets
|
||||
|
||||
To add a secret, press **Add Secret** button at the top of the dashboard.
|
||||
|
||||

|
||||
|
||||
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 delete a secret, hover over it and press the **X** button that appears on the right side.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### Search
|
||||
|
||||
To search for specific secrets by their key name, you can use the search bar.
|
||||
|
||||

|
||||
|
||||
To assist you with finding secrets, you can also group them by similar prefixes and filter them by tags (if applicable).
|
||||
|
||||

|
||||
|
||||
### Hide/Un-hide
|
||||
|
||||
To view/hide all secrets at once, toggle the hide or un-hide button.
|
||||
|
||||

|
||||
|
||||
### Download as .env
|
||||
|
||||
To download/export secrets back into a `.env` file, press the download button.
|
||||
|
||||

|
||||
|
||||
### Tags
|
||||
|
||||
To better organize similar secrets, hover over them and label them with a tag.
|
||||
|
||||

|
||||
|
||||
### Comments
|
||||
|
||||
To provide more context about a given secret, especially for your team, hover over it and press the comment button.
|
||||
|
||||

|
||||
|
||||
### 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>
|
||||
|
||||

|
||||
|
||||
### Drawer
|
||||
|
||||
To view the full details of each secret, you can hover over it and press on the ellipses button.
|
||||
|
||||

|
||||
|
||||
This opens up a side-drawer:
|
||||
|
||||

|
@@ -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."
|
||||
---
|
||||
|
@@ -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."
|
||||
---
|
||||
|
After Width: | Height: | Size: 491 KiB |
After Width: | Height: | Size: 680 KiB |
BIN
docs/images/platform/identities/auth-templates/ldap-template.png
Normal file
After Width: | Height: | Size: 487 KiB |
After Width: | Height: | Size: 660 KiB |
After Width: | Height: | Size: 192 KiB |
After Width: | Height: | Size: 688 KiB |
After Width: | Height: | Size: 680 KiB |
After Width: | Height: | Size: 541 KiB |
BIN
docs/images/platform/organization/organization-billing.png
Normal file
After Width: | Height: | Size: 608 KiB |
Before Width: | Height: | Size: 484 KiB After Width: | Height: | Size: 513 KiB |
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 692 KiB |
BIN
docs/images/platform/organization/organization-roles.png
Normal file
After Width: | Height: | Size: 521 KiB |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 993 KiB |
BIN
docs/images/platform/organization/organization-settings.png
Normal file
After Width: | Height: | Size: 725 KiB |
Before Width: | Height: | Size: 1.4 MiB |
BIN
docs/images/platform/organization/organization-users.png
Normal file
After Width: | Height: | Size: 522 KiB |
BIN
docs/images/platform/organization/organization.png
Normal file
After Width: | Height: | Size: 691 KiB |
BIN
docs/images/platform/project/project-roles.png
Normal file
After Width: | Height: | Size: 624 KiB |
BIN
docs/images/platform/project/project-types.png
Normal file
After Width: | Height: | Size: 468 KiB |
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
```
|
||||
```
|
||||
|
@@ -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>
|
||||
|
@@ -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 |
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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?: {
|
||||
|
3
frontend/src/hooks/api/identityAuthTemplates/index.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./mutations";
|
||||
export * from "./queries";
|
||||
export * from "./types";
|
97
frontend/src/hooks/api/identityAuthTemplates/mutations.tsx
Normal 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 })
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
89
frontend/src/hooks/api/identityAuthTemplates/queries.tsx
Normal 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)
|
||||
});
|
||||
};
|
78
frontend/src/hooks/api/identityAuthTemplates/types.ts
Normal 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;
|
@@ -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";
|
||||
|
@@ -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;
|
||||
|
@@ -2,6 +2,7 @@ export type CreateReminderDTO = {
|
||||
message?: string | null;
|
||||
repeatDays?: number | null;
|
||||
nextReminderDate?: Date | null;
|
||||
fromDate?: Date | null;
|
||||
secretId: string;
|
||||
recipients?: string[];
|
||||
};
|
||||
|
@@ -53,4 +53,5 @@ export type SubscriptionPlan = {
|
||||
secretScanning: boolean;
|
||||
enterpriseSecretSyncs: boolean;
|
||||
enterpriseAppConnections: boolean;
|
||||
machineIdentityAuthTemplates: boolean;
|
||||
};
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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>
|
||||
)}
|
||||
/>
|
||||
|
@@ -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)}
|
||||
|
@@ -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>
|
||||
);
|
||||
};
|
@@ -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()
|
||||
|
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@@ -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>;
|
||||
|
@@ -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}
|
||||
|
@@ -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)}
|
||||
/>
|
||||
|