From f0edcd1f0f1d52f386df9929be95fe2657e885cd Mon Sep 17 00:00:00 2001 From: x032205 Date: Thu, 16 Oct 2025 18:12:03 -0400 Subject: [PATCH 001/224] feat(pam): account credential rotation --- .../20251015042917_pam-account-rotation.ts | 43 +++++++ backend/src/db/schemas/pam-accounts.ts | 5 +- backend/src/db/schemas/pam-resources.ts | 4 +- .../pam-account-endpoints.ts | 12 +- .../pam-resource-endpoints.ts | 2 + .../ee/services/audit-log/audit-log-types.ts | 16 +++ .../services/pam-account/pam-account-dal.ts | 16 ++- .../pam-account/pam-account-service.ts | 114 +++++++++++++++++- .../services/pam-account/pam-account-types.ts | 5 +- .../services/pam-resource/pam-resource-fns.ts | 10 +- .../pam-resource/pam-resource-schemas.ts | 9 +- .../pam-resource/pam-resource-service.ts | 60 ++++++++- .../pam-resource/pam-resource-types.ts | 7 +- .../postgres/postgres-resource-schemas.ts | 14 ++- .../shared/sql/sql-resource-factory.ts | 67 +++++++++- backend/src/queue/queue-service.ts | 10 +- backend/src/server/routes/index.ts | 10 +- .../pam-account-rotation-queue.ts | 61 ++++++++++ .../src/hooks/api/pam/types/base-account.ts | 3 + .../hooks/api/pam/types/postgres-resource.ts | 1 + .../PamAccountForm/PamAccountForm.tsx | 10 +- .../PamAccountForm/PostgresAccountForm.tsx | 4 +- .../PamAccountForm/RotateAccountFields.tsx | 71 +++++++++++ .../PamResourceForm/PostgresResourceForm.tsx | 6 +- .../shared/SqlRotateAccountFields.tsx | 64 ++++++++++ 25 files changed, 588 insertions(+), 36 deletions(-) create mode 100644 backend/src/db/migrations/20251015042917_pam-account-rotation.ts create mode 100644 backend/src/services/pam-account-rotation/pam-account-rotation-queue.ts create mode 100644 frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx create mode 100644 frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx diff --git a/backend/src/db/migrations/20251015042917_pam-account-rotation.ts b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts new file mode 100644 index 000000000..cd9a7d19a --- /dev/null +++ b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts @@ -0,0 +1,43 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +export async function up(knex: Knex): Promise { + if ( + !(await knex.schema.hasColumn(TableName.PamAccount, "rotationEnabled")) && + !(await knex.schema.hasColumn(TableName.PamAccount, "rotationIntervalSeconds")) && + !(await knex.schema.hasColumn(TableName.PamAccount, "lastRotatedAt")) + ) { + await knex.schema.alterTable(TableName.PamAccount, (t) => { + t.boolean("rotationEnabled").notNullable().defaultTo(false); + t.integer("rotationIntervalSeconds").notNullable().defaultTo(2592000); + t.timestamp("lastRotatedAt").nullable(); + }); + } + + if (!(await knex.schema.hasColumn(TableName.PamResource, "encryptedRotationAccountCredentials"))) { + await knex.schema.alterTable(TableName.PamResource, (t) => { + t.binary("encryptedRotationAccountCredentials").nullable(); + }); + } +} + +export async function down(knex: Knex): Promise { + if (await knex.schema.hasColumn(TableName.PamResource, "encryptedRotationAccountCredentials")) { + await knex.schema.alterTable(TableName.PamResource, (t) => { + t.dropColumn("encryptedRotationAccountCredentials"); + }); + } + + if ( + (await knex.schema.hasColumn(TableName.PamAccount, "rotationEnabled")) && + (await knex.schema.hasColumn(TableName.PamAccount, "rotationIntervalSeconds")) && + (await knex.schema.hasColumn(TableName.PamAccount, "lastRotatedAt")) + ) { + await knex.schema.alterTable(TableName.PamAccount, (t) => { + t.dropColumn("lastRotatedAt"); + t.dropColumn("rotationIntervalSeconds"); + t.dropColumn("rotationEnabled"); + }); + } +} diff --git a/backend/src/db/schemas/pam-accounts.ts b/backend/src/db/schemas/pam-accounts.ts index 5a9a45617..9bcfa5cec 100644 --- a/backend/src/db/schemas/pam-accounts.ts +++ b/backend/src/db/schemas/pam-accounts.ts @@ -18,7 +18,10 @@ export const PamAccountsSchema = z.object({ description: z.string().nullable().optional(), encryptedCredentials: zodBuffer, createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + rotationEnabled: z.boolean().default(false), + rotationIntervalSeconds: z.number().default(2592000), + lastRotatedAt: z.date().nullable().optional() }); export type TPamAccounts = z.infer; diff --git a/backend/src/db/schemas/pam-resources.ts b/backend/src/db/schemas/pam-resources.ts index d34017d0f..929a31c79 100644 --- a/backend/src/db/schemas/pam-resources.ts +++ b/backend/src/db/schemas/pam-resources.ts @@ -17,7 +17,9 @@ export const PamResourcesSchema = z.object({ resourceType: z.string(), encryptedConnectionDetails: zodBuffer, createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedRotationAccountDetails: zodBuffer.nullable().optional(), + encryptedRotationAccountCredentials: zodBuffer.nullable().optional() }); export type TPamResources = z.infer; diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts index 0ed7e238a..dccaaa8ca 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts @@ -22,11 +22,15 @@ export const registerPamResourceEndpoints = ({ folderId?: C["folderId"]; name: C["name"]; description?: C["description"]; + rotationEnabled: C["rotationEnabled"]; + rotationIntervalSeconds: C["rotationIntervalSeconds"]; }>; updateAccountSchema: z.ZodType<{ credentials?: C["credentials"]; name?: C["name"]; description?: C["description"]; + rotationEnabled?: C["rotationEnabled"]; + rotationIntervalSeconds?: C["rotationIntervalSeconds"]; }>; accountResponseSchema: z.ZodTypeAny; }) => { @@ -60,7 +64,9 @@ export const registerPamResourceEndpoints = ({ resourceType, folderId: req.body.folderId, name: req.body.name, - description: req.body.description + description: req.body.description, + rotationEnabled: req.body.rotationEnabled, + rotationIntervalSeconds: req.body.rotationIntervalSeconds } } }); @@ -108,7 +114,9 @@ export const registerPamResourceEndpoints = ({ resourceId: account.resourceId, resourceType, name: req.body.name, - description: req.body.description + description: req.body.description, + rotationEnabled: req.body.rotationEnabled, + rotationIntervalSeconds: req.body.rotationIntervalSeconds } } }); diff --git a/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-endpoints.ts b/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-endpoints.ts index 776de8e48..ffbeae5c0 100644 --- a/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-endpoints.ts +++ b/backend/src/ee/routes/v1/pam-resource-routers/pam-resource-endpoints.ts @@ -21,11 +21,13 @@ export const registerPamResourceEndpoints = ({ connectionDetails: T["connectionDetails"]; gatewayId: T["gatewayId"]; name: T["name"]; + rotationAccountCredentials?: T["rotationAccountCredentials"]; }>; updateResourceSchema: z.ZodType<{ connectionDetails?: T["connectionDetails"]; gatewayId?: T["gatewayId"]; name?: T["name"]; + rotationAccountCredentials?: T["rotationAccountCredentials"]; }>; resourceResponseSchema: z.ZodTypeAny; }) => { diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index bc50283d4..ff2dafa9f 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -515,6 +515,7 @@ export enum EventType { PAM_ACCOUNT_CREATE = "pam-account-create", PAM_ACCOUNT_UPDATE = "pam-account-update", PAM_ACCOUNT_DELETE = "pam-account-delete", + PAM_ACCOUNT_CREDENTIAL_ROTATION = "pam-account-credential-rotation", PAM_RESOURCE_LIST = "pam-resource-list", PAM_RESOURCE_GET = "pam-resource-get", PAM_RESOURCE_CREATE = "pam-resource-create", @@ -3795,6 +3796,8 @@ interface PamAccountCreateEvent { folderId?: string | null; name: string; description?: string | null; + rotationEnabled: boolean; + rotationIntervalSeconds: number; }; } @@ -3806,6 +3809,8 @@ interface PamAccountUpdateEvent { resourceType: string; name?: string; description?: string | null; + rotationEnabled?: boolean; + rotationIntervalSeconds?: number; }; } @@ -3819,6 +3824,16 @@ interface PamAccountDeleteEvent { }; } +interface PamAccountCredentialRotationEvent { + type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION; + metadata: { + accountName: string; + accountId: string; + resourceId: string; + resourceType: string; + }; +} + interface PamResourceListEvent { type: EventType.PAM_RESOURCE_LIST; metadata: { @@ -4209,6 +4224,7 @@ export type Event = | PamAccountCreateEvent | PamAccountUpdateEvent | PamAccountDeleteEvent + | PamAccountCredentialRotationEvent | PamResourceListEvent | PamResourceGetEvent | PamResourceCreateEvent diff --git a/backend/src/ee/services/pam-account/pam-account-dal.ts b/backend/src/ee/services/pam-account/pam-account-dal.ts index b62e940fe..c422c2b1d 100644 --- a/backend/src/ee/services/pam-account/pam-account-dal.ts +++ b/backend/src/ee/services/pam-account/pam-account-dal.ts @@ -39,5 +39,19 @@ export const pamAccountDALFactory = (db: TDbClient) => { })); }; - return { ...orm, findWithResourceDetails }; + const findAccountsDueForRotation = async (tx?: Knex) => { + const dbClient = tx || db.replicaNode(); + + const accounts = await dbClient(TableName.PamAccount) + .innerJoin(TableName.PamResource, `${TableName.PamAccount}.resourceId`, `${TableName.PamResource}.id`) + .whereNotNull(`${TableName.PamResource}.encryptedRotationAccountCredentials`) + .whereRaw( + `COALESCE("${TableName.PamAccount}"."lastRotatedAt", "${TableName.PamAccount}"."createdAt") + "${TableName.PamAccount}"."rotationIntervalSeconds" * interval '1 second' < NOW()` + ) + .select(selectAllTableCols(TableName.PamAccount)); + + return accounts; + }; + + return { ...orm, findWithResourceDetails, findAccountsDueForRotation }; }; diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index e9ea76e8c..6146c1a13 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -11,12 +11,14 @@ import { } from "@app/ee/services/permission/project-permission"; import { DatabaseErrorCode } from "@app/lib/error-codes"; import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { logger } from "@app/lib/logger"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType } from "@app/services/auth/auth-type"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; +import { EventType, TAuditLogServiceFactory } from "../audit-log/audit-log-types"; import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "../license/license-service"; import { TPamFolderDALFactory } from "../pam-folder/pam-folder-dal"; @@ -45,10 +47,12 @@ type TPamAccountServiceFactoryDep = { "getPAMConnectionDetails" | "getPlatformConnectionDetailsByGatewayId" >; userDAL: TUserDALFactory; + auditLogService: Pick; }; - export type TPamAccountServiceFactory = ReturnType; +const ROTATION_CONCURRENCY_LIMIT = 10; + export const pamAccountServiceFactory = ({ pamResourceDAL, pamSessionDAL, @@ -59,10 +63,19 @@ export const pamAccountServiceFactory = ({ permissionService, licenseService, kmsService, - gatewayV2Service + gatewayV2Service, + auditLogService }: TPamAccountServiceFactoryDep) => { const create = async ( - { credentials, resourceId, name, description, folderId }: TCreateAccountDTO, + { + credentials, + resourceId, + name, + description, + folderId, + rotationEnabled, + rotationIntervalSeconds + }: TCreateAccountDTO, actor: OrgServiceActor ) => { const orgLicensePlan = await licenseService.getPlan(actor.orgId); @@ -84,6 +97,10 @@ export const pamAccountServiceFactory = ({ actionProjectType: ActionProjectType.PAM }); + if (!resource.encryptedRotationAccountCredentials && rotationEnabled) { + throw new NotFoundError({ message: "Rotation credentials are not configured for this account's resource" }); + } + const accountPath = await getFullPamFolderPath({ pamFolderDAL, folderId, @@ -126,7 +143,9 @@ export const pamAccountServiceFactory = ({ encryptedCredentials, name, description, - folderId + folderId, + rotationEnabled, + rotationIntervalSeconds }); return { @@ -145,7 +164,7 @@ export const pamAccountServiceFactory = ({ }; const updateById = async ( - { accountId, credentials, description, name }: TUpdateAccountDTO, + { accountId, credentials, description, name, rotationEnabled, rotationIntervalSeconds }: TUpdateAccountDTO, actor: OrgServiceActor ) => { const orgLicensePlan = await licenseService.getPlan(actor.orgId); @@ -195,6 +214,17 @@ export const pamAccountServiceFactory = ({ updateDoc.description = description; } + if (rotationEnabled !== undefined) { + if (!resource.encryptedRotationAccountCredentials && rotationEnabled) { + throw new NotFoundError({ message: "Rotation credentials are not configured for this account's resource" }); + } + updateDoc.rotationEnabled = rotationEnabled; + } + + if (rotationIntervalSeconds !== undefined) { + updateDoc.rotationIntervalSeconds = rotationIntervalSeconds; + } + if (credentials !== undefined) { const connectionDetails = await decryptResourceConnectionDetails({ projectId: account.projectId, @@ -516,12 +546,84 @@ export const pamAccountServiceFactory = ({ }; }; + const rotateAllDueAccounts = async () => { + const accounts = await pamAccountDAL.findAccountsDueForRotation(); + + for (let i = 0; i < accounts.length; i += ROTATION_CONCURRENCY_LIMIT) { + const batch = accounts.slice(i, i + ROTATION_CONCURRENCY_LIMIT); + + const rotationPromises = batch.map(async (account) => { + try { + const resource = await pamResourceDAL.findById(account.resourceId); + if (!resource || !resource.encryptedRotationAccountCredentials) return; + + const { connectionDetails, rotationAccountCredentials, gatewayId, resourceType } = await decryptResource( + resource, + account.projectId, + kmsService + ); + + if (!rotationAccountCredentials) return; + + const accountCredentials = await decryptAccountCredentials({ + encryptedCredentials: account.encryptedCredentials, + projectId: account.projectId, + kmsService + }); + + const factory = PAM_RESOURCE_FACTORY_MAP[resourceType as PamResource]( + resourceType as PamResource, + connectionDetails, + gatewayId, + gatewayV2Service + ); + + const newCredentials = await factory.rotateAccountCredentials(rotationAccountCredentials, accountCredentials); + + const encryptedCredentials = await encryptAccountCredentials({ + credentials: newCredentials, + projectId: account.projectId, + kmsService + }); + + await pamAccountDAL.updateById(account.id, { + encryptedCredentials, + lastRotatedAt: new Date() + }); + + await auditLogService.createAuditLog({ + projectId: account.projectId, + actor: { + type: ActorType.PLATFORM, + metadata: {} + }, + event: { + type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION, + metadata: { + accountId: account.id, + accountName: account.name, + resourceId: resource.id, + resourceType: resource.resourceType + } + } + }); + } catch (error) { + logger.error(error, `Failed to rotate credentials for account ${account.id}`); + } + }); + + // eslint-disable-next-line no-await-in-loop + await Promise.all(rotationPromises); + } + }; + return { create, updateById, deleteById, list, access, - getSessionCredentials + getSessionCredentials, + rotateAllDueAccounts }; }; diff --git a/backend/src/ee/services/pam-account/pam-account-types.ts b/backend/src/ee/services/pam-account/pam-account-types.ts index 514d7d780..4bbccc6fa 100644 --- a/backend/src/ee/services/pam-account/pam-account-types.ts +++ b/backend/src/ee/services/pam-account/pam-account-types.ts @@ -1,7 +1,10 @@ import { TPamAccount } from "../pam-resource/pam-resource-types"; // DTOs -export type TCreateAccountDTO = Pick; +export type TCreateAccountDTO = Pick< + TPamAccount, + "name" | "description" | "credentials" | "folderId" | "resourceId" | "rotationEnabled" | "rotationIntervalSeconds" +>; export type TUpdateAccountDTO = Partial> & { accountId: string; diff --git a/backend/src/ee/services/pam-resource/pam-resource-fns.ts b/backend/src/ee/services/pam-resource/pam-resource-fns.ts index 1d79e892e..9d7493e68 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-fns.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-fns.ts @@ -2,6 +2,7 @@ import { TPamResources } from "@app/db/schemas"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { KmsDataKey } from "@app/services/kms/kms-types"; +import { decryptAccountCredentials } from "../pam-account/pam-account-fns"; import { TPamResource, TPamResourceConnectionDetails } from "./pam-resource-types"; import { getPostgresResourceListItem } from "./postgres/postgres-resource-fns"; @@ -63,6 +64,13 @@ export const decryptResource = async ( encryptedConnectionDetails: resource.encryptedConnectionDetails, projectId, kmsService - }) + }), + rotationAccountCredentials: resource.encryptedRotationAccountCredentials + ? await decryptAccountCredentials({ + encryptedCredentials: resource.encryptedRotationAccountCredentials, + projectId, + kmsService + }) + : null } as TPamResource; }; diff --git a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts index 80a50a9a4..5b0199166 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts @@ -6,6 +6,7 @@ import { slugSchema } from "@app/server/lib/schemas"; // Resources export const BasePamResourceSchema = PamResourcesSchema.omit({ encryptedConnectionDetails: true, + encryptedRotationAccountCredentials: true, resourceType: true }); @@ -37,10 +38,14 @@ export const BaseCreatePamAccountSchema = z.object({ resourceId: z.string().uuid(), folderId: z.string().uuid().optional(), name: slugSchema({ field: "name" }), - description: z.string().max(512).nullable().optional() + description: z.string().max(512).nullable().optional(), + rotationEnabled: z.boolean(), + rotationIntervalSeconds: z.number().min(3600) }); export const BaseUpdatePamAccountSchema = z.object({ name: slugSchema({ field: "name" }).optional(), - description: z.string().max(512).nullable().optional() + description: z.string().max(512).nullable().optional(), + rotationEnabled: z.boolean().optional(), + rotationIntervalSeconds: z.number().min(3600).optional() }); diff --git a/backend/src/ee/services/pam-resource/pam-resource-service.ts b/backend/src/ee/services/pam-resource/pam-resource-service.ts index 312795a50..1cc3ca135 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-service.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-service.ts @@ -10,10 +10,16 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "../license/license-service"; +import { encryptAccountCredentials } from "../pam-account/pam-account-fns"; import { TPamResourceDALFactory } from "./pam-resource-dal"; import { PamResource } from "./pam-resource-enums"; import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory"; -import { decryptResource, encryptResourceConnectionDetails, listResourceOptions } from "./pam-resource-fns"; +import { + decryptResource, + decryptResourceConnectionDetails, + encryptResourceConnectionDetails, + listResourceOptions +} from "./pam-resource-fns"; import { TCreateResourceDTO, TUpdateResourceDTO } from "./pam-resource-types"; type TPamResourceServiceFactoryDep = { @@ -61,7 +67,7 @@ export const pamResourceServiceFactory = ({ }; const create = async ( - { resourceType, connectionDetails, gatewayId, name, projectId }: TCreateResourceDTO, + { resourceType, connectionDetails, gatewayId, name, projectId, rotationAccountCredentials }: TCreateResourceDTO, actor: OrgServiceActor ) => { const orgLicensePlan = await licenseService.getPlan(actor.orgId); @@ -88,26 +94,42 @@ export const pamResourceServiceFactory = ({ gatewayId, gatewayV2Service ); - const validatedConnectionDetails = await factory.validateConnection(); + const validatedConnectionDetails = await factory.validateConnection(); const encryptedConnectionDetails = await encryptResourceConnectionDetails({ connectionDetails: validatedConnectionDetails, projectId, kmsService }); + let encryptedRotationAccountCredentials: Buffer | null = null; + + if (rotationAccountCredentials) { + const validatedRotationAccountCredentials = await factory.validateAccountCredentials(rotationAccountCredentials); + + encryptedRotationAccountCredentials = await encryptAccountCredentials({ + credentials: validatedRotationAccountCredentials, + projectId, + kmsService + }); + } + const resource = await pamResourceDAL.create({ resourceType, encryptedConnectionDetails, gatewayId, name, - projectId + projectId, + encryptedRotationAccountCredentials }); return decryptResource(resource, projectId, kmsService); }; - const updateById = async ({ connectionDetails, resourceId, name }: TUpdateResourceDTO, actor: OrgServiceActor) => { + const updateById = async ( + { connectionDetails, resourceId, name, rotationAccountCredentials }: TUpdateResourceDTO, + actor: OrgServiceActor + ) => { const orgLicensePlan = await licenseService.getPlan(actor.orgId); if (!orgLicensePlan.pam) { throw new BadRequestError({ @@ -151,6 +173,34 @@ export const pamResourceServiceFactory = ({ updateDoc.encryptedConnectionDetails = encryptedConnectionDetails; } + if (rotationAccountCredentials !== undefined) { + updateDoc.encryptedRotationAccountCredentials = null; + + if (rotationAccountCredentials) { + const decryptedConnectionDetails = await decryptResourceConnectionDetails({ + encryptedConnectionDetails: resource.encryptedConnectionDetails, + projectId: resource.projectId, + kmsService + }); + + const factory = PAM_RESOURCE_FACTORY_MAP[resource.resourceType as PamResource]( + resource.resourceType as PamResource, + decryptedConnectionDetails, + resource.gatewayId, + gatewayV2Service + ); + + const validatedRotationAccountCredentials = + await factory.validateAccountCredentials(rotationAccountCredentials); + + updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({ + credentials: validatedRotationAccountCredentials, + projectId: resource.projectId, + kmsService + }); + } + } + // If nothing was updated, return the fetched resource if (Object.keys(updateDoc).length === 0) { return decryptResource(resource, resource.projectId, kmsService); diff --git a/backend/src/ee/services/pam-resource/pam-resource-types.ts b/backend/src/ee/services/pam-resource/pam-resource-types.ts index fb1b669ed..f2016420a 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-types.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-types.ts @@ -18,7 +18,7 @@ export type TPamAccountCredentials = TPostgresAccountCredentials; // Resource DTOs export type TCreateResourceDTO = Pick< TPamResource, - "name" | "connectionDetails" | "resourceType" | "gatewayId" | "projectId" + "name" | "connectionDetails" | "resourceType" | "gatewayId" | "projectId" | "rotationAccountCredentials" >; export type TUpdateResourceDTO = Partial> & { @@ -30,6 +30,10 @@ export type TPamResourceFactoryValidateConnection = ( credentials: C ) => Promise; +export type TPamResourceFactoryRotateAccountCredentials = ( + rotationAccountCredentials: C, + currentCredentials: C +) => Promise; export type TPamResourceFactory = ( resourceType: PamResource, @@ -39,4 +43,5 @@ export type TPamResourceFactory { validateConnection: TPamResourceFactoryValidateConnection; validateAccountCredentials: TPamResourceFactoryValidateAccountCredentials; + rotateAccountCredentials: TPamResourceFactoryRotateAccountCredentials; }; diff --git a/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts b/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts index a97e3f2e7..f383f521a 100644 --- a/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts @@ -15,13 +15,15 @@ import { BaseSqlResourceConnectionDetailsSchema } from "../shared/sql/sql-resource-schemas"; -// Resources export const PostgresResourceConnectionDetailsSchema = BaseSqlResourceConnectionDetailsSchema; +export const PostgresAccountCredentialsSchema = BaseSqlAccountCredentialsSchema; +// Resources const BasePostgresResourceSchema = BasePamResourceSchema.extend({ resourceType: z.literal(PamResource.Postgres) }); export const PostgresResourceSchema = BasePostgresResourceSchema.extend({ - connectionDetails: PostgresResourceConnectionDetailsSchema + connectionDetails: PostgresResourceConnectionDetailsSchema, + rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() }); export const PostgresResourceListItemSchema = z.object({ @@ -30,16 +32,16 @@ export const PostgresResourceListItemSchema = z.object({ }); export const CreatePostgresResourceSchema = BaseCreatePamResourceSchema.extend({ - connectionDetails: PostgresResourceConnectionDetailsSchema + connectionDetails: PostgresResourceConnectionDetailsSchema, + rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() }); export const UpdatePostgresResourceSchema = BaseUpdatePamResourceSchema.extend({ - connectionDetails: PostgresResourceConnectionDetailsSchema.optional() + connectionDetails: PostgresResourceConnectionDetailsSchema.optional(), + rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() }); // Accounts -export const PostgresAccountCredentialsSchema = BaseSqlAccountCredentialsSchema; - export const PostgresAccountSchema = BasePamAccountSchema.extend({ credentials: PostgresAccountCredentialsSchema }); diff --git a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts index 74a2c74ae..73defd6e6 100644 --- a/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts +++ b/backend/src/ee/services/pam-resource/shared/sql/sql-resource-factory.ts @@ -6,9 +6,14 @@ import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2 import { BadRequestError } from "@app/lib/errors"; import { GatewayProxyProtocol } from "@app/lib/gateway"; import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; import { PamResource } from "../../pam-resource-enums"; -import { TPamResourceFactory, TPamResourceFactoryValidateAccountCredentials } from "../../pam-resource-types"; +import { + TPamResourceFactory, + TPamResourceFactoryRotateAccountCredentials, + TPamResourceFactoryValidateAccountCredentials +} from "../../pam-resource-types"; import { TSqlAccountCredentials, TSqlResourceConnectionDetails } from "./sql-resource-types"; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; @@ -176,8 +181,66 @@ export const sqlResourceFactory: TPamResourceFactory = async ( + rotationAccountCredentials, + currentCredentials + ) => { + try { + const newPassword = alphaNumericNanoId(32); + + await executeWithGateway( + { + connectionDetails, + gatewayId, + resourceType, + username: rotationAccountCredentials.username, + password: rotationAccountCredentials.password + }, + gatewayV2Service, + async (client) => { + switch (resourceType) { + case PamResource.Postgres: + await client.raw(`ALTER USER ?? WITH PASSWORD '${newPassword}'`, [currentCredentials.username]); + break; + default: + throw new BadRequestError({ + message: `Password rotation for ${resourceType as PamResource} is not supported.` + }); + } + } + ); + + return { username: currentCredentials.username, password: newPassword }; + } catch (error) { + if (error instanceof BadRequestError) { + if (error.message === `password authentication failed for user "${rotationAccountCredentials.username}"`) { + throw new BadRequestError({ + message: "Management credentials invalid: Username or password incorrect" + }); + } + + if (error.message.includes("permission denied")) { + throw new BadRequestError({ + message: `Management credentials lack permission to rotate password for user "${currentCredentials.username}"` + }); + } + + if (error.message === "Connection terminated unexpectedly") { + throw new BadRequestError({ + message: "Connection terminated unexpectedly. Verify that host and port are correct" + }); + } + } + + throw new BadRequestError({ + message: `Unable to rotate account credentials for ${resourceType}: ${(error as Error).message || String(error)}` + }); + } + }; + return { validateConnection, - validateAccountCredentials + validateAccountCredentials, + rotateAccountCredentials }; }; diff --git a/backend/src/queue/queue-service.ts b/backend/src/queue/queue-service.ts index 7f45e3821..9d8c472f4 100644 --- a/backend/src/queue/queue-service.ts +++ b/backend/src/queue/queue-service.ts @@ -77,7 +77,8 @@ export enum QueueName { DailyReminders = "daily-reminders", SecretReminderMigration = "secret-reminder-migration", UserNotification = "user-notification", - HealthAlert = "health-alert" + HealthAlert = "health-alert", + PamAccountRotation = "pam-account-rotation" } export enum QueueJobs { @@ -126,7 +127,8 @@ export enum QueueJobs { DailyReminders = "daily-reminders", SecretReminderMigration = "secret-reminder-migration", UserNotification = "user-notification-job", - HealthAlert = "health-alert" + HealthAlert = "health-alert", + PamAccountRotation = "pam-account-rotation" } export type TQueueJobTypes = { @@ -357,6 +359,10 @@ export type TQueueJobTypes = { name: QueueJobs.HealthAlert; payload: undefined; }; + [QueueName.PamAccountRotation]: { + name: QueueJobs.PamAccountRotation; + payload: undefined; + }; }; const SECRET_SCANNING_JOBS = [ diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index e95dd501c..448564704 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -250,6 +250,7 @@ import { orgDALFactory } from "@app/services/org/org-dal"; import { orgServiceFactory } from "@app/services/org/org-service"; import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; +import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue"; import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; @@ -2193,7 +2194,13 @@ export const registerRoutes = async ( pamSessionDAL, permissionService, projectDAL, - userDAL + userDAL, + auditLogService + }); + + const pamAccountRotation = pamAccountRotationServiceFactory({ + queueService, + pamAccountService }); const pamSessionService = pamSessionServiceFactory({ @@ -2220,6 +2227,7 @@ export const registerRoutes = async ( await dailyResourceCleanUp.init(); await healthAlert.init(); await pkiSyncCleanup.init(); + await pamAccountRotation.init(); await dailyReminderQueueService.startDailyRemindersJob(); await dailyReminderQueueService.startSecretReminderMigrationJob(); await dailyExpiringPkiItemAlert.startSendingAlerts(); diff --git a/backend/src/services/pam-account-rotation/pam-account-rotation-queue.ts b/backend/src/services/pam-account-rotation/pam-account-rotation-queue.ts new file mode 100644 index 000000000..fb265ac56 --- /dev/null +++ b/backend/src/services/pam-account-rotation/pam-account-rotation-queue.ts @@ -0,0 +1,61 @@ +import { TPamAccountServiceFactory } from "@app/ee/services/pam-account/pam-account-service"; +import { getConfig } from "@app/lib/config/env"; +import { logger } from "@app/lib/logger"; +import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; + +type TPamAccountRotationServiceFactoryDep = { + queueService: TQueueServiceFactory; + pamAccountService: Pick; +}; + +export type TPamAccountRotationServiceFactory = ReturnType; + +export const pamAccountRotationServiceFactory = ({ + queueService, + pamAccountService +}: TPamAccountRotationServiceFactoryDep) => { + const appCfg = getConfig(); + + const init = async () => { + if (appCfg.isSecondaryInstance) { + return; + } + + await queueService.stopRepeatableJob( + QueueName.PamAccountRotation, + QueueJobs.PamAccountRotation, + { pattern: "0 * * * *", utc: true }, + QueueName.PamAccountRotation // job id + ); + + await queueService.startPg( + QueueJobs.PamAccountRotation, + async () => { + try { + logger.info(`${QueueName.PamAccountRotation}: pam account rotation task started`); + await pamAccountService.rotateAllDueAccounts(); + logger.info(`${QueueName.PamAccountRotation}: pam account rotation task completed`); + } catch (error) { + logger.error(error, `${QueueName.PamAccountRotation}: pam account rotation failed`); + throw error; + } + }, + { + batchSize: 1, + workerCount: 1, + pollingIntervalSeconds: 1 // 5 * 60 + } + ); + + await queueService.schedulePg( + QueueJobs.PamAccountRotation, + "0 * * * *", // Schedule to run every hour + undefined, + { tz: "UTC" } + ); + }; + + return { + init + }; +}; diff --git a/frontend/src/hooks/api/pam/types/base-account.ts b/frontend/src/hooks/api/pam/types/base-account.ts index 9f45b1a4a..ec94d51b2 100644 --- a/frontend/src/hooks/api/pam/types/base-account.ts +++ b/frontend/src/hooks/api/pam/types/base-account.ts @@ -12,6 +12,9 @@ export interface TBasePamAccount { }; name: string; description?: string | null; + rotationEnabled: boolean; + rotationIntervalSeconds: number; + lastRotatedAt?: string | null; createdAt: string; updatedAt: string; } diff --git a/frontend/src/hooks/api/pam/types/postgres-resource.ts b/frontend/src/hooks/api/pam/types/postgres-resource.ts index 513610be1..b1b5b7487 100644 --- a/frontend/src/hooks/api/pam/types/postgres-resource.ts +++ b/frontend/src/hooks/api/pam/types/postgres-resource.ts @@ -6,6 +6,7 @@ import { TBasePamResource } from "./base-resource"; // Resources export type TPostgresResource = TBasePamResource & { resourceType: PamResourceType.Postgres } & { connectionDetails: TBaseSqlConnectionDetails; + rotationAccountCredentials?: TBaseSqlCredentials | null; }; // Accounts diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index ead2b06e1..bb64e33c0 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -37,7 +37,10 @@ const CreateForm = ({ console.log({ folderId }); const onSubmit = async ( - formData: DiscriminativePick + formData: DiscriminativePick< + TPamAccount, + "name" | "description" | "credentials" | "rotationEnabled" | "rotationIntervalSeconds" + > ) => { try { const account = await createPamAccount.mutateAsync({ @@ -74,7 +77,10 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { const updatePamAccount = useUpdatePamAccount(); const onSubmit = async ( - formData: DiscriminativePick + formData: DiscriminativePick< + TPamAccount, + "name" | "description" | "credentials" | "rotationEnabled" | "rotationIntervalSeconds" + > ) => { try { const updatedAccount = await updatePamAccount.mutateAsync({ diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index 5bcd459aa..0033c48f3 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -8,13 +8,14 @@ import { TPostgresAccount } from "@app/hooks/api/pam"; import { BaseSqlAccountSchema } from "./shared/sql-account-schemas"; import { SqlAccountFields } from "./shared/SqlAccountFields"; import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields"; +import { RotateAccountFields, rotateAccountFieldsSchema } from "./RotateAccountFields"; type Props = { account?: TPostgresAccount; onSubmit: (formData: FormData) => Promise; }; -const formSchema = genericAccountFieldsSchema.extend({ +const formSchema = genericAccountFieldsSchema.extend(rotateAccountFieldsSchema.shape).extend({ credentials: BaseSqlAccountSchema }); @@ -50,6 +51,7 @@ export const PostgresAccountForm = ({ account, onSubmit }: Props) => { > +

@@ -257,6 +269,10 @@ export const GatewayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteGateway()} /> + handlePopUpToggle("deployGateway", isOpen)} + /> diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx new file mode 100644 index 000000000..2b8f17fe7 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -0,0 +1,243 @@ +import { createNotification } from "@app/components/notifications"; +import { + FilterableSelect, + FormLabel, + IconButton, + Input, + Modal, + ModalContent +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { useGetRelays } from "@app/hooks/api/relays"; +import { slugSchema } from "@app/lib/schemas"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { RelayOption } from "./RelayOption"; +import { useNavigate } from "@tanstack/react-router"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { twMerge } from "tailwind-merge"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + + const [name, setName] = useState(""); + const [relay, setRelay] = useState(null); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + + useEffect(() => { + if (!isOpen) { + setName(""); + setRelay(null); + setIdentity(null); + setIdentityToken(""); + } + }, [isOpen]); + + const { data: relays, isPending: isRelaysLoading } = useGetRelays(); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + useEffect(() => { + const generateToken = async () => { + if (!identity) return; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: identity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: identity.id, + name: "gateway token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + generateToken(); + }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); + + const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { + setIdentity(selectedIdentity); + setIdentityToken(""); + }; + + const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); + + const isCommandReady = useMemo( + () => !!name && isNameValid && !!relay && !!identityToken, + [name, isNameValid, relay, identityToken] + ); + + const command = useMemo( + () => + `infisical gateway start --name=${name} --relay=${ + relay?.name || "" + } --domain=${siteURL} --token=${identityToken}`, + [name, relay, identityToken, siteURL] + ); + + return ( + + + + setName(e.target.value)} + placeholder="Enter gateway name..." + isError={!isNameValid} + /> + + + { + if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { + navigate({ + search: { selectedTab: "relays" } + }); + return; + } + + setRelay(newValue as SingleValue<{ id: string; name: string }>); + }} + isLoading={isRelaysLoading} + options={[ + { + id: "_create", + name: "Deploy New Relay" + }, + ...(relays || []) + ]} + placeholder="Select relay..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id} + components={{ Option: RelayOption }} + /> + + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + + +

+ + { + navigator.clipboard.writeText(command); + + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} + isDisabled={!isCommandReady} + > + + +
+ + Install the Infisical CLI + + + + + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx new file mode 100644 index 000000000..2347c0472 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx @@ -0,0 +1,204 @@ +import { createNotification } from "@app/components/notifications"; +import { + FilterableSelect, + FormLabel, + IconButton, + Input, + Modal, + ModalContent +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { twMerge } from "tailwind-merge"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const DeployRelayModal = ({ isOpen, onOpenChange }: Props) => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [name, setName] = useState(""); + const [host, setHost] = useState(""); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + + useEffect(() => { + if (!isOpen) { + setName(""); + setHost(""); + setIdentity(null); + setIdentityToken(""); + } + }, [isOpen]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + useEffect(() => { + const generateToken = async () => { + if (!identity) return; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: identity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: identity.id, + name: "relay token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + generateToken(); + }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); + + const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { + setIdentity(selectedIdentity); + setIdentityToken(""); + }; + + const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); + + const isCommandReady = useMemo( + () => !!name && !!host && isNameValid && !!identityToken, + [name, isNameValid, host, identityToken] + ); + + const command = useMemo( + () => + `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`, + [name, siteURL, host, identityToken] + ); + + return ( + + + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={!isNameValid} + /> + + + setHost(e.target.value)} placeholder="0.0.0.0" /> + + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + + +
+ + { + navigator.clipboard.writeText(command); + + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} + isDisabled={!isCommandReady} + > + + +
+ + Install the Infisical CLI + + +
+
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx new file mode 100644 index 000000000..90c9a7ebd --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx @@ -0,0 +1,32 @@ +import { components, OptionProps } from "react-select"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export const RelayOption = ({ + isSelected, + children, + ...props +}: OptionProps<{ id: string; name: string }>) => { + const isCreateOption = props.data.id === "_create"; + + return ( + +
+ {isCreateOption ? ( +
+ + Deploy New Relay +
+ ) : ( + <> +

{children}

+ {isSelected && ( + + )} + + )} +
+
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx index 956a4d1ed..dc90b7f21 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx @@ -1,14 +1,19 @@ -import { useState } from "react"; -import { useSearch } from "@tanstack/react-router"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { GatewayTab } from "../GatewayTab/GatewayTab"; import { RelayTab } from "../RelayTab/RelayTab"; +import { ROUTE_PATHS } from "@app/const/routes"; export const NetworkingTabGroup = () => { - const search = useSearch({ - from: "/_authenticate/_inject-org-details/_org-layout/organization/networking/" + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + const selectedTab = useSearch({ + from: ROUTE_PATHS.Organization.NetworkingPage.id, + select: (el) => el.selectedTab, + structuralSharing: true }); const tabs = [ @@ -16,10 +21,14 @@ export const NetworkingTabGroup = () => { { name: "Relays", key: "relays", component: RelayTab } ]; - const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); + const handleTabChange = (tab: string) => { + navigate({ + search: { selectedTab: tab } + }); + }; return ( - + {tabs.map((tab) => ( diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index e525b765d..2bfa8b250 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -7,6 +7,7 @@ import { faEllipsisV, faInfoCircle, faMagnifyingGlass, + faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; @@ -16,6 +17,7 @@ import { formatRelative } from "date-fns"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { + Button, DeleteActionModal, DropdownMenu, DropdownMenuContent, @@ -41,6 +43,7 @@ import { import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; +import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -66,7 +69,10 @@ export const RelayTab = withPermission( const [search, setSearch] = useState(""); const { data: relays, isPending: isRelaysLoading } = useGetRelays(); - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteRelay"] as const); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "deleteRelay", + "deployRelay" + ] as const); const deleteRelayById = useDeleteRelayById(); @@ -87,8 +93,8 @@ export const RelayTab = withPermission( return (
-
-
+
+

Relays

+
+

@@ -222,6 +236,10 @@ export const RelayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteRelay()} /> + handlePopUpToggle("deployRelay", isOpen)} + />

diff --git a/frontend/src/pages/organization/NetworkingPage/route.tsx b/frontend/src/pages/organization/NetworkingPage/route.tsx index 906b12806..433647476 100644 --- a/frontend/src/pages/organization/NetworkingPage/route.tsx +++ b/frontend/src/pages/organization/NetworkingPage/route.tsx @@ -5,16 +5,16 @@ import { z } from "zod"; import { NetworkingPage } from "./NetworkingPage"; const NetworkingPageQueryParams = z.object({ - selectedTab: z.string().catch("") + selectedTab: z.string().catch("gateways") }); export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/organization/networking/" + "/_authenticate/_inject-org-details/_org-layout/organization/networking" )({ component: NetworkingPage, validateSearch: zodValidator(NetworkingPageQueryParams), search: { - middlewares: [stripSearchParams({ selectedTab: "" })] + middlewares: [stripSearchParams({ selectedTab: "gateways" })] }, context: () => ({ breadcrumbs: [ diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 1562f302d..bc92b6ef0 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -50,6 +50,7 @@ import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/ import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route' import { Route as adminAccessManagementPageRouteImport } from './pages/admin/AccessManagementPage/route' import { Route as organizationProjectsPageRouteImport } from './pages/organization/ProjectsPage/route' +import { Route as organizationNetworkingPageRouteImport } from './pages/organization/NetworkingPage/route' import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route' import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route' import { Route as organizationAccessManagementPageRouteImport } from './pages/organization/AccessManagementPage/route' @@ -63,7 +64,6 @@ import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route' import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route' import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route' -import { Route as organizationNetworkingPageRouteImport } from './pages/organization/NetworkingPage/route' import { Route as organizationAppConnectionsAppConnectionsPageRouteImport } from './pages/organization/AppConnections/AppConnectionsPage/route' import { Route as sshLayoutImport } from './pages/ssh/layout' import { Route as secretScanningLayoutImport } from './pages/secret-scanning/layout' @@ -270,10 +270,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing', )() -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport = - createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/organization/networking', - )() const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/organization/app-connections', @@ -594,14 +590,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute = - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport.update({ - id: '/networking', - path: '/networking', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, - } as any) - const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport.update({ id: '/app-connections', @@ -658,6 +646,14 @@ const organizationProjectsPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationNetworkingPageRouteRoute = + organizationNetworkingPageRouteImport.update({ + id: '/networking', + path: '/networking', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + } as any) + const organizationBillingPageRouteRoute = organizationBillingPageRouteImport.update({ id: '/billing', @@ -807,14 +803,6 @@ const organizationSecretSharingPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, } as any) -const organizationNetworkingPageRouteRoute = - organizationNetworkingPageRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute, - } as any) - const organizationAppConnectionsAppConnectionsPageRouteRoute = organizationAppConnectionsAppConnectionsPageRouteImport.update({ id: '/', @@ -2477,6 +2465,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationBillingPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } + '/_authenticate/_inject-org-details/_org-layout/organization/networking': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/networking' + path: '/networking' + fullPath: '/organization/networking' + preLoaderRoute: typeof organizationNetworkingPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + } '/_authenticate/_inject-org-details/_org-layout/organization/projects': { id: '/_authenticate/_inject-org-details/_org-layout/organization/projects' path: '/projects' @@ -2533,13 +2528,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } - '/_authenticate/_inject-org-details/_org-layout/organization/networking': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/networking' - path: '/networking' - fullPath: '/organization/networking' - preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport - } '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': { id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' path: '/secret-sharing' @@ -2568,13 +2556,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationAppConnectionsAppConnectionsPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport } - '/_authenticate/_inject-org-details/_org-layout/organization/networking/': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/networking/' - path: '/' - fullPath: '/organization/networking/' - preLoaderRoute: typeof organizationNetworkingPageRouteImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport - } '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': { id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' path: '/' @@ -4002,20 +3983,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithCh AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren, ) -interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren { - organizationNetworkingPageRouteRoute: typeof organizationNetworkingPageRouteRoute -} - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren = - { - organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, - } - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren = - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute._addFileChildren( - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren, - ) - interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren { organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute organizationSecretSharingSettingsPageRouteRoute: typeof organizationSecretSharingSettingsPageRouteRoute @@ -4055,9 +4022,9 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute + organizationNetworkingPageRouteRoute: typeof organizationNetworkingPageRouteRoute organizationProjectsPageRouteRoute: typeof organizationProjectsPageRouteRoute AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute @@ -4072,11 +4039,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationAccessManagementPageRouteRoute, organizationAuditLogsPageRouteRoute: organizationAuditLogsPageRouteRoute, organizationBillingPageRouteRoute: organizationBillingPageRouteRoute, + organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, organizationProjectsPageRouteRoute: organizationProjectsPageRouteRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute: - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: @@ -5057,6 +5023,7 @@ export interface FileRoutesByFullPath { '/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute + '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/projects': typeof organizationProjectsPageRouteRoute '/admin/access-management': typeof adminAccessManagementPageRouteRoute '/admin/authentication': typeof adminAuthenticationPageRouteRoute @@ -5065,12 +5032,10 @@ export interface FileRoutesByFullPath { '/admin/environment': typeof adminEnvironmentPageRouteRoute '/admin/integrations': typeof adminIntegrationsPageRouteRoute '/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - '/organization/networking': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren '/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/organization/networking/': typeof organizationNetworkingPageRouteRoute '/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/organization/settings/': typeof organizationSettingsPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5294,6 +5259,7 @@ export interface FileRoutesByTo { '/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute + '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/projects': typeof organizationProjectsPageRouteRoute '/admin/access-management': typeof adminAccessManagementPageRouteRoute '/admin/authentication': typeof adminAuthenticationPageRouteRoute @@ -5303,7 +5269,6 @@ export interface FileRoutesByTo { '/admin/integrations': typeof adminIntegrationsPageRouteRoute '/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/settings': typeof organizationSettingsPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5529,6 +5494,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/networking': typeof organizationNetworkingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/projects': typeof organizationProjectsPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/access-management': typeof adminAccessManagementPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute @@ -5537,12 +5503,10 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/admin/_admin-layout/environment': typeof adminEnvironmentPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - '/_authenticate/_inject-org-details/_org-layout/organization/networking': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/organization/networking/': typeof organizationNetworkingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/settings/': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5778,6 +5742,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -5786,12 +5751,10 @@ export interface FileRouteTypes { | '/admin/environment' | '/admin/integrations' | '/organization/app-connections' - | '/organization/networking' | '/organization/secret-sharing' | '/organization/settings' | '/secret-manager/$projectId' | '/organization/app-connections/' - | '/organization/networking/' | '/organization/secret-sharing/' | '/organization/settings/' | '/organization/groups/$groupId' @@ -6014,6 +5977,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -6023,7 +5987,6 @@ export interface FileRouteTypes { | '/admin/integrations' | '/secret-manager/$projectId' | '/organization/app-connections' - | '/organization/networking' | '/organization/secret-sharing' | '/organization/settings' | '/organization/groups/$groupId' @@ -6247,6 +6210,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/access-management' | '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/organization/billing' + | '/_authenticate/_inject-org-details/_org-layout/organization/networking' | '/_authenticate/_inject-org-details/_org-layout/organization/projects' | '/_authenticate/_inject-org-details/admin/_admin-layout/access-management' | '/_authenticate/_inject-org-details/admin/_admin-layout/authentication' @@ -6255,12 +6219,10 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/admin/_admin-layout/environment' | '/_authenticate/_inject-org-details/admin/_admin-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' - | '/_authenticate/_inject-org-details/_org-layout/organization/networking' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' - | '/_authenticate/_inject-org-details/_org-layout/organization/networking/' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' | '/_authenticate/_inject-org-details/_org-layout/organization/settings/' | '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' @@ -6703,9 +6665,9 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/access-management", "/_authenticate/_inject-org-details/_org-layout/organization/audit-logs", "/_authenticate/_inject-org-details/_org-layout/organization/billing", + "/_authenticate/_inject-org-details/_org-layout/organization/networking", "/_authenticate/_inject-org-details/_org-layout/organization/projects", "/_authenticate/_inject-org-details/_org-layout/organization/app-connections", - "/_authenticate/_inject-org-details/_org-layout/organization/networking", "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId", @@ -6744,6 +6706,10 @@ export const routeTree = rootRoute "filePath": "organization/BillingPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" }, + "/_authenticate/_inject-org-details/_org-layout/organization/networking": { + "filePath": "organization/NetworkingPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization" + }, "/_authenticate/_inject-org-details/_org-layout/organization/projects": { "filePath": "organization/ProjectsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -6780,13 +6746,6 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback" ] }, - "/_authenticate/_inject-org-details/_org-layout/organization/networking": { - "filePath": "", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization", - "children": [ - "/_authenticate/_inject-org-details/_org-layout/organization/networking/" - ] - }, "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/organization", @@ -6814,10 +6773,6 @@ export const routeTree = rootRoute "filePath": "organization/AppConnections/AppConnectionsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/app-connections" }, - "/_authenticate/_inject-org-details/_org-layout/organization/networking/": { - "filePath": "organization/NetworkingPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization/networking" - }, "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/": { "filePath": "organization/SecretSharingPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index bebc50339..e84e5873d 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -40,7 +40,7 @@ const organizationRoutes = route("/organization", [ "organization/AppConnections/OauthCallbackPage/route.tsx" ) ]), - route("/networking", [index("organization/NetworkingPage/route.tsx")]) + route("/networking", "organization/NetworkingPage/route.tsx") ]); const secretManagerRoutes = route("/projects/secret-management/$projectId", [ From 1e6beb08c945b7176a830039d5244466b25a4a52 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 06:29:48 -0400 Subject: [PATCH 007/224] lint --- .../components/GatewayTab/GatewayTab.tsx | 2 +- .../GatewayTab/components/DeployGatewayModal.tsx | 16 +++++++++------- .../GatewayTab/components/DeployRelayModal.tsx | 11 ++++++----- .../NetworkingTabGroup/NetworkingTabGroup.tsx | 2 +- .../components/RelayTab/RelayTab.tsx | 1 + 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx index d5e79d326..a93694a3d 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx @@ -48,8 +48,8 @@ import { usePopUp } from "@app/hooks"; import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways"; import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2"; -import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; import { DeployGatewayModal } from "./components/DeployGatewayModal"; +import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; const GatewayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index 2b8f17fe7..e5bc048c5 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -1,3 +1,10 @@ +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { twMerge } from "tailwind-merge"; + import { createNotification } from "@app/components/notifications"; import { FilterableSelect, @@ -7,6 +14,7 @@ import { Modal, ModalContent } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { useOrganization } from "@app/context"; import { useAddIdentityTokenAuth, @@ -16,14 +24,8 @@ import { } from "@app/hooks/api"; import { useGetRelays } from "@app/hooks/api/relays"; import { slugSchema } from "@app/lib/schemas"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; + import { RelayOption } from "./RelayOption"; -import { useNavigate } from "@tanstack/react-router"; -import { ROUTE_PATHS } from "@app/const/routes"; -import { twMerge } from "tailwind-merge"; type Props = { isOpen: boolean; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx index 2347c0472..399d9851c 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx @@ -1,3 +1,9 @@ +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + import { createNotification } from "@app/components/notifications"; import { FilterableSelect, @@ -15,11 +21,6 @@ import { useGetIdentityTokenAuth } from "@app/hooks/api"; import { slugSchema } from "@app/lib/schemas"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { twMerge } from "tailwind-merge"; type Props = { isOpen: boolean; diff --git a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx index dc90b7f21..07da157ab 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx @@ -1,10 +1,10 @@ import { useNavigate, useSearch } from "@tanstack/react-router"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { GatewayTab } from "../GatewayTab/GatewayTab"; import { RelayTab } from "../RelayTab/RelayTab"; -import { ROUTE_PATHS } from "@app/const/routes"; export const NetworkingTabGroup = () => { const navigate = useNavigate({ diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index 2bfa8b250..e731aab42 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -43,6 +43,7 @@ import { import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; + import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { From ab48e098d2c2df5ee65eee9a22615a4064d6ff2d Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 06:43:36 -0400 Subject: [PATCH 008/224] remove low opacity from input --- .../components/GatewayTab/components/DeployGatewayModal.tsx | 2 +- .../components/GatewayTab/components/DeployRelayModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index e5bc048c5..9a98ce4b1 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -211,7 +211,7 @@ export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => {
- + {
- + Date: Fri, 17 Oct 2025 16:49:22 -0400 Subject: [PATCH 009/224] add "rotated
From ebb28844eed1adc852f68b6da79d892195e8e87d Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 22:54:22 -0400 Subject: [PATCH 010/224] make resource rotation account credentials write-only --- .../routes/v1/pam-resource-routers/index.ts | 4 +-- .../pam-resource-router.ts | 6 ++-- .../pam-resource/pam-resource-service.ts | 17 ++++++++-- .../postgres/postgres-resource-schemas.ts | 9 ++++++ .../components/PamAccountRow.tsx | 2 +- .../PamResourceForm/PostgresResourceForm.tsx | 32 ++++++++++++------- .../shared/SqlRotateAccountFields.tsx | 21 +++++++++--- 7 files changed, 65 insertions(+), 26 deletions(-) diff --git a/backend/src/ee/routes/v1/pam-resource-routers/index.ts b/backend/src/ee/routes/v1/pam-resource-routers/index.ts index a63b67d94..6b53781ae 100644 --- a/backend/src/ee/routes/v1/pam-resource-routers/index.ts +++ b/backend/src/ee/routes/v1/pam-resource-routers/index.ts @@ -1,7 +1,7 @@ import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums"; import { CreatePostgresResourceSchema, - PostgresResourceSchema, + SanitizedPostgresResourceSchema, UpdatePostgresResourceSchema } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; @@ -12,7 +12,7 @@ export const PAM_RESOURCE_REGISTER_ROUTER_MAP: Record { }), response: { 200: z.object({ - resources: ResourceSchema.array() + resources: SanitizedResourceSchema.array() }) } }, diff --git a/backend/src/ee/services/pam-resource/pam-resource-service.ts b/backend/src/ee/services/pam-resource/pam-resource-service.ts index 1c5ead252..076ec6855 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-service.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-service.ts @@ -10,7 +10,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { TLicenseServiceFactory } from "../license/license-service"; -import { encryptAccountCredentials } from "../pam-account/pam-account-fns"; +import { decryptAccountCredentials, encryptAccountCredentials } from "../pam-account/pam-account-fns"; import { TPamResourceDALFactory } from "./pam-resource-dal"; import { PamResource } from "./pam-resource-enums"; import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory"; @@ -192,8 +192,19 @@ export const pamResourceServiceFactory = ({ gatewayV2Service ); - const validatedRotationAccountCredentials = - await factory.validateAccountCredentials(rotationAccountCredentials); + // Logic to prevent overwriting unedited censored values + const finalCredentials = { ...rotationAccountCredentials }; + if (resource.encryptedRotationAccountCredentials && rotationAccountCredentials.password === "******") { + const decryptedCredentials = await decryptAccountCredentials({ + encryptedCredentials: resource.encryptedRotationAccountCredentials, + projectId: resource.projectId, + kmsService + }); + + finalCredentials.password = decryptedCredentials.password; + } + + const validatedRotationAccountCredentials = await factory.validateAccountCredentials(finalCredentials); updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({ credentials: validatedRotationAccountCredentials, diff --git a/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts b/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts index f383f521a..bbe83a3a4 100644 --- a/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/postgres/postgres-resource-schemas.ts @@ -26,6 +26,15 @@ export const PostgresResourceSchema = BasePostgresResourceSchema.extend({ rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional() }); +export const SanitizedPostgresResourceSchema = BasePostgresResourceSchema.extend({ + connectionDetails: PostgresResourceConnectionDetailsSchema, + rotationAccountCredentials: PostgresAccountCredentialsSchema.pick({ + username: true + }) + .nullable() + .optional() +}); + export const PostgresResourceListItemSchema = z.object({ name: z.literal("PostgreSQL"), resource: z.literal(PamResource.Postgres) diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx index 758ef2c20..8e7507ac9 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountRow.tsx @@ -11,8 +11,8 @@ import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; import { formatDistance } from "date-fns"; +import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { ProjectPermissionCan } from "@app/components/permissions"; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx index c172bb561..427fff40b 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx @@ -31,17 +31,25 @@ export const PostgresResourceForm = ({ resource, onSubmit }: Props) => { const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: resource ?? { - resourceType: PamResourceType.Postgres, - connectionDetails: { - host: "", - port: 5432, - database: "default", - sslEnabled: true, - sslRejectUnauthorized: true, - sslCertificate: undefined - } - } + defaultValues: resource + ? { + ...resource, + rotationAccountCredentials: { + ...resource.rotationAccountCredentials, + password: "******" + } + } + : { + resourceType: PamResourceType.Postgres, + connectionDetails: { + host: "", + port: 5432, + database: "default", + sslEnabled: true, + sslRejectUnauthorized: true, + sslCertificate: undefined + } + } }); const { @@ -62,7 +70,7 @@ export const PostgresResourceForm = ({ resource, onSubmit }: Props) => { selectedTabIndex={selectedTabIndex} setSelectedTabIndex={setSelectedTabIndex} /> - +
)} From 3a61ce07365fc6f5ec0dcda06c3ec6476c8b6dac Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Mon, 20 Oct 2025 21:44:39 -0300 Subject: [PATCH 025/224] Lint fix --- .../components/SecretDropzone/SecretDropzone.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx index 5d8ea59c3..ded34f9d4 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretDropzone/SecretDropzone.tsx @@ -38,12 +38,12 @@ import { fetchProjectSecrets, mergePersonalSecrets } from "@app/hooks/api/secret import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types"; import { - PopUpNames, - usePopUpAction, - useBatchModeActions, + BatchContext, PendingSecretCreate, PendingSecretUpdate, - BatchContext + PopUpNames, + useBatchModeActions, + usePopUpAction } from "../../SecretMainPage.store"; import { CopySecretsFromBoard } from "./CopySecretsFromBoard"; import { PasteSecretEnvModal } from "./PasteSecretEnvModal"; From 64b3d6132599bfa9a163f5f20f86a0cb42131b95 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 20:45:54 -0400 Subject: [PATCH 026/224] tweak pam resource query --- frontend/src/hooks/api/pam/queries.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/api/pam/queries.tsx b/frontend/src/hooks/api/pam/queries.tsx index c6df40e37..60ead74ec 100644 --- a/frontend/src/hooks/api/pam/queries.tsx +++ b/frontend/src/hooks/api/pam/queries.tsx @@ -12,7 +12,7 @@ export const pamKeys = { session: () => [...pamKeys.all, "session"] as const, listResourceOptions: () => [...pamKeys.resource(), "options"] as const, listResources: (projectId: string) => [...pamKeys.resource(), "list", projectId], - getResource: (resourceId?: string) => [...pamKeys.resource(), "get", resourceId], + getResource: (resourceId: string) => [...pamKeys.resource(), "get", resourceId], listAccounts: (projectId: string) => [...pamKeys.account(), "list", projectId], getSession: (sessionId: string) => [...pamKeys.session(), "get", sessionId], listSessions: (projectId: string) => [...pamKeys.session(), "list", projectId] @@ -77,7 +77,7 @@ export const useGetPamResourceById = ( > ) => { return useQuery({ - queryKey: pamKeys.getResource(resourceId), + queryKey: pamKeys.getResource(resourceId || ""), queryFn: async () => { const { data } = await apiRequest.get<{ resource: TPamResource }>( `/api/v1/pam/resources/${resourceId}` From 2b6fcb2564d61a35fd76f7ce6d665f8f633942e1 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 20:46:15 -0400 Subject: [PATCH 027/224] fix rotation account configured detection --- backend/src/ee/services/pam-resource/pam-resource-schemas.ts | 2 ++ .../components/PamAccountForm/PostgresAccountForm.tsx | 2 +- .../components/PamAccountForm/RotateAccountFields.tsx | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts index 5b0199166..8f4752f4e 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts @@ -31,6 +31,8 @@ export const BasePamAccountSchemaWithResource = BasePamAccountSchema.extend({ id: true, name: true, resourceType: true + }).extend({ + rotationCredentialsConfigured: z.boolean() }) }); diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index bbf7982fb..5a2104275 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -54,7 +54,7 @@ export const PostgresAccountForm = ({ account, resourceId, onSubmit }: Props) => } else { setRotationCredentialsConfigured(!!resource?.rotationAccountCredentials); } - }, [account]); + }, [account, resource]); return ( diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx index 32a7040ec..2739e1cd5 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx @@ -22,7 +22,10 @@ export const RotateAccountFields = ({ const rotationEnabled = watch("rotationEnabled"); return ( - +
Date: Mon, 20 Oct 2025 21:03:35 -0400 Subject: [PATCH 028/224] fix endpoint and improve frontend performance --- frontend/src/hooks/api/pam/queries.tsx | 8 +++++--- .../components/PamAccountForm/PamAccountForm.tsx | 8 +++++++- .../components/PamAccountForm/PostgresAccountForm.tsx | 9 ++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/frontend/src/hooks/api/pam/queries.tsx b/frontend/src/hooks/api/pam/queries.tsx index 60ead74ec..22810c4e3 100644 --- a/frontend/src/hooks/api/pam/queries.tsx +++ b/frontend/src/hooks/api/pam/queries.tsx @@ -4,6 +4,7 @@ import { apiRequest } from "@app/config/request"; import { TPamResourceOption } from "./types/resource-options"; import { TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types"; +import { PamResourceType } from "./enums"; export const pamKeys = { all: ["pam"] as const, @@ -70,22 +71,23 @@ export const useListPamResources = ( }; export const useGetPamResourceById = ( + resourceType?: PamResourceType, resourceId?: string, options?: Omit< UseQueryOptions>, - "queryKey" | "queryFn" | "enabled" + "queryKey" | "queryFn" > ) => { return useQuery({ queryKey: pamKeys.getResource(resourceId || ""), queryFn: async () => { const { data } = await apiRequest.get<{ resource: TPamResource }>( - `/api/v1/pam/resources/${resourceId}` + `/api/v1/pam/resources/${resourceType}/${resourceId}` ); return data.resource; }, - enabled: !!resourceId, + enabled: !!resourceId && !!resourceType && (options?.enabled ?? true), ...options }); }; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index eee101a54..8b553e656 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -65,7 +65,13 @@ const CreateForm = ({ switch (resourceType) { case PamResourceType.Postgres: - return ; + return ( + + ); default: throw new Error(`Unhandled resource: ${resourceType}`); } diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index 5a2104275..c353a278b 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -4,7 +4,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Button, ModalClose } from "@app/components/v2"; -import { TPostgresAccount, useGetPamResourceById } from "@app/hooks/api/pam"; +import { PamResourceType, TPostgresAccount, useGetPamResourceById } from "@app/hooks/api/pam"; import { BaseSqlAccountSchema } from "./shared/sql-account-schemas"; import { SqlAccountFields } from "./shared/SqlAccountFields"; @@ -14,6 +14,7 @@ import { RotateAccountFields, rotateAccountFieldsSchema } from "./RotateAccountF type Props = { account?: TPostgresAccount; resourceId?: string; + resourceType?: PamResourceType; onSubmit: (formData: FormData) => Promise; }; @@ -23,7 +24,7 @@ const formSchema = genericAccountFieldsSchema.extend(rotateAccountFieldsSchema.s type FormData = z.infer; -export const PostgresAccountForm = ({ account, resourceId, onSubmit }: Props) => { +export const PostgresAccountForm = ({ account, resourceId, resourceType, onSubmit }: Props) => { const isUpdate = Boolean(account); const form = useForm({ @@ -46,7 +47,9 @@ export const PostgresAccountForm = ({ account, resourceId, onSubmit }: Props) => const [rotationCredentialsConfigured, setRotationCredentialsConfigured] = useState(false); - const { data: resource } = useGetPamResourceById(resourceId); + const { data: resource } = useGetPamResourceById(resourceType, resourceId, { + enabled: !account && !!resourceId && !!resourceType + }); useEffect(() => { if (account) { From bdbec5b1589320d705f5acf9ab7069b0daed0810 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 21:09:01 -0400 Subject: [PATCH 029/224] add resource type to error log --- backend/src/ee/services/audit-log/audit-log-types.ts | 1 + backend/src/ee/services/pam-account/pam-account-service.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index be5906af5..94561e6d8 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -3841,6 +3841,7 @@ interface PamAccountCredentialRotationFailedEvent { accountName: string; accountId: string; resourceId: string; + resourceType: string; errorMessage: string; }; } diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 209eb25b9..4d0ccc6ab 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -554,9 +554,11 @@ export const pamAccountServiceFactory = ({ const batch = accounts.slice(i, i + ROTATION_CONCURRENCY_LIMIT); const rotationPromises = batch.map(async (account) => { + let logResourceType = "unknown"; try { const resource = await pamResourceDAL.findById(account.resourceId); if (!resource || !resource.encryptedRotationAccountCredentials) return; + logResourceType = resource.resourceType; const { connectionDetails, rotationAccountCredentials, gatewayId, resourceType } = await decryptResource( resource, @@ -604,7 +606,7 @@ export const pamAccountServiceFactory = ({ accountId: account.id, accountName: account.name, resourceId: resource.id, - resourceType: resource.resourceType + resourceType: logResourceType } } }); @@ -623,6 +625,7 @@ export const pamAccountServiceFactory = ({ accountId: account.id, accountName: account.name, resourceId: account.resourceId, + resourceType: logResourceType, errorMessage } } From efd63dcb35b85e7751dc9ba0daf09282d25729a1 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 21:13:40 -0400 Subject: [PATCH 030/224] improve rotation account error response --- .../src/ee/services/pam-resource/pam-resource-service.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/backend/src/ee/services/pam-resource/pam-resource-service.ts b/backend/src/ee/services/pam-resource/pam-resource-service.ts index 683e0bca4..9ec702b24 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-service.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-service.ts @@ -213,12 +213,9 @@ export const pamResourceServiceFactory = ({ kmsService }); } catch (err) { - if ( - err instanceof BadRequestError && - err.message === "Account credentials invalid: Username or password incorrect" - ) { + if (err instanceof BadRequestError) { throw new BadRequestError({ - message: "Rotation Account credentials invalid: Username or password incorrect" + message: `Rotation Account Error: ${err.message}` }); } From 35d4720d8631288211a9a1a55c93b17464abf046 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 21:24:57 -0400 Subject: [PATCH 031/224] final small fixes --- .../services/pam-account/pam-account-service.ts | 4 ++-- .../CustomProviderAuditLogStreamForm.tsx | 15 +++++++++------ .../PamAccountForm/shared/SqlAccountFields.tsx | 12 +++++++----- .../shared/SqlRotateAccountFields.tsx | 12 +++++++----- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 4d0ccc6ab..83e488231 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -611,6 +611,8 @@ export const pamAccountServiceFactory = ({ } }); } catch (error) { + logger.error(error, `Failed to rotate credentials for account [accountId=${account.id}]`); + const errorMessage = error instanceof Error ? error.message : "An unknown error occurred"; await auditLogService.createAuditLog({ @@ -630,8 +632,6 @@ export const pamAccountServiceFactory = ({ } } }); - - logger.error(error, `Failed to rotate credentials for account ${account.id}`); } }); diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx index 7d2868dcc..b9a125900 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx @@ -7,6 +7,7 @@ import { z } from "zod"; import { Button, FormControl, FormLabel, IconButton, Input, ModalClose } from "@app/components/v2"; import { LogProvider } from "@app/hooks/api/auditLogStreams/enums"; import { TCustomProviderLogStream } from "@app/hooks/api/auditLogStreams/types/providers/custom-provider"; +import { useState } from "react"; type Props = { auditLogStream?: TCustomProviderLogStream; @@ -29,6 +30,8 @@ const formSchema = z.object({ type FormData = z.infer; export const CustomProviderAuditLogStreamForm = ({ auditLogStream, onSubmit }: Props) => { + const [showPassword, setShowPassword] = useState(false); + const isUpdate = Boolean(auditLogStream); const form = useForm({ @@ -96,10 +99,10 @@ export const CustomProviderAuditLogStreamForm = ({ auditLogStream, onSubmit }: P > { + placeholder="Bearer " + onFocus={() => { if ( auditLogStream && auditLogStream.credentials.headers[i] && @@ -108,9 +111,9 @@ export const CustomProviderAuditLogStreamForm = ({ auditLogStream, onSubmit }: P ) { field.onChange(""); } - e.target.type = "text"; + setShowPassword(true); }} - onBlur={(e) => { + onBlur={() => { if ( auditLogStream && auditLogStream.credentials.headers[i] && @@ -119,7 +122,7 @@ export const CustomProviderAuditLogStreamForm = ({ auditLogStream, onSubmit }: P ) { field.onChange("******"); } - e.target.type = "password"; + setShowPassword(false); }} /> diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx index c32102231..3e9db178f 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx @@ -1,9 +1,11 @@ import { Controller, useFormContext } from "react-hook-form"; import { FormControl, Input } from "@app/components/v2"; +import { useState } from "react"; export const SqlAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { const { control } = useFormContext(); + const [showPassword, setShowPassword] = useState(false); return (
@@ -33,19 +35,19 @@ export const SqlAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { > { + onFocus={() => { if (isUpdate && field.value === "******") { field.onChange(""); } - e.target.type = "text"; + setShowPassword(true); }} - onBlur={(e) => { + onBlur={() => { if (isUpdate && field.value === "") { field.onChange("******"); } - e.target.type = "password"; + setShowPassword(false); }} /> diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx index 05b28aa94..869594140 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { @@ -11,6 +12,7 @@ import { export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { const { control } = useFormContext(); + const [showPassword, setShowPassword] = useState(false); return ( @@ -50,19 +52,19 @@ export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { > { + onFocus={() => { if (isUpdate && field.value === "******") { field.onChange(""); } - e.target.type = "text"; + setShowPassword(true); }} - onBlur={(e) => { + onBlur={() => { if (isUpdate && field.value === "") { field.onChange("******"); } - e.target.type = "password"; + setShowPassword(false); }} /> From 8bbddb09c0a1b47cde961325bd6fa8837cbd806e Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 21:28:27 -0400 Subject: [PATCH 032/224] lint --- frontend/src/hooks/api/pam/queries.tsx | 2 +- .../AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx | 2 +- .../components/PamAccountForm/shared/SqlAccountFields.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/api/pam/queries.tsx b/frontend/src/hooks/api/pam/queries.tsx index 22810c4e3..77afbe832 100644 --- a/frontend/src/hooks/api/pam/queries.tsx +++ b/frontend/src/hooks/api/pam/queries.tsx @@ -3,8 +3,8 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { TPamResourceOption } from "./types/resource-options"; -import { TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types"; import { PamResourceType } from "./enums"; +import { TPamAccount, TPamFolder, TPamResource, TPamSession } from "./types"; export const pamKeys = { all: ["pam"] as const, diff --git a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx index b9a125900..e60f09c4a 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/AuditLogStreamTab/AuditLogStreamForm/CustomProviderAuditLogStreamForm.tsx @@ -1,3 +1,4 @@ +import { useState } from "react"; import { Controller, FormProvider, useFieldArray, useForm } from "react-hook-form"; import { faPlus, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -7,7 +8,6 @@ import { z } from "zod"; import { Button, FormControl, FormLabel, IconButton, Input, ModalClose } from "@app/components/v2"; import { LogProvider } from "@app/hooks/api/auditLogStreams/enums"; import { TCustomProviderLogStream } from "@app/hooks/api/auditLogStreams/types/providers/custom-provider"; -import { useState } from "react"; type Props = { auditLogStream?: TCustomProviderLogStream; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx index 3e9db178f..7d9e85c44 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx @@ -1,7 +1,7 @@ +import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { FormControl, Input } from "@app/components/v2"; -import { useState } from "react"; export const SqlAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { const { control } = useFormContext(); From df069cbd5b5037a4c4b0d01ed5c4dc612e816b57 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 20 Oct 2025 21:34:28 -0400 Subject: [PATCH 033/224] tiny greptile improvement --- frontend/src/hooks/api/pam/queries.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/api/pam/queries.tsx b/frontend/src/hooks/api/pam/queries.tsx index 77afbe832..6339b4761 100644 --- a/frontend/src/hooks/api/pam/queries.tsx +++ b/frontend/src/hooks/api/pam/queries.tsx @@ -13,7 +13,12 @@ export const pamKeys = { session: () => [...pamKeys.all, "session"] as const, listResourceOptions: () => [...pamKeys.resource(), "options"] as const, listResources: (projectId: string) => [...pamKeys.resource(), "list", projectId], - getResource: (resourceId: string) => [...pamKeys.resource(), "get", resourceId], + getResource: (resourceType: string, resourceId: string) => [ + ...pamKeys.resource(), + "get", + resourceType, + resourceId + ], listAccounts: (projectId: string) => [...pamKeys.account(), "list", projectId], getSession: (sessionId: string) => [...pamKeys.session(), "get", sessionId], listSessions: (projectId: string) => [...pamKeys.session(), "list", projectId] @@ -79,7 +84,7 @@ export const useGetPamResourceById = ( > ) => { return useQuery({ - queryKey: pamKeys.getResource(resourceId || ""), + queryKey: pamKeys.getResource(resourceType || "", resourceId || ""), queryFn: async () => { const { data } = await apiRequest.get<{ resource: TPamResource }>( `/api/v1/pam/resources/${resourceType}/${resourceId}` From c8a00e7e3fd6b0df3ad6f44a05a095a71b514b2f Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 16:46:22 +0400 Subject: [PATCH 034/224] checkpoint --- backend/src/db/migrations/utils/env-config.ts | 15 +++++--- backend/src/db/migrations/utils/services.ts | 3 +- .../ee/services/license/license-service.ts | 38 ++++++++++--------- backend/src/lib/config/env.ts | 5 --- backend/src/server/routes/index.ts | 3 +- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index de32f4db9..6da7044aa 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { crypto } from "@app/lib/crypto/cryptography"; +import { removeTrailingSlash } from "@app/lib/fn"; import { zpStr } from "@app/lib/zod"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; @@ -22,13 +23,17 @@ const envSchema = z HSM_LIB_PATH: zpStr(z.string().optional()), HSM_PIN: zpStr(z.string().optional()), HSM_KEY_LABEL: zpStr(z.string().optional()), - HSM_SLOT: z.coerce.number().optional().default(0) + HSM_SLOT: z.coerce.number().optional().default(0), + + LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")), + LICENSE_SERVER_KEY: zpStr(z.string().optional()), + LICENSE_KEY: zpStr(z.string().optional()), + LICENSE_KEY_OFFLINE: zpStr(z.string().optional()), + INTERNAL_REGION: zpStr(z.enum(["us", "eu"]).optional()), + + SITE_URL: zpStr(z.string().transform((val) => (val ? removeTrailingSlash(val) : val))).optional() }) // To ensure that basic encryption is always possible. - .refine( - (data) => Boolean(data.ENCRYPTION_KEY) || Boolean(data.ROOT_ENCRYPTION_KEY), - "Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY must be defined." - ) .transform((data) => ({ ...data, isHsmConfigured: diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts index 1d61086fd..26640e38e 100644 --- a/backend/src/db/migrations/utils/services.ts +++ b/backend/src/db/migrations/utils/services.ts @@ -61,7 +61,8 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore } licenseDAL, keyStore, identityOrgMembershipDAL, - projectDAL + projectDAL, + envConfig }); // ----- HSM startup ----- diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index c2e67908f..3a5928713 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -10,7 +10,7 @@ import { CronJob } from "cron"; import { Knex } from "knex"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { verifyOfflineLicense } from "@app/lib/crypto"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -45,6 +45,10 @@ import { } from "./license-types"; type TLicenseServiceFactoryDep = { + envConfig: Pick< + TEnvConfig, + "LICENSE_SERVER_URL" | "LICENSE_SERVER_KEY" | "LICENSE_KEY" | "LICENSE_KEY_OFFLINE" | "INTERNAL_REGION" | "SITE_URL" + >; orgDAL: Pick; permissionService: Pick; licenseDAL: TLicenseDALFactory; @@ -67,26 +71,26 @@ export const licenseServiceFactory = ({ licenseDAL, keyStore, identityOrgMembershipDAL, - projectDAL + projectDAL, + envConfig }: TLicenseServiceFactoryDep) => { let isValidLicense = false; let instanceType = InstanceType.OnPrem; let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures(); let selfHostedLicense: TOfflineLicense | null = null; - const appCfg = getConfig(); const licenseServerCloudApi = setupLicenseRequestWithStore( - appCfg.LICENSE_SERVER_URL || "", + envConfig.LICENSE_SERVER_URL || "", LICENSE_SERVER_CLOUD_LOGIN, - appCfg.LICENSE_SERVER_KEY || "", - appCfg.INTERNAL_REGION + envConfig.LICENSE_SERVER_KEY || "", + envConfig.INTERNAL_REGION ); const licenseServerOnPremApi = setupLicenseRequestWithStore( - appCfg.LICENSE_SERVER_URL || "", + envConfig.LICENSE_SERVER_URL || "", LICENSE_SERVER_ON_PREM_LOGIN, - appCfg.LICENSE_KEY || "", - appCfg.INTERNAL_REGION + envConfig.LICENSE_KEY || "", + envConfig.INTERNAL_REGION ); const syncLicenseKeyOnPremFeatures = async (shouldThrow: boolean = false) => { @@ -120,7 +124,7 @@ export const licenseServiceFactory = ({ const init = async () => { try { - if (appCfg.LICENSE_SERVER_KEY) { + if (envConfig.LICENSE_SERVER_KEY) { const token = await licenseServerCloudApi.refreshLicense(); if (token) instanceType = InstanceType.Cloud; logger.info(`Instance type: ${InstanceType.Cloud}`); @@ -128,7 +132,7 @@ export const licenseServiceFactory = ({ return; } - if (appCfg.LICENSE_KEY) { + if (envConfig.LICENSE_KEY) { const token = await licenseServerOnPremApi.refreshLicense(); if (token) { await syncLicenseKeyOnPremFeatures(true); @@ -139,10 +143,10 @@ export const licenseServiceFactory = ({ return; } - if (appCfg.LICENSE_KEY_OFFLINE) { + if (envConfig.LICENSE_KEY_OFFLINE) { let isValidOfflineLicense = true; const contents: TOfflineLicenseContents = JSON.parse( - Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8") + Buffer.from(envConfig.LICENSE_KEY_OFFLINE, "base64").toString("utf8") ); const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature); @@ -181,7 +185,7 @@ export const licenseServiceFactory = ({ }; const initializeBackgroundSync = async () => { - if (appCfg.LICENSE_KEY) { + if (envConfig.LICENSE_KEY) { logger.info("Setting up background sync process for refresh onPremFeatures"); const job = new CronJob("*/10 * * * *", syncLicenseKeyOnPremFeatures); job.start(); @@ -397,8 +401,8 @@ export const licenseServiceFactory = ({ } = await licenseServerCloudApi.request.post( `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`, { - success_url: `${appCfg.SITE_URL}/organization/billing`, - cancel_url: `${appCfg.SITE_URL}/organization/billing` + success_url: `${envConfig.SITE_URL}/organization/billing`, + cancel_url: `${envConfig.SITE_URL}/organization/billing` } ); @@ -411,7 +415,7 @@ export const licenseServiceFactory = ({ } = await licenseServerCloudApi.request.post( `/api/license-server/v1/customers/${organization.customerId}/billing-details/billing-portal`, { - return_url: `${appCfg.SITE_URL}/organization/billing` + return_url: `${envConfig.SITE_URL}/organization/billing` } ); diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 15f878323..31e0eaeb4 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -363,11 +363,6 @@ const envSchema = z /* INTERNAL ----------------------------------------------------------------------------- */ INTERNAL_REGION: zpStr(z.enum(["us", "eu"]).optional()) }) - // To ensure that basic encryption is always possible. - .refine( - (data) => Boolean(data.ENCRYPTION_KEY) || Boolean(data.ROOT_ENCRYPTION_KEY), - "Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY must be defined." - ) .refine( (data) => Boolean(data.REDIS_URL) || Boolean(data.REDIS_SENTINEL_HOSTS) || Boolean(data.REDIS_CLUSTER_HOSTS), "Either REDIS_URL, REDIS_SENTINEL_HOSTS or REDIS_CLUSTER_HOSTS must be defined." diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index eb94afd4f..bee447c36 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -559,7 +559,8 @@ export const registerRoutes = async ( licenseDAL, keyStore, identityOrgMembershipDAL, - projectDAL + projectDAL, + envConfig }); const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL }); From afc9ab94bf84a9c842f72b02bfbb2b5f4f87e407 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 19:39:40 +0400 Subject: [PATCH 035/224] Create 20251021124744_fix-project-deletion-approval-policy-constraint.ts --- ...ect-deletion-approval-policy-constraint.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 backend/src/db/migrations/20251021124744_fix-project-deletion-approval-policy-constraint.ts diff --git a/backend/src/db/migrations/20251021124744_fix-project-deletion-approval-policy-constraint.ts b/backend/src/db/migrations/20251021124744_fix-project-deletion-approval-policy-constraint.ts new file mode 100644 index 000000000..88a38c6d6 --- /dev/null +++ b/backend/src/db/migrations/20251021124744_fix-project-deletion-approval-policy-constraint.ts @@ -0,0 +1,68 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +// Fix for 20250722152841_add-policies-environments-table.ts migration. +// 20250722152841_add-policies-environments-table.ts introduced a bug where you can no longer delete a project if it has any approval policy environments. + +export async function up(knex: Knex): Promise { + // Fix SecretApprovalPolicyEnvironment to cascade delete when environment is deleted + // note: this won't actually happen, as we prevent deletion of environments with active approval policies + + // in the old migration it was ON DELETE SET NULL, which doesn't work because envId is not a nullable col + await knex.schema.alterTable(TableName.SecretApprovalPolicyEnvironment, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); + }); + + // Fix AccessApprovalPolicyEnvironment to cascade delete when environment is deleted + // note: this won't actually happen, as we prevent deletion of environments with active approval policies + + // in the old migration it was ON DELETE SET NULL, which doesn't work because envId is not a nullable col + await knex.schema.alterTable(TableName.AccessApprovalPolicyEnvironment, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); + }); + + // Fix SecretApprovalPolicy to CASCADE instead of SET NULL + + // in the old migration it was ON DELETE SET NULL, which doesn't work because envId is not a nullable col + await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); + }); + + // Fix AccessApprovalPolicy to CASCADE instead of SET NULL + + // in the old migration it was ON DELETE SET NULL, which doesn't work because envId is not a nullable col + await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE"); + }); +} + +export async function down(knex: Knex): Promise { + // Revert SecretApprovalPolicyEnvironment + await knex.schema.alterTable(TableName.SecretApprovalPolicyEnvironment, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment); + }); + + // Revert AccessApprovalPolicyEnvironment + await knex.schema.alterTable(TableName.AccessApprovalPolicyEnvironment, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment); + }); + + // Revert SecretApprovalPolicy back to SET NULL + await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL"); + }); + + // Revert AccessApprovalPolicy back to SET NULL + await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => { + t.dropForeign(["envId"]); + t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL"); + }); +} From 0f925cfaad3f835272ade12fe7fcd3e2a561e565 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 20:48:47 +0400 Subject: [PATCH 036/224] smaller fixes --- backend/src/db/migrations/utils/services.ts | 2 +- backend/src/ee/services/hsm/hsm-fns.ts | 3 ++- backend/src/ee/services/hsm/hsm-service.ts | 15 ++++++++++++++- backend/src/services/kms/kms-service.ts | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts index 26640e38e..e4b675f7e 100644 --- a/backend/src/db/migrations/utils/services.ts +++ b/backend/src/db/migrations/utils/services.ts @@ -68,13 +68,13 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore } // ----- HSM startup ----- const hsmModule = initializeHsmModule(envConfig); + hsmModule.initialize(); const hsmService = hsmServiceFactory({ hsmModule: hsmModule.getModule(), envConfig }); - hsmModule.initialize(); await hsmService.startService(); const hsmStatus = await isHsmActiveAndEnabled({ diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index 352a36443..2603f80bd 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -35,6 +35,7 @@ export const initializeHsmModule = (envConfig: Pick null); - rootKmsConfigEncryptionStrategy = rootKmsConfig?.encryptionStrategy as RootKeyEncryptionStrategy | null; + rootKmsConfigEncryptionStrategy = (rootKmsConfig?.encryptionStrategy || null) as RootKeyEncryptionStrategy | null; if (rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.HSM && !licenseService.onPremFeatures.hsm) { throw new BadRequestError({ message: "Your license does not include HSM integration. Please upgrade to the Enterprise plan to use HSM." diff --git a/backend/src/ee/services/hsm/hsm-service.ts b/backend/src/ee/services/hsm/hsm-service.ts index 0ed4c5faf..3b332e446 100644 --- a/backend/src/ee/services/hsm/hsm-service.ts +++ b/backend/src/ee/services/hsm/hsm-service.ts @@ -460,10 +460,23 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon } }; + const randomBytes = async (length: number) => { + if (!pkcs11 || !isInitialized) { + throw new Error("PKCS#11 module is not initialized"); + } + + const randomData = await $withSession((sessionHandle) => + pkcs11.C_GenerateRandom(sessionHandle, Buffer.alloc(length)) + ); + + return randomData; + }; + return { encrypt, startService, isActive, - decrypt + decrypt, + randomBytes }; }; diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 035b3db02..5b35f2f63 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -1083,8 +1083,8 @@ export const kmsServiceFactory = ({ const isHsmActive = hsmStatus.isHsmConfigured; - logger.info("KMS: Generating new ROOT Key"); - const newRootKey = crypto.randomBytes(32); + logger.info(`KMS: Generating new ROOT Key with ${isHsmActive ? "HSM" : "software"} encryption`); + const newRootKey = isHsmActive ? await hsmService.randomBytes(32) : crypto.randomBytes(32); const encryptionStrategy = isHsmActive ? RootKeyEncryptionStrategy.HSM : RootKeyEncryptionStrategy.Software; From aa8aff9d8b998da52206393a22c51da0837642e7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 22:01:43 +0400 Subject: [PATCH 037/224] fix: persist softhsm configuration --- backend/Dockerfile.dev | 7 ++++--- backend/Dockerfile.dev.fips | 7 ++++--- backend/dev-entrypoint.sh | 16 ++++++++++++++++ docker-compose.dev.yml | 3 +++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100755 backend/dev-entrypoint.sh diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev index 5e17cf2bb..b5f4f7ac2 100644 --- a/backend/Dockerfile.dev +++ b/backend/Dockerfile.dev @@ -49,9 +49,6 @@ RUN rm -fr ${SOFTHSM2_SOURCES} # Install pkcs11-tool RUN apt-get install -y opensc -RUN mkdir -p /etc/softhsm2/tokens && \ - softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000 - # ? App setup # Install Infisical CLI @@ -64,10 +61,14 @@ WORKDIR /app COPY package.json package.json COPY package-lock.json package-lock.json +COPY dev-entrypoint.sh dev-entrypoint.sh +RUN chmod +x dev-entrypoint.sh + RUN npm install COPY . . ENV HOST=0.0.0.0 +ENTRYPOINT ["/app/dev-entrypoint.sh"] CMD ["npm", "run", "dev:docker"] diff --git a/backend/Dockerfile.dev.fips b/backend/Dockerfile.dev.fips index db5107985..4d5b84260 100644 --- a/backend/Dockerfile.dev.fips +++ b/backend/Dockerfile.dev.fips @@ -50,9 +50,6 @@ RUN rm -fr ${SOFTHSM2_SOURCES} # Install pkcs11-tool RUN apt-get install -y opensc -RUN mkdir -p /etc/softhsm2/tokens && \ - softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000 - WORKDIR /openssl-build RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ && tar -xf openssl-3.1.2.tar.gz \ @@ -77,6 +74,9 @@ WORKDIR /app COPY package.json package.json COPY package-lock.json package-lock.json +COPY dev-entrypoint.sh dev-entrypoint.sh +RUN chmod +x dev-entrypoint.sh + RUN npm install COPY . . @@ -87,4 +87,5 @@ ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules # ENV NODE_OPTIONS=--force-fips # Note(Daniel): We can't set this on the node options because it may break for existing folks using the infisical/infisical-fips image. Instead we call crypto.setFips(true) at runtime. ENV FIPS_ENABLED=true +ENTRYPOINT ["/app/dev-entrypoint.sh"] CMD ["npm", "run", "dev:docker"] diff --git a/backend/dev-entrypoint.sh b/backend/dev-entrypoint.sh new file mode 100755 index 000000000..9cb3c0a5e --- /dev/null +++ b/backend/dev-entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +update-ca-certificates + +# Initialize SoftHSM token if it doesn't exist +if [ ! -f /etc/softhsm2/tokens/auth-app.db ]; then + echo "Initializing SoftHSM token..." + mkdir -p /etc/softhsm2/tokens + softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000 + echo "SoftHSM token initialized" +else + echo "SoftHSM token already exists, skipping initialization" +fi + + +exec "$@" \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 00dc19a46..e60ef1ba5 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -77,6 +77,7 @@ services: - TELEMETRY_ENABLED=false volumes: - ./backend/src:/app/src + - softhsm_tokens:/etc/softhsm2/tokens # SoftHSM tokens are stored in a volume to persist across container restarts extra_hosts: - "host.docker.internal:host-gateway" @@ -198,3 +199,5 @@ volumes: ldap_data: ldap_config: grafana_storage: + softhsm_tokens: + driver: local \ No newline at end of file From 4dd343af11206c9b32ad2b9da84704072fa559cf Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 22:27:20 +0400 Subject: [PATCH 038/224] removed reloader requested changes --- .github/values.yaml | 57 ------------------- .../workflows/helm-release-infisical-core.yml | 2 +- ...rt-tests-infisical-standalone-postgres.yml | 2 +- .../CHANGELOG.md | 4 +- .../infisical-standalone-postgres/Chart.lock | 7 +-- .../infisical-standalone-postgres/Chart.yaml | 4 -- .../infisical-standalone-postgres/README.md | 1 - .../templates/infisical.yaml | 13 ----- .../templates/schema-migration-job.yaml | 56 ------------------ .../infisical-standalone-postgres/values.yaml | 8 +-- 10 files changed, 8 insertions(+), 146 deletions(-) delete mode 100644 .github/values.yaml delete mode 100644 helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml diff --git a/.github/values.yaml b/.github/values.yaml deleted file mode 100644 index 1b3ffd87a..000000000 --- a/.github/values.yaml +++ /dev/null @@ -1,57 +0,0 @@ -## @section Common parameters -## - -## @param nameOverride Override release name -## -nameOverride: "" -## @param fullnameOverride Override release fullname -## -fullnameOverride: "" - -## @section Infisical backend parameters -## Documentation : https://infisical.com/docs/self-hosting/deployments/kubernetes -## - -infisical: - autoDatabaseSchemaMigration: false - - enabled: false - - name: infisical - replicaCount: 3 - image: - repository: infisical/staging_infisical - tag: "latest" - pullPolicy: Always - - deploymentAnnotations: - secrets.infisical.com/auto-reload: "true" - - kubeSecretRef: "managed-secret" - -ingress: - ## @param ingress.enabled Enable ingress - ## - enabled: true - ## @param ingress.ingressClassName Ingress class name - ## - ingressClassName: nginx - ## @param ingress.nginx.enabled Ingress controller - ## - # nginx: - # enabled: true - ## @param ingress.annotations Ingress annotations - ## - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - hostName: "gamma.infisical.com" - tls: - - secretName: letsencrypt-prod - hosts: - - gamma.infisical.com - -postgresql: - enabled: false - -redis: - enabled: false diff --git a/.github/workflows/helm-release-infisical-core.yml b/.github/workflows/helm-release-infisical-core.yml index 6c317cc27..49118a5ae 100644 --- a/.github/workflows/helm-release-infisical-core.yml +++ b/.github/workflows/helm-release-infisical-core.yml @@ -56,7 +56,7 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0" \ --namespace infisical-standalone-postgres release: diff --git a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml index a7bb2c619..2023de187 100644 --- a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml +++ b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml @@ -67,5 +67,5 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres --set infisical.autoBootstrap.enabled=true" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0 --set infisical.autoBootstrap.enabled=true" \ --namespace infisical-standalone-postgres diff --git a/helm-charts/infisical-standalone-postgres/CHANGELOG.md b/helm-charts/infisical-standalone-postgres/CHANGELOG.md index a0e7216e0..5298432d7 100644 --- a/helm-charts/infisical-standalone-postgres/CHANGELOG.md +++ b/helm-charts/infisical-standalone-postgres/CHANGELOG.md @@ -1,7 +1,9 @@ ## 1.7.2 (October 20, 2025) Changes: +* Updated the default `infisical.image.tag` value to `v0.151.0`. +* `autoDatabaseSchemaMigration` has been fully removed as all newer versions of Infisical automatically run migrations as apart of the startup process. * Added automatic reloading support for the Infisical deployment when the `infisical.kubeSecretRef` kubernetes secret changes. - * Configurable by `infisical.redeployOnSecretChange: true|false`. Defaults to `true`. + * Configurable by `infisical.redeployOnSecretChange: true|false`. Defaults to `false`. ## 1.7.1 (October 10, 2025) diff --git a/helm-charts/infisical-standalone-postgres/Chart.lock b/helm-charts/infisical-standalone-postgres/Chart.lock index 8e7a3004e..b8ae1fd3f 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.lock +++ b/helm-charts/infisical-standalone-postgres/Chart.lock @@ -8,8 +8,5 @@ dependencies: - name: redis repository: oci://registry-1.docker.io/bitnamicharts version: 18.14.1 -- name: reloader - repository: https://stakater.github.io/stakater-charts - version: 2.2.3 -digest: sha256:ae7d9ff526de87e972fed0f9c8f32ca40af8cf8b24b59d814cf72beb66ee4198 -generated: "2025-10-20T21:07:19.162271+04:00" +digest: sha256:57a18fb5258fc153d27b633f6570104c7628af651f08f3ae7e1cf8920c2c31fa +generated: "2025-10-21T22:30:21.313884+04:00" diff --git a/helm-charts/infisical-standalone-postgres/Chart.yaml b/helm-charts/infisical-standalone-postgres/Chart.yaml index 0d4381e72..21f358626 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.yaml +++ b/helm-charts/infisical-standalone-postgres/Chart.yaml @@ -28,7 +28,3 @@ dependencies: version: 18.14.1 repository: oci://registry-1.docker.io/bitnamicharts condition: redis.enabled - - name: reloader - version: 2.2.3 - repository: https://stakater.github.io/stakater-charts - condition: infisical.redeployOnSecretChange diff --git a/helm-charts/infisical-standalone-postgres/README.md b/helm-charts/infisical-standalone-postgres/README.md index cd0e18848..b266563e8 100644 --- a/helm-charts/infisical-standalone-postgres/README.md +++ b/helm-charts/infisical-standalone-postgres/README.md @@ -18,7 +18,6 @@ A helm chart to deploy Infisical |-----|------|---------|-------------| | fullnameOverride | string | `""` | Overrides the full name of the release, affecting resource names | | infisical.affinity | object | `{}` | Node affinity settings for pod placement | -| infisical.autoDatabaseSchemaMigration | bool | `true` | Automatically migrates new database schema when deploying | | infisical.databaseSchemaMigrationJob.image.pullPolicy | string | `"IfNotPresent"` | Pulls image only if not present on the node | | infisical.databaseSchemaMigrationJob.image.repository | string | `"ghcr.io/groundnuty/k8s-wait-for"` | Image repository for migration wait job | | infisical.databaseSchemaMigrationJob.image.tag | string | `"no-root-v2.0"` | Image tag version | diff --git a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml index d4637866b..d13c51295 100644 --- a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml @@ -4,9 +4,6 @@ kind: Deployment metadata: name: {{ include "infisical.fullname" . }} annotations: - {{- if $infisicalValues.redeployOnSecretChange }} - secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} - {{- end }} updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} {{- with $infisicalValues.deploymentAnnotations }} {{- toYaml . | nindent 4 }} @@ -47,16 +44,6 @@ spec: {{- if $infisicalValues.image.imagePullSecrets }} imagePullSecrets: {{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }} - {{- end }} - {{- if $infisicalValues.autoDatabaseSchemaMigration }} - serviceAccountName: {{ include "infisical.serviceAccountName" . }} - initContainers: - - name: "migration-init" - image: "{{ $infisicalValues.databaseSchemaMigrationJob.image.repository }}:{{ $infisicalValues.databaseSchemaMigrationJob.image.tag }}" - imagePullPolicy: {{ $infisicalValues.databaseSchemaMigrationJob.image.pullPolicy }} - args: - - "job" - - "{{ .Release.Name }}-schema-migration-{{ .Release.Revision }}" {{- end }} containers: - name: {{ template "infisical.name" . }}-{{ $infisicalValues.name }} diff --git a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml deleted file mode 100644 index c53c6e3d1..000000000 --- a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml +++ /dev/null @@ -1,56 +0,0 @@ -{{- $infisicalValues := .Values.infisical }} -{{- if $infisicalValues.autoDatabaseSchemaMigration }} -apiVersion: batch/v1 -kind: Job -metadata: - name: "{{ .Release.Name }}-schema-migration-{{ .Release.Revision }}" - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - backoffLimit: 10 - template: - metadata: - name: "{{ .Release.Name }}-create-tables" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - {{- if $infisicalValues.redeployOnSecretChange }} - annotations: - secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} - {{- end }} - spec: - serviceAccountName: {{ include "infisical.serviceAccountName" . }} - {{- if $infisicalValues.image.imagePullSecrets }} - imagePullSecrets: - {{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }} - {{- end }} - restartPolicy: OnFailure - containers: - - name: infisical-schema-migration - image: "{{ $infisicalValues.image.repository }}:{{ $infisicalValues.image.tag }}" - command: ["npm", "run", "migration:latest"] - env: - {{- if .Values.postgresql.useExistingPostgresSecret.enabled }} - - name: DB_CONNECTION_URI - valueFrom: - secretKeyRef: - name: {{ .Values.postgresql.useExistingPostgresSecret.existingConnectionStringSecret.name }} - key: {{ .Values.postgresql.useExistingPostgresSecret.existingConnectionStringSecret.key }} - {{- end }} - {{- if .Values.postgresql.enabled }} - - name: DB_CONNECTION_URI - value: {{ include "infisical.postgresDBConnectionString" . }} - {{- end }} - envFrom: - - secretRef: - name: {{ $infisicalValues.kubeSecretRef }} - {{- with $infisicalValues.extraVolumeMounts }} - volumeMounts: - {{- toYaml . | nindent 10 }} - {{- end }} - {{- with $infisicalValues.extraVolumes }} - volumes: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/helm-charts/infisical-standalone-postgres/values.yaml b/helm-charts/infisical-standalone-postgres/values.yaml index 202a9008a..e0a81d9e3 100644 --- a/helm-charts/infisical-standalone-postgres/values.yaml +++ b/helm-charts/infisical-standalone-postgres/values.yaml @@ -10,12 +10,6 @@ infisical: # -- Sets the name of the deployment within this chart name: infisical - # -- Automatically migrates new database schema when deploying - autoDatabaseSchemaMigration: true - - # -- redeployOnSecretChange is used to reload the Infisical instance when the Kubernetes secret referenced by `infisical.kubeSecretRef` is updated - redeployOnSecretChange: true - autoBootstrap: # -- Enable auto-bootstrap of the Infisical instance enabled: false @@ -71,7 +65,7 @@ infisical: # -- Image repository for the Infisical service repository: infisical/infisical # -- Specific version tag of the Infisical image. View the latest version here https://hub.docker.com/r/infisical/infisical - tag: "v0.93.1-postgres" + tag: "v0.151.0" # -- Pulls image only if not already present on the node pullPolicy: IfNotPresent # -- Secret references for pulling the image, if needed From 407a7e60758c6e1ac5f503e087e4a75264dfc917 Mon Sep 17 00:00:00 2001 From: x032205 Date: Tue, 21 Oct 2025 14:54:05 -0400 Subject: [PATCH 039/224] remove default from rotationIntervalSeconds --- .../src/db/migrations/20251015042917_pam-account-rotation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/db/migrations/20251015042917_pam-account-rotation.ts b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts index cd9a7d19a..eee9fd1fd 100644 --- a/backend/src/db/migrations/20251015042917_pam-account-rotation.ts +++ b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts @@ -10,7 +10,7 @@ export async function up(knex: Knex): Promise { ) { await knex.schema.alterTable(TableName.PamAccount, (t) => { t.boolean("rotationEnabled").notNullable().defaultTo(false); - t.integer("rotationIntervalSeconds").notNullable().defaultTo(2592000); + t.integer("rotationIntervalSeconds").notNullable(); t.timestamp("lastRotatedAt").nullable(); }); } From c68da1201886a6936705c1942597d01e08577d54 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 21 Oct 2025 15:54:46 -0300 Subject: [PATCH 040/224] docs: enhance login documentation with detailed authentication methods and examples --- docs/cli/commands/login.mdx | 203 ++++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 7 deletions(-) diff --git a/docs/cli/commands/login.mdx b/docs/cli/commands/login.mdx index f93e3b4b2..50b96baa1 100644 --- a/docs/cli/commands/login.mdx +++ b/docs/cli/commands/login.mdx @@ -9,22 +9,92 @@ infisical login ### Description -The CLI uses authentication to verify your identity. When you enter the correct email and password for your account, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI. +The CLI uses authentication to verify your identity. You can authenticate using: +- **Browser Login** (default): Opens a browser for authentication +- **Direct Login**: Provide email and password via flags or environment variables for non-interactive workflows +- **Interactive CLI Login**: Use the `--interactive` flag to enter credentials via CLI prompts + +When authenticated, a token is generated and saved in your system Keyring to allow you to make future interactions with the CLI. To change where the login credentials are stored, visit the [vaults command](./vault). If you have added multiple users, you can switch between the users by using the [user command](./user). - When you authenticate with **any other method than `user`**, an access token will be printed to the console upon successful login. This token can be used to authenticate with the Infisical API and the CLI by passing it in the `--token` flag when applicable. - - Use flag `--plain` along with `--silent` to print only the token in plain text when using a machine identity auth method. - + **JWT Token Output:** + - For **user authentication** with the `--plain` flag: outputs only the JWT access token (useful for scripting) + - For **machine identity authentication**: an access token is always printed to the console + + Use the `--plain` flag to print only the token in plain text, which is ideal for capturing in environment variables or CI/CD pipelines. ### Authentication Methods -The Infisical CLI supports multiple authentication methods. Below are the available authentication methods, with their respective flags. +The Infisical CLI supports two main categories of authentication: User Authentication and Machine Identity Authentication. + +#### User Authentication + +User authentication is designed for individual developers and supports multiple login flows. + + + + The User authentication method allows you to log in with your email and password. This method supports three different login flows: + + - **Browser Login** (default): Opens a browser for authentication + - **Direct Login**: Provide credentials via flags or environment variables for CI/CD + - **Interactive CLI Login**: Enter credentials via CLI prompts using `--interactive` + + + + + Your email address. Required for direct login along with `--password`. + + + Your password. Required for direct login along with `--email`. + + + Force interactive CLI login instead of browser-based authentication. + + + Output only the JWT token (useful for scripting and CI/CD). + + + + + + + **Browser Login (Default)** + ```bash + infisical login + ``` + + **Direct Login (CI/CD)** + ```bash + infisical login --email=user@example.com --password=your-password + + # Or using environment variables + export INFISICAL_EMAIL="user@example.com" + export INFISICAL_PASSWORD="your-password" + infisical login + ``` + + **Interactive CLI Login** + ```bash + infisical login --interactive + ``` + + **Plain Token Output (for scripting)** + ```bash + export INFISICAL_TOKEN=$(infisical login --email=user@example.com --password=your-password --plain) + ``` + + + + + +#### Machine Identity Authentication + +Machine identity authentication methods are designed for automated systems, services, and CI/CD pipelines. @@ -330,6 +400,59 @@ The login command supports a number of flags that you can use for different auth + + ```bash + infisical login --email= --password= + ``` + + #### Description + Email address for direct user login. Must be used together with `--password` for non-interactive authentication. + + + The `email` flag can be substituted with the `INFISICAL_EMAIL` environment variable. + + + + + ```bash + infisical login --email= --password= + ``` + + #### Description + Password for direct user login. Must be used together with `--email` for non-interactive authentication. + + + For security in CI/CD environments, prefer using the `INFISICAL_PASSWORD` environment variable instead of passing the password as a command-line flag. + + + + The `password` flag can be substituted with the `INFISICAL_PASSWORD` environment variable. + + + + + ```bash + infisical login --interactive + ``` + + #### Description + Forces interactive CLI login where you'll be prompted to enter your email and password in the terminal, instead of opening a browser. + + + + ```bash + infisical login --email= --password= --plain + ``` + + #### Description + When used with direct user login or machine identity authentication, outputs only the JWT access token without any additional formatting. This is useful for scripting and CI/CD pipelines where you need to capture the token. + + ```bash + # Example: Capture token in a variable + export INFISICAL_TOKEN=$(infisical login --email= --password= --plain) + ``` + + @@ -346,6 +469,72 @@ The login command supports a number of flags that you can use for different auth +### User Authentication Examples + +The following examples demonstrate different ways to authenticate as a user with the Infisical CLI. + + + + Direct login is ideal for CI/CD pipelines and automation scripts where browser-based authentication is not possible. + + #### Using Command-Line Flags + + ```bash + # Basic direct login + infisical login --email user@example.com --password "your-password" + + # With custom domain (US Cloud) + infisical login --email user@example.com --password "your-password" --domain https://app.infisical.com + + # With custom domain (EU Cloud) + infisical login --email user@example.com --password "your-password" --domain https://eu.infisical.com + + # Output only JWT token for scripting + export INFISICAL_TOKEN=$(infisical login --email user@example.com --password "your-password" --plain) + ``` + + #### Using Environment Variables (Recommended for CI/CD) + + ```bash + # Set credentials as environment variables + export INFISICAL_EMAIL="user@example.com" + export INFISICAL_PASSWORD="your-password" + export INFISICAL_API_URL="https://app.infisical.com/api" + + # Login without additional flags + infisical login + + # Or with plain output for token capture + export INFISICAL_TOKEN=$(infisical login --plain) + ``` + + + + Interactive login prompts you to enter credentials in the terminal instead of opening a browser. + + ```bash + # Force interactive CLI login + infisical login --interactive + ``` + + You'll be prompted to enter: + - Email address + - Password + + + + + By default, running `infisical login` without any flags opens your browser for authentication. + + ```bash + # Opens browser for authentication + infisical login + ``` + + The browser will open to the Infisical login page, and upon successful authentication, the CLI will be automatically authenticated. + + + ### Machine Identity Authentication Quick Start @@ -367,7 +556,7 @@ In this example we'll be using the `universal-auth` method to login to obtain an ``` - + ```bash infisical secrets --projectId= Date: Wed, 22 Oct 2025 03:08:22 +0800 Subject: [PATCH 041/224] feat: add dynamic secret kube auth vault migration --- .../routes/v3/external-migration-router.ts | 50 ++++ .../hc-vault/hc-vault-connection-fns.ts | 121 ++++++++++ .../hc-vault/hc-vault-connection-types.ts | 23 ++ .../external-migration-service.ts | 53 ++++- .../platform/external-migrations/vault.mdx | 47 ++++ .../import-vault-kubernetes-role-modal.png | Bin 0 -> 417789 bytes frontend/src/hooks/api/migration/queries.tsx | 35 ++- frontend/src/hooks/api/migration/types.ts | 21 ++ .../IdentityKubernetesAuthForm.tsx | 38 ++- .../components/ActionBar/ActionBar.tsx | 46 ++-- .../KubernetesInputForm.tsx | 126 +++++++++- .../VaultKubernetesImportModal.tsx | 223 ++++++++++++++++++ 12 files changed, 747 insertions(+), 36 deletions(-) create mode 100644 docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png create mode 100644 frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx diff --git a/backend/src/server/routes/v3/external-migration-router.ts b/backend/src/server/routes/v3/external-migration-router.ts index f0737e01d..367490c82 100644 --- a/backend/src/server/routes/v3/external-migration-router.ts +++ b/backend/src/server/routes/v3/external-migration-router.ts @@ -393,6 +393,56 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider } }); + server.route({ + method: "GET", + url: "/vault/kubernetes-roles", + config: { + rateLimit: readLimit + }, + schema: { + querystring: z.object({ + namespace: z.string(), + mountPath: z.string() + }), + response: { + 200: z.object({ + roles: z.array( + z.object({ + name: z.string(), + mountPath: z.string(), + allowed_kubernetes_namespaces: z.array(z.string()).nullish(), + allowed_kubernetes_namespace_selector: z.string().nullish(), + token_max_ttl: z.number().nullish(), + token_default_ttl: z.number().nullish(), + token_default_audiences: z.array(z.string()).nullish(), + service_account_name: z.string().nullish(), + kubernetes_role_name: z.string().nullish(), + kubernetes_role_type: z.string().nullish(), + generated_role_rules: z.string().nullish(), + name_template: z.string().nullish(), + extra_annotations: z.record(z.string()).nullish(), + extra_labels: z.record(z.string()).nullish(), + config: z.object({ + kubernetes_host: z.string(), + kubernetes_ca_cert: z.string().nullish() + }) + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const roles = await server.services.migration.getVaultKubernetesRoles({ + actor: req.permission, + namespace: req.query.namespace, + mountPath: req.query.mountPath + }); + + return { roles }; + } + }); + server.route({ method: "GET", url: "/vault/secret-paths", diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts index 38f97700c..9a6b95567 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts @@ -21,6 +21,8 @@ import { THCVaultKubernetesAuthConfig, THCVaultKubernetesAuthRole, THCVaultKubernetesAuthRoleWithConfig, + THCVaultKubernetesRole, + THCVaultKubernetesSecretsConfig, THCVaultMount, THCVaultMountResponse } from "./hc-vault-connection-types"; @@ -799,3 +801,122 @@ export const getHCVaultKubernetesAuthRoles = async ( }); } }; + +export const getHCVaultKubernetesRoles = async ( + namespace: string, + mountPath: string, + connection: THCVaultConnection, + gatewayService: Pick +): Promise => { + // Remove trailing slash from mount path + const cleanMountPath = mountPath.endsWith("/") ? mountPath.slice(0, -1) : mountPath; + + try { + const instanceUrl = await getHCVaultInstanceUrl(connection); + const accessToken = await getHCVaultAccessToken(connection, gatewayService); + // 1. Get the Kubernetes secrets engine configuration for this mount + const { data: configResponse } = await requestWithHCVaultGateway<{ data: THCVaultKubernetesSecretsConfig }>( + connection, + gatewayService, + { + url: `${instanceUrl}/v1/${cleanMountPath}/config`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + } + ); + + const kubernetesConfig = configResponse.data; + + // 2. List all roles in this mount + let roleNames: string[] = []; + try { + const { data: roleListResponse } = await requestWithHCVaultGateway<{ data: { keys: string[] } }>( + connection, + gatewayService, + { + url: `${instanceUrl}/v1/${cleanMountPath}/roles?list=true`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + } + ); + roleNames = roleListResponse.data.keys || []; + } catch (error) { + // Vault returns 404 when no roles are configured yet + if (error && typeof error === "object" && "response" in error) { + const axiosError = error as { response?: { status?: number } }; + if (axiosError.response?.status === 404) { + return []; + } + } + + throw error; + } + + if (!roleNames || roleNames.length === 0) { + return []; + } + + // 3. Fetch details for each role with concurrency control + const limiter = createConcurrencyLimiter(HC_VAULT_CONCURRENCY_LIMIT); + + const roleDetailsPromises = roleNames.map((roleName) => + limiter(async () => { + const { data: roleResponse } = await requestWithHCVaultGateway<{ + data: { + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + }; + }>(connection, gatewayService, { + url: `${instanceUrl}/v1/${cleanMountPath}/roles/${roleName}`, + method: "GET", + headers: { + "X-Vault-Token": accessToken, + "X-Vault-Namespace": namespace + } + }); + + // 4. Merge the role with the config + return { + ...roleResponse.data, + name: roleName, + config: kubernetesConfig, + mountPath: cleanMountPath + } as THCVaultKubernetesRole; + }) + ); + + const roles = await Promise.all(roleDetailsPromises); + + return roles; + } catch (error: unknown) { + logger.error(error, "Unable to list HC Vault Kubernetes secrets engine roles"); + + if (error instanceof AxiosError) { + const errorMessage = + (error.response?.data as { errors?: string[] })?.errors?.[0] || error.message || "Unknown error"; + throw new BadRequestError({ + message: `Failed to list Kubernetes secrets engine roles: ${errorMessage}` + }); + } + + throw new BadRequestError({ + message: "Unable to list Kubernetes secrets engine roles from HashiCorp Vault" + }); + } +}; diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts index d25dbc9e6..2b1956c84 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-types.ts @@ -95,3 +95,26 @@ export type THCVaultKubernetesAuthRoleWithConfig = THCVaultKubernetesAuthRole & config: THCVaultKubernetesAuthConfig; mountPath: string; }; + +export type THCVaultKubernetesSecretsConfig = { + kubernetes_host: string; + kubernetes_ca_cert?: string; +}; + +export type THCVaultKubernetesRole = { + name: string; + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + config: THCVaultKubernetesSecretsConfig; + mountPath: string; +}; diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index 4192ffcd5..6298a4932 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -18,6 +18,7 @@ import { TAppConnectionServiceFactory } from "../app-connection/app-connection-s import { getHCVaultAuthMounts, getHCVaultKubernetesAuthRoles, + getHCVaultKubernetesRoles, getHCVaultSecretsForPath, HCVaultAuthType, listHCVaultMounts, @@ -747,6 +748,55 @@ export const externalMigrationServiceFactory = ({ return roles; }; + const getVaultKubernetesRoles = async ({ + actor, + namespace, + mountPath + }: { + actor: OrgServiceActor; + namespace: string; + mountPath: string; + }) => { + const { hasRole } = await permissionService.getOrgPermission( + actor.type, + actor.id, + actor.orgId, + actor.authMethod, + actor.orgId + ); + + if (!hasRole(OrgMembershipRole.Admin)) { + throw new ForbiddenRequestError({ message: "Only admins can get Kubernetes roles" }); + } + + const vaultConfig = await vaultExternalMigrationConfigDAL.findOne({ + orgId: actor.orgId, + namespace + }); + + if (!vaultConfig) { + throw new NotFoundError({ message: "Vault migration config not found for this namespace" }); + } + + if (!vaultConfig.connection) { + throw new BadRequestError({ message: "Vault migration connection is not configured for this namespace" }); + } + + const credentials = await decryptAppConnectionCredentials({ + orgId: vaultConfig.orgId, + encryptedCredentials: vaultConfig.connection.encryptedCredentials, + kmsService, + projectId: null + }); + + const connection = { + ...vaultConfig.connection, + credentials + } as THCVaultConnection; + + return getHCVaultKubernetesRoles(namespace, mountPath, connection, gatewayService); + }; + return { importEnvKeyData, importVaultData, @@ -761,6 +811,7 @@ export const externalMigrationServiceFactory = ({ getVaultAuthMounts, getVaultSecretPaths, importVaultSecrets, - getVaultKubernetesAuthRoles + getVaultKubernetesAuthRoles, + getVaultKubernetesRoles }; }; diff --git a/docs/documentation/platform/external-migrations/vault.mdx b/docs/documentation/platform/external-migrations/vault.mdx index e267cc5e9..c58e1ea6b 100644 --- a/docs/documentation/platform/external-migrations/vault.mdx +++ b/docs/documentation/platform/external-migrations/vault.mdx @@ -26,6 +26,12 @@ Infisical provides two approaches for migrating from HashiCorp Vault. This migration approach lets you set up a connection to your Vault instance once, then import specific resources as needed throughout Infisical. + + **Organization Admin Access Required:** All in-platform migration features + (importing secrets, Kubernetes configurations, and policies from Vault) are + only accessible to organization admins. + + ### Step 1: Set Up Your Vault Connection @@ -84,6 +90,19 @@ This migration approach lets you set up a connection to your Vault instance once path "auth/+/role/*" { capabilities = ["read"] } + + # Kubernetes secrets engine - for reading secrets engine configuration and roles + path "+/config" { + capabilities = ["read"] + } + + path "+/roles" { + capabilities = ["list"] + } + + path "+/roles/*" { + capabilities = ["read"] + } ``` @@ -160,6 +179,34 @@ The authentication settings (service accounts, TTL, policies, etc.) will be auto must be manually provided in the form after importing the configuration. +#### Import Kubernetes Dynamic Secret Configurations + +When creating a Kubernetes dynamic secret, you can import the configuration from a Vault Kubernetes secrets engine role: + +1. Navigate to your project and select an environment +2. Click **"+ Add Secret"** dropdown and choose **"Dynamic Secret"** +3. Select **Kubernetes** as the provider +4. Click **"Load from Vault"** at the top of the form + + ![Load Kubernetes Dynamic Secret from Vault](/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png) + +5. Select your Vault namespace, Kubernetes secrets engine mount, and role +6. Click **"Load Configuration"** + +The form will be automatically populated with the role's configuration including: + +- Cluster URL and CA certificate +- Credential type (Static or Dynamic) +- Service account name or Kubernetes role settings +- Allowed namespaces +- Token TTL values +- Token audiences + + + Sensitive values like cluster tokens cannot be retrieved from Vault and must + be manually provided in the form after loading the configuration. + + #### Import and Translate Access Control Policies When configuring project role-based access control, you can import Vault HCL policies and automatically translate them to Infisical permissions. diff --git a/docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png b/docs/images/platform/external-migrations/vault-in-platform/import-vault-kubernetes-role-modal.png new file mode 100644 index 0000000000000000000000000000000000000000..7cb34563771b8caf23332ceaaa4477987c544211 GIT binary patch literal 417789 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

AodVcqFl)ah3$Ct6)(s#3(Q-fH_T|6!>m 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

nJQ(MZ@p0#pl14}STOvFGJgqS3$XnoH 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 literal 0 HcmV?d00001 diff --git a/frontend/src/hooks/api/migration/queries.tsx b/frontend/src/hooks/api/migration/queries.tsx index e4ce6824d..198bf887b 100644 --- a/frontend/src/hooks/api/migration/queries.tsx +++ b/frontend/src/hooks/api/migration/queries.tsx @@ -5,7 +5,8 @@ import { apiRequest } from "@app/config/request"; import { ExternalMigrationProviders, TVaultExternalMigrationConfig, - VaultKubernetesAuthRole + VaultKubernetesAuthRole, + VaultKubernetesRole } from "./types"; export const externalMigrationQueryKeys = { @@ -31,6 +32,11 @@ export const externalMigrationQueryKeys = { "vault-kubernetes-auth-roles", namespace, mountPath + ], + vaultKubernetesRoles: (namespace?: string, mountPath?: string) => [ + "vault-kubernetes-roles", + namespace, + mountPath ] }; @@ -172,3 +178,30 @@ export const useGetVaultKubernetesAuthRoles = ( enabled: enabled && !!namespace && !!mountPath }); }; + +export const useGetVaultKubernetesRoles = ( + enabled = true, + namespace?: string, + mountPath?: string +) => { + return useQuery({ + queryKey: externalMigrationQueryKeys.vaultKubernetesRoles(namespace, mountPath), + queryFn: async () => { + if (!namespace || !mountPath) { + throw new Error("Both namespace and mountPath are required"); + } + + const { data } = await apiRequest.get<{ + roles: VaultKubernetesRole[]; + }>("/api/v3/external-migration/vault/kubernetes-roles", { + params: { + namespace, + mountPath + } + }); + + return data.roles; + }, + enabled: enabled && !!namespace && !!mountPath + }); +}; diff --git a/frontend/src/hooks/api/migration/types.ts b/frontend/src/hooks/api/migration/types.ts index f4303ea26..4c101548c 100644 --- a/frontend/src/hooks/api/migration/types.ts +++ b/frontend/src/hooks/api/migration/types.ts @@ -49,3 +49,24 @@ export type VaultKubernetesAuthRole = { disable_local_ca_jwt?: boolean; }; }; + +export type VaultKubernetesRole = { + name: string; + mountPath: string; + allowed_kubernetes_namespaces?: string[]; + allowed_kubernetes_namespace_selector?: string; + token_max_ttl?: number; + token_default_ttl?: number; + token_default_audiences?: string[]; + service_account_name?: string; + kubernetes_role_name?: string; + kubernetes_role_type?: string; + generated_role_rules?: string; + name_template?: string; + extra_annotations?: Record; + extra_labels?: Record; + config: { + kubernetes_host: string; + kubernetes_ca_cert?: string; + }; +}; diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx index 3cfe1c355..9b8624990 100644 --- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx +++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityKubernetesAuthForm.tsx @@ -22,11 +22,12 @@ import { TextArea, Tooltip } from "@app/components/v2"; -import { useOrganization, useSubscription } from "@app/context"; +import { useOrganization, useOrgPermission, useSubscription } from "@app/context"; import { OrgGatewayPermissionActions, OrgPermissionSubjects } from "@app/context/OrgPermissionContext/types"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { gatewaysQueryKeys, useAddIdentityKubernetesAuth, @@ -129,6 +130,8 @@ export const IdentityKubernetesAuthForm = ({ ] as const); const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs(); const hasVaultConnection = vaultConfigs.some((config) => config.connectionId); + const { hasOrgRole } = useOrgPermission(); + const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const { control, @@ -409,20 +412,29 @@ export const IdentityKubernetesAuthForm = ({ Load values from HashiCorp Vault

- + +
)}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx index 182bb1d80..916b3818b 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx @@ -55,6 +55,7 @@ import { ProjectPermissionActions, ProjectPermissionDynamicSecretActions, ProjectPermissionSub, + useOrgPermission, useProject, useProjectPermission, useSubscription @@ -64,6 +65,7 @@ import { ProjectPermissionSecretActions, ProjectPermissionSecretRotationActions } from "@app/context/ProjectPermissionContext/types"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { usePopUp } from "@app/hooks"; import { useCreateFolder, @@ -203,6 +205,8 @@ export const ActionBar = ({ const { permission } = useProjectPermission(); const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs(); const hasVaultConnection = vaultConfigs.some((config) => config.connectionId); + const { hasOrgRole } = useOrgPermission(); + const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const handleFolderCreate = async (folderName: string, description: string | null) => { try { @@ -1114,25 +1118,33 @@ export const ActionBar = ({ })} > {(isAllowed) => ( - + + )} )} diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index 8e9206695..4c4a8f68c 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -1,5 +1,6 @@ +import { useState } from "react"; import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form"; -import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle, faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; @@ -21,16 +22,22 @@ import { TextArea, Tooltip } from "@app/components/v2"; +import { useOrgPermission } from "@app/context"; import { OrgPermissionSubjects } from "@app/context/OrgPermissionContext"; import { OrgGatewayPermissionActions } from "@app/context/OrgPermissionContext/types"; +import { OrgMembershipRole } from "@app/helpers/roles"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { DynamicSecretProviders, KubernetesDynamicSecretCredentialType } from "@app/hooks/api/dynamicSecret/types"; +import { useGetVaultExternalMigrationConfigs } from "@app/hooks/api/migration/queries"; +import { VaultKubernetesRole } from "@app/hooks/api/migration/types"; import { ProjectEnv } from "@app/hooks/api/types"; import { slugSchema } from "@app/lib/schemas"; +import { VaultKubernetesImportModal } from "./VaultKubernetesImportModal"; + enum RoleType { ClusterRole = "cluster-role", Role = "role" @@ -162,11 +169,14 @@ export const KubernetesInputForm = ({ environments, isSingleEnvironmentMode }: Props) => { + const [isVaultImportModalOpen, setIsVaultImportModalOpen] = useState(false); + const { control, formState: { isSubmitting }, handleSubmit, - watch + watch, + setValue } = useForm({ resolver: zodResolver(formSchema), defaultValues: { @@ -193,11 +203,81 @@ export const KubernetesInputForm = ({ const createDynamicSecret = useCreateDynamicSecret(); const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); + const { data: vaultConfigs = [] } = useGetVaultExternalMigrationConfigs(); + const hasVaultConnection = vaultConfigs.some((config) => config.connectionId); + const { hasOrgRole } = useOrgPermission(); + const isOrgAdmin = hasOrgRole(OrgMembershipRole.Admin); const sslEnabled = watch("provider.sslEnabled"); const credentialType = watch("provider.credentialType"); const authMethod = watch("provider.authMethod"); + const handleVaultImport = (role: VaultKubernetesRole) => { + try { + setValue("name", role.name); + + setValue("provider.url", role.config.kubernetes_host); + + // Set CA certificate if available + if (role.config.kubernetes_ca_cert) { + setValue("provider.ca", role.config.kubernetes_ca_cert); + setValue("provider.sslEnabled", true); + } + + // Determine credential type based on role configuration + if (role.service_account_name) { + // Static credential type + setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Static); + setValue("provider.serviceAccountName", role.service_account_name); + + // Set namespace (single namespace for static) + if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) { + setValue("provider.namespace", role.allowed_kubernetes_namespaces[0]); + } + } else if (role.kubernetes_role_name) { + // Dynamic credential type + setValue("provider.credentialType", KubernetesDynamicSecretCredentialType.Dynamic); + setValue("provider.role", role.kubernetes_role_name); + + // Set role type + const roleType = + role.kubernetes_role_type === "ClusterRole" ? RoleType.ClusterRole : RoleType.Role; + setValue("provider.roleType", roleType); + + // Set allowed namespaces (comma-separated for dynamic) + if (role.allowed_kubernetes_namespaces && role.allowed_kubernetes_namespaces.length > 0) { + setValue("provider.namespace", role.allowed_kubernetes_namespaces.join(", ")); + } + } + + // Set TTLs if available + if (role.token_default_ttl) { + const defaultTTL = `${role.token_default_ttl}s`; + setValue("defaultTTL", defaultTTL); + } + + if (role.token_max_ttl) { + const maxTTL = `${role.token_max_ttl}s`; + setValue("maxTTL", maxTTL); + } + + // Set audiences if available + if (role.token_default_audiences && role.token_default_audiences.length > 0) { + setValue("provider.audiences", role.token_default_audiences); + } + + createNotification({ + type: "info", + text: "Configuration loaded successfully from HashiCorp Vault" + }); + } catch { + createNotification({ + type: "error", + text: "Failed to load configuration from HashiCorp Vault" + }); + } + }; + const handleCreateDynamicSecret = async (formData: TForm) => { const { provider, usernameTemplate, ...rest } = formData; // wait till previous request is finished @@ -235,6 +315,39 @@ export const KubernetesInputForm = ({ return (
+ {hasVaultConnection && ( +
+
+ + Load values from HashiCorp Vault +
+ + + +
+ )} +
-
- Configuration +
+

Configuration

@@ -654,6 +767,11 @@ export const KubernetesInputForm = ({ Cancel
+ ); }; diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx new file mode 100644 index 000000000..3e9412ef9 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/VaultKubernetesImportModal.tsx @@ -0,0 +1,223 @@ +import { useEffect, useState } from "react"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FilterableSelect, + FormControl, + Modal, + ModalClose, + ModalContent +} from "@app/components/v2"; +import { + useGetVaultKubernetesRoles, + useGetVaultMounts, + useGetVaultNamespaces +} from "@app/hooks/api/migration/queries"; +import { VaultKubernetesRole } from "@app/hooks/api/migration/types"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + onImport: (role: VaultKubernetesRole) => void; +}; + +type ContentProps = { + onClose: () => void; + onImport: (role: VaultKubernetesRole) => void; +}; + +const Content = ({ onClose, onImport }: ContentProps) => { + const [selectedNamespace, setSelectedNamespace] = useState(null); + const [selectedMountPath, setSelectedMountPath] = useState(null); + const [selectedRole, setSelectedRole] = useState(null); + const [shouldFetchRoles, setShouldFetchRoles] = useState(false); + const [shouldFetchMounts, setShouldFetchMounts] = useState(false); + + const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces(); + const { data: roles, isLoading: isLoadingRoles } = useGetVaultKubernetesRoles( + shouldFetchRoles, + selectedNamespace ?? undefined, + selectedMountPath ?? undefined + ); + const { data: mounts, isLoading: isLoadingMounts } = useGetVaultMounts( + shouldFetchMounts, + selectedNamespace ?? undefined + ); + + // Filter to only show Kubernetes mounts + const kubernetesMounts = mounts?.filter((mount) => mount.type === "kubernetes"); + + // Enable fetching mounts when namespace is selected + useEffect(() => { + if (selectedNamespace) { + setShouldFetchMounts(true); + } + }, [selectedNamespace]); + + // Enable fetching roles when both namespace and mount path are selected + useEffect(() => { + if (selectedNamespace && selectedMountPath) { + setShouldFetchRoles(true); + } else { + setShouldFetchRoles(false); + } + }, [selectedNamespace, selectedMountPath]); + + const handleImport = () => { + if (!selectedRole) { + createNotification({ + type: "error", + text: "Please select a Vault Kubernetes role to load" + }); + return; + } + + if (!selectedNamespace) { + createNotification({ type: "error", text: "Please select a namespace" }); + return; + } + + if (!mounts || mounts.length === 0) { + createNotification({ + type: "error", + text: "No Vault mounts found. Please ensure you have Kubernetes secrets engine configured." + }); + return; + } + + onImport(selectedRole); + onClose(); + }; + + return ( + <> +
+
+ +
+

+ Select a Kubernetes secrets engine role from Vault to pre-fill the form with its + configuration including cluster URL, CA certificate, TTL settings, etc. +

+
+
+
+ + + <> + ns.name === selectedNamespace)} + onChange={(value) => { + if (value && !Array.isArray(value)) { + const namespace = value as { id: string; name: string }; + setSelectedNamespace(namespace.name); + setSelectedMountPath(null); + setSelectedRole(null); + } + }} + options={namespaces || []} + getOptionValue={(option) => option.name} + getOptionLabel={(option) => (option.name === "/" ? "root" : option.name)} + isDisabled={isLoadingNamespaces} + placeholder="Select namespace..." + className="w-full" + /> +

+ Select the Vault namespace to fetch available Kubernetes secrets engines +

+ +
+ + + <> + mount.path === selectedMountPath)} + onChange={(value) => { + if (value && !Array.isArray(value)) { + const mount = value as { path: string; type: string; version: string | null }; + setSelectedMountPath(mount.path.replace(/\/$/, "")); // Remove trailing slash + setSelectedRole(null); + } + }} + options={kubernetesMounts || []} + getOptionValue={(option) => option.path} + getOptionLabel={(option) => option.path.replace(/\/$/, "")} + isDisabled={isLoadingMounts || !kubernetesMounts?.length} + placeholder="Select Kubernetes secrets engine..." + className="w-full" + /> +

+ Choose a Kubernetes secrets engine mount to list available roles +

+ +
+ + + <> + { + if (value && !Array.isArray(value)) { + setSelectedRole(value as VaultKubernetesRole); + } else { + setSelectedRole(null); + } + }} + options={roles || []} + getOptionValue={(option) => option.name} + getOptionLabel={(option) => option.name} + isDisabled={isLoadingRoles || !roles?.length || !selectedMountPath} + placeholder={ + !selectedMountPath ? "Select a mount path first..." : "Select a role to load..." + } + isClearable + className="w-full" + /> +

+ Choose a Kubernetes role from the selected mount to load its configuration +

+ +
+ +
+ + + + +
+ + ); +}; + +export const VaultKubernetesImportModal = ({ isOpen, onOpenChange, onImport }: Props) => { + return ( + + + onOpenChange(false)} onImport={onImport} /> + + + ); +}; From ef22fb4366894b3432ef95cc3f909322501e02f6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 23:20:22 +0400 Subject: [PATCH 042/224] Update hsm-fns.ts --- backend/src/ee/services/hsm/hsm-fns.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index 2603f80bd..f4d0b8801 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -31,8 +31,6 @@ export const initializeHsmModule = (envConfig: Pick Date: Wed, 22 Oct 2025 03:28:54 +0800 Subject: [PATCH 043/224] misc: added handling for non-string vault values for migration --- .../hc-vault/hc-vault-connection-fns.ts | 21 +++++++++++++++++-- .../external-migration-service.ts | 3 ++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts index 38f97700c..425c9c4d2 100644 --- a/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts +++ b/backend/src/services/app-connection/hc-vault/hc-vault-connection-fns.ts @@ -25,6 +25,23 @@ import { THCVaultMountResponse } from "./hc-vault-connection-types"; +// HashiCorp Vault stores JSON data, so values can be any valid JSON type +type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue }; + +export const convertVaultValueToString = (value: JsonValue): string => { + if (value === null) { + return ""; + } + if (typeof value === "string") { + return value; + } + if (typeof value === "number" || typeof value === "boolean") { + return String(value); + } + // For objects and arrays, serialize as JSON + return JSON.stringify(value); +}; + // Concurrency limit for HC Vault API requests to avoid rate limiting const HC_VAULT_CONCURRENCY_LIMIT = 20; @@ -598,7 +615,7 @@ export const getHCVaultSecretsForPath = async ( // For KV v2: /v1/{mount}/data/{path} const { data } = await requestWithHCVaultGateway<{ data: { - data: Record; // KV v2 has nested data structure + data: Record; // KV v2 has nested data structure, supports all JSON types metadata: { created_time: string; deletion_time: string; @@ -620,7 +637,7 @@ export const getHCVaultSecretsForPath = async ( // For KV v1: /v1/{mount}/{path} const { data } = await requestWithHCVaultGateway<{ - data: Record; // KV v1 has flat data structure + data: Record; // KV v1 has flat data structure, supports all JSON types lease_duration: number; lease_id: string; renewable: boolean; diff --git a/backend/src/services/external-migration/external-migration-service.ts b/backend/src/services/external-migration/external-migration-service.ts index 4192ffcd5..469b27bb3 100644 --- a/backend/src/services/external-migration/external-migration-service.ts +++ b/backend/src/services/external-migration/external-migration-service.ts @@ -16,6 +16,7 @@ import { AppConnection } from "../app-connection/app-connection-enums"; import { decryptAppConnectionCredentials } from "../app-connection/app-connection-fns"; import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service"; import { + convertVaultValueToString, getHCVaultAuthMounts, getHCVaultKubernetesAuthRoles, getHCVaultSecretsForPath, @@ -581,7 +582,7 @@ export const externalMigrationServiceFactory = ({ projectId, secrets: Object.entries(vaultSecrets).map(([secretKey, secretValue]) => ({ secretKey, - secretValue + secretValue: convertVaultValueToString(secretValue) })) }); From 9d19e37710116071f5620e565618bf7f1acd5eb2 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 22 Oct 2025 03:34:37 +0800 Subject: [PATCH 044/224] misc: addressed greptile --- .../ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx index 4c4a8f68c..e16451564 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/KubernetesInputForm.tsx @@ -196,7 +196,7 @@ export const KubernetesInputForm = ({ } }); - const { fields, append, remove } = useFieldArray({ + const { fields, append, remove, replace } = useFieldArray({ control, name: "provider.audiences" }); @@ -263,7 +263,7 @@ export const KubernetesInputForm = ({ // Set audiences if available if (role.token_default_audiences && role.token_default_audiences.length > 0) { - setValue("provider.audiences", role.token_default_audiences); + replace(role.token_default_audiences); } createNotification({ From 10cebfbbe29b824a6ef5b7def3e475e91195e19e Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Tue, 21 Oct 2025 16:37:08 -0300 Subject: [PATCH 045/224] Check identitiesUsed instead of identityLimit on getPlan --- backend/src/ee/services/license/license-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/services/license/license-service.ts b/backend/src/ee/services/license/license-service.ts index fba9d0cca..817f4609c 100644 --- a/backend/src/ee/services/license/license-service.ts +++ b/backend/src/ee/services/license/license-service.ts @@ -212,9 +212,8 @@ export const licenseServiceFactory = ({ const membersUsed = await licenseDAL.countOfOrgMembers(orgId); currentPlan.membersUsed = membersUsed; const identityUsed = await licenseDAL.countOrgUsersAndIdentities(orgId); - currentPlan.identitiesUsed = identityUsed; - if (currentPlan.identityLimit && currentPlan.identityLimit !== identityUsed) { + if (currentPlan?.identitiesUsed && currentPlan.identitiesUsed !== identityUsed) { try { await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, { quantity: membersUsed, @@ -227,6 +226,7 @@ export const licenseServiceFactory = ({ ); } } + currentPlan.identitiesUsed = identityUsed; await keyStore.setItemWithExpiry( FEATURE_CACHE_KEY(org.id), From 98b2c556b8dc66b786997ebc61c98214284490a8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 21 Oct 2025 16:45:57 -0300 Subject: [PATCH 046/224] docs: fix syntax error in login command example for fetching secrets --- docs/cli/commands/login.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli/commands/login.mdx b/docs/cli/commands/login.mdx index 50b96baa1..16fef7985 100644 --- a/docs/cli/commands/login.mdx +++ b/docs/cli/commands/login.mdx @@ -558,7 +558,7 @@ In this example we'll be using the `universal-auth` method to login to obtain an ```bash - infisical secrets --projectId= --env=dev --recursive ``` This command will fetch all secrets from the `dev` environment in your project, including all secrets in subfolders. From 65b36e6232cfbfb4e4a3c88752a317c000babba6 Mon Sep 17 00:00:00 2001 From: x032205 Date: Tue, 21 Oct 2025 15:57:44 -0400 Subject: [PATCH 047/224] use __INFISICAL_UNCHANGED__ instead of empty string for unchanged placeholder --- .../pam-account/pam-account-service.ts | 23 +++++++++++++++---- .../pam-resource/pam-resource-service.ts | 5 +++- .../PamAccountForm/PostgresAccountForm.tsx | 2 +- .../shared/SqlAccountFields.tsx | 4 ++-- .../PamResourceForm/PostgresResourceForm.tsx | 2 +- .../shared/SqlRotateAccountFields.tsx | 4 ++-- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/backend/src/ee/services/pam-account/pam-account-service.ts b/backend/src/ee/services/pam-account/pam-account-service.ts index 83e488231..2dfdec196 100644 --- a/backend/src/ee/services/pam-account/pam-account-service.ts +++ b/backend/src/ee/services/pam-account/pam-account-service.ts @@ -150,7 +150,12 @@ export const pamAccountServiceFactory = ({ return { ...(await decryptAccount(account, resource.projectId, kmsService)), - resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } + resource: { + id: resource.id, + name: resource.name, + resourceType: resource.resourceType, + rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials + } }; } catch (err) { if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { @@ -241,7 +246,7 @@ export const pamAccountServiceFactory = ({ // Logic to prevent overwriting unedited censored values const finalCredentials = { ...credentials }; - if (credentials.password === "******") { + if (credentials.password === "__INFISICAL_UNCHANGED__") { const decryptedCredentials = await decryptAccountCredentials({ encryptedCredentials: account.encryptedCredentials, projectId: account.projectId, @@ -269,7 +274,12 @@ export const pamAccountServiceFactory = ({ return { ...(await decryptAccount(updatedAccount, account.projectId, kmsService)), - resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } + resource: { + id: resource.id, + name: resource.name, + resourceType: resource.resourceType, + rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials + } }; }; @@ -308,7 +318,12 @@ export const pamAccountServiceFactory = ({ return { ...(await decryptAccount(deletedAccount, account.projectId, kmsService)), - resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } + resource: { + id: resource.id, + name: resource.name, + resourceType: resource.resourceType, + rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials + } }; }; diff --git a/backend/src/ee/services/pam-resource/pam-resource-service.ts b/backend/src/ee/services/pam-resource/pam-resource-service.ts index 9ec702b24..d97905dbe 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-service.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-service.ts @@ -194,7 +194,10 @@ export const pamResourceServiceFactory = ({ // Logic to prevent overwriting unedited censored values const finalCredentials = { ...rotationAccountCredentials }; - if (resource.encryptedRotationAccountCredentials && rotationAccountCredentials.password === "******") { + if ( + resource.encryptedRotationAccountCredentials && + rotationAccountCredentials.password === "__INFISICAL_UNCHANGED__" + ) { const decryptedCredentials = await decryptAccountCredentials({ encryptedCredentials: resource.encryptedRotationAccountCredentials, projectId: resource.projectId, diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx index c353a278b..17d68bdf0 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PostgresAccountForm.tsx @@ -34,7 +34,7 @@ export const PostgresAccountForm = ({ account, resourceId, resourceType, onSubmi ...account, credentials: { ...account.credentials, - password: "******" + password: "__INFISICAL_UNCHANGED__" } } : undefined diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx index 7d9e85c44..0fd5760f3 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/SqlAccountFields.tsx @@ -38,14 +38,14 @@ export const SqlAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { type={showPassword ? "text" : "password"} autoComplete="new-password" onFocus={() => { - if (isUpdate && field.value === "******") { + if (isUpdate && field.value === "__INFISICAL_UNCHANGED__") { field.onChange(""); } setShowPassword(true); }} onBlur={() => { if (isUpdate && field.value === "") { - field.onChange("******"); + field.onChange("__INFISICAL_UNCHANGED__"); } setShowPassword(false); }} diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx index 20ac710c3..2c53c89df 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx @@ -37,7 +37,7 @@ export const PostgresResourceForm = ({ resource, onSubmit }: Props) => { rotationAccountCredentials: resource.rotationAccountCredentials ? { ...resource.rotationAccountCredentials, - password: "******" + password: "__INFISICAL_UNCHANGED__" } : resource.rotationAccountCredentials } diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx index 869594140..6ce92b8e6 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx @@ -55,14 +55,14 @@ export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { type={showPassword ? "text" : "password"} autoComplete="new-password" onFocus={() => { - if (isUpdate && field.value === "******") { + if (isUpdate && field.value === "__INFISICAL_UNCHANGED__") { field.onChange(""); } setShowPassword(true); }} onBlur={() => { if (isUpdate && field.value === "") { - field.onChange("******"); + field.onChange("__INFISICAL_UNCHANGED__"); } setShowPassword(false); }} From 85daaf806e536f749140a94ffc7bdc0c4085f41f Mon Sep 17 00:00:00 2001 From: x032205 Date: Tue, 21 Oct 2025 16:21:52 -0400 Subject: [PATCH 048/224] Make rotationIntervalSeconds nullable --- .../db/migrations/20251015042917_pam-account-rotation.ts | 2 +- backend/src/db/schemas/pam-accounts.ts | 2 +- .../routes/v1/pam-account-routers/pam-account-endpoints.ts | 2 +- backend/src/ee/services/audit-log/audit-log-types.ts | 4 ++-- backend/src/ee/services/pam-account/pam-account-dal.ts | 1 + .../src/ee/services/pam-resource/pam-resource-schemas.ts | 4 ++-- frontend/src/hooks/api/pam/types/base-account.ts | 2 +- .../components/PamAccountForm/RotateAccountFields.tsx | 6 +++--- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/backend/src/db/migrations/20251015042917_pam-account-rotation.ts b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts index eee9fd1fd..4e9fc90d4 100644 --- a/backend/src/db/migrations/20251015042917_pam-account-rotation.ts +++ b/backend/src/db/migrations/20251015042917_pam-account-rotation.ts @@ -10,7 +10,7 @@ export async function up(knex: Knex): Promise { ) { await knex.schema.alterTable(TableName.PamAccount, (t) => { t.boolean("rotationEnabled").notNullable().defaultTo(false); - t.integer("rotationIntervalSeconds").notNullable(); + t.integer("rotationIntervalSeconds").nullable(); t.timestamp("lastRotatedAt").nullable(); }); } diff --git a/backend/src/db/schemas/pam-accounts.ts b/backend/src/db/schemas/pam-accounts.ts index 9bcfa5cec..7e78e0874 100644 --- a/backend/src/db/schemas/pam-accounts.ts +++ b/backend/src/db/schemas/pam-accounts.ts @@ -20,7 +20,7 @@ export const PamAccountsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), rotationEnabled: z.boolean().default(false), - rotationIntervalSeconds: z.number().default(2592000), + rotationIntervalSeconds: z.number().nullable().optional(), lastRotatedAt: z.date().nullable().optional() }); diff --git a/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts b/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts index dccaaa8ca..44e2a5ea1 100644 --- a/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts +++ b/backend/src/ee/routes/v1/pam-account-routers/pam-account-endpoints.ts @@ -23,7 +23,7 @@ export const registerPamResourceEndpoints = ({ name: C["name"]; description?: C["description"]; rotationEnabled: C["rotationEnabled"]; - rotationIntervalSeconds: C["rotationIntervalSeconds"]; + rotationIntervalSeconds?: C["rotationIntervalSeconds"]; }>; updateAccountSchema: z.ZodType<{ credentials?: C["credentials"]; diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index 94561e6d8..8569d764b 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -3798,7 +3798,7 @@ interface PamAccountCreateEvent { name: string; description?: string | null; rotationEnabled: boolean; - rotationIntervalSeconds: number; + rotationIntervalSeconds?: number | null; }; } @@ -3811,7 +3811,7 @@ interface PamAccountUpdateEvent { name?: string; description?: string | null; rotationEnabled?: boolean; - rotationIntervalSeconds?: number; + rotationIntervalSeconds?: number | null; }; } diff --git a/backend/src/ee/services/pam-account/pam-account-dal.ts b/backend/src/ee/services/pam-account/pam-account-dal.ts index e0377871c..4c8f5136a 100644 --- a/backend/src/ee/services/pam-account/pam-account-dal.ts +++ b/backend/src/ee/services/pam-account/pam-account-dal.ts @@ -49,6 +49,7 @@ export const pamAccountDALFactory = (db: TDbClient) => { const accounts = await dbClient(TableName.PamAccount) .innerJoin(TableName.PamResource, `${TableName.PamAccount}.resourceId`, `${TableName.PamResource}.id`) .whereNotNull(`${TableName.PamResource}.encryptedRotationAccountCredentials`) + .whereNotNull(`${TableName.PamAccount}.rotationIntervalSeconds`) .whereRaw( `COALESCE("${TableName.PamAccount}"."lastRotatedAt", "${TableName.PamAccount}"."createdAt") + "${TableName.PamAccount}"."rotationIntervalSeconds" * interval '1 second' < NOW()` ) diff --git a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts index 8f4752f4e..7f6165d88 100644 --- a/backend/src/ee/services/pam-resource/pam-resource-schemas.ts +++ b/backend/src/ee/services/pam-resource/pam-resource-schemas.ts @@ -42,12 +42,12 @@ export const BaseCreatePamAccountSchema = z.object({ name: slugSchema({ field: "name" }), description: z.string().max(512).nullable().optional(), rotationEnabled: z.boolean(), - rotationIntervalSeconds: z.number().min(3600) + rotationIntervalSeconds: z.number().min(3600).nullable().optional() }); export const BaseUpdatePamAccountSchema = z.object({ name: slugSchema({ field: "name" }).optional(), description: z.string().max(512).nullable().optional(), rotationEnabled: z.boolean().optional(), - rotationIntervalSeconds: z.number().min(3600).optional() + rotationIntervalSeconds: z.number().min(3600).nullable().optional() }); diff --git a/frontend/src/hooks/api/pam/types/base-account.ts b/frontend/src/hooks/api/pam/types/base-account.ts index 09ad6550d..20cb7aa60 100644 --- a/frontend/src/hooks/api/pam/types/base-account.ts +++ b/frontend/src/hooks/api/pam/types/base-account.ts @@ -14,7 +14,7 @@ export interface TBasePamAccount { name: string; description?: string | null; rotationEnabled: boolean; - rotationIntervalSeconds: number; + rotationIntervalSeconds?: number | null; lastRotatedAt?: string | null; createdAt: string; updatedAt: string; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx index 2739e1cd5..a73fad58b 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/RotateAccountFields.tsx @@ -6,7 +6,7 @@ import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/component export const rotateAccountFieldsSchema = z.object({ rotationEnabled: z.boolean(), - rotationIntervalSeconds: z.number() + rotationIntervalSeconds: z.number().nullable().optional() }); export const RotateAccountFields = ({ @@ -16,7 +16,7 @@ export const RotateAccountFields = ({ }) => { const { control, watch } = useFormContext<{ rotationEnabled: boolean; - rotationIntervalSeconds: number; + rotationIntervalSeconds?: number | null; }>(); const rotationEnabled = watch("rotationEnabled"); @@ -62,7 +62,7 @@ export const RotateAccountFields = ({ className="mb-0" > onChange(val)} + className="w-full border border-mineshaft-500" + position="popper" + dropdownContainerClassName="max-w-none" + > + {Object.values(NorthflankConnectionMethod).map((method) => { + return ( + + {getAppConnectionMethodDetails(method).name}{" "} + + ); + })} + + + )} + /> + ( + + onChange(e.target.value)} + /> + + )} + /> +
+ + + + +
+ + + ); +}; + diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx index 8cd28ea91..320abf2df 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx @@ -72,6 +72,7 @@ export const AppConnectionsSelect = ({ onSelect, projectType }: Props) => { return ( , - - ]} - > - {isNonConflictingUpload ? ( -
- Are you sure you want to import {createSecretCount} secret - {createSecretCount > 1 ? "s" : ""} to this environment? -
- ) : ( -
-
Your project already contains the following {updateSecretCount} secrets:
-
- {Object.keys((popUp?.confirmUpload?.data as TSecOverwriteOpt)?.update || {}) - ?.map((key) => key) - .join(", ")} -
-
- Are you sure you want to overwrite these secrets - {createSecretCount > 0 - ? ` and import ${createSecretCount} new - one${createSecretCount > 1 ? "s" : ""}` - : ""} - ? These will be applied when you commit your changes. -
-
- )} - - {/* Matrix Import Modal */} Date: Wed, 22 Oct 2025 02:51:58 -0400 Subject: [PATCH 056/224] rewrite to use a multi-step modal flow --- frontend/src/hooks/api/index.tsx | 1 + .../components/GatewayTab/GatewayTab.tsx | 4 +- .../components/DeployGatewayModal.tsx | 245 --------------- .../components/DeployRelayModal.tsx | 205 ------------ .../components/GatewayCliDeploymentMethod.tsx | 297 ++++++++++++++++++ .../components/GatewayDeployModal.tsx | 45 +++ .../GatewayDeploymentMethodSelect.tsx | 56 ++++ .../components/RelayTab/RelayTab.tsx | 4 +- .../components/RelayCliDeploymentMethod.tsx | 268 ++++++++++++++++ .../RelayTab/components/RelayDeployModal.tsx | 45 +++ .../RelayDeploymentMethodSelect.tsx | 56 ++++ 11 files changed, 772 insertions(+), 454 deletions(-) delete mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx delete mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 6167f6e07..e9e80feec 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -31,6 +31,7 @@ export * from "./pkiSubscriber"; export * from "./projects"; export * from "./projectUserAdditionalPrivilege"; export * from "./rateLimit"; +export * from "./relays"; export * from "./roles"; export * from "./scim"; export * from "./secretApproval"; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx index a93694a3d..a2a24015f 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx @@ -48,8 +48,8 @@ import { usePopUp } from "@app/hooks"; import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways"; import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2"; -import { DeployGatewayModal } from "./components/DeployGatewayModal"; import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; +import { GatewayDeployModal } from "./components/GatewayDeployModal"; const GatewayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -269,7 +269,7 @@ export const GatewayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteGateway()} /> - handlePopUpToggle("deployGateway", isOpen)} /> diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx deleted file mode 100644 index 53e061eb5..000000000 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useNavigate } from "@tanstack/react-router"; -import { twMerge } from "tailwind-merge"; - -import { createNotification } from "@app/components/notifications"; -import { - FilterableSelect, - FormLabel, - IconButton, - Input, - Modal, - ModalContent -} from "@app/components/v2"; -import { ROUTE_PATHS } from "@app/const/routes"; -import { useOrganization } from "@app/context"; -import { - useAddIdentityTokenAuth, - useCreateTokenIdentityTokenAuth, - useGetIdentityMembershipOrgs, - useGetIdentityTokenAuth -} from "@app/hooks/api"; -import { useGetRelays } from "@app/hooks/api/relays"; -import { slugSchema } from "@app/lib/schemas"; - -import { RelayOption } from "./RelayOption"; - -type Props = { - isOpen: boolean; - onOpenChange: (isOpen: boolean) => void; -}; - -export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => { - const { protocol, hostname, port } = window.location; - const portSuffix = port && port !== "80" ? `:${port}` : ""; - const siteURL = `${protocol}//${hostname}${portSuffix}`; - - const navigate = useNavigate({ - from: ROUTE_PATHS.Organization.NetworkingPage.path - }); - - const [name, setName] = useState(""); - const [relay, setRelay] = useState(null); - const [identity, setIdentity] = useState(null); - const [identityToken, setIdentityToken] = useState(""); - - useEffect(() => { - if (!isOpen) { - setName(""); - setRelay(null); - setIdentity(null); - setIdentityToken(""); - } - }, [isOpen]); - - const { data: relays, isPending: isRelaysLoading } = useGetRelays(); - - const { currentOrg } = useOrganization(); - const organizationId = currentOrg?.id || ""; - - const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = - useGetIdentityMembershipOrgs({ - organizationId, - limit: 20000 - }); - const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; - - const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); - const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); - const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); - - useEffect(() => { - const generateToken = async () => { - if (!identity) return; - - try { - const { data: identityTokenAuth } = await refetch(); - if (!identityTokenAuth) { - await addIdentityTokenAuth({ - identityId: identity.id, - organizationId, - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] - }); - createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" - }); - } - - const token = await createToken({ - identityId: identity.id, - name: "gateway token (autogenerated)" - }); - setIdentityToken(token.accessToken); - createNotification({ - text: "Automatically generated a token for this identity.", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for identity", - type: "error" - }); - setIdentityToken(""); - } - }; - - generateToken(); - }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); - - const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { - setIdentity(selectedIdentity); - setIdentityToken(""); - }; - - const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); - - const isCommandReady = useMemo( - () => !!name && isNameValid && !!relay && !!identityToken, - [name, isNameValid, relay, identityToken] - ); - - const command = useMemo( - () => - `infisical gateway start --name=${name} --relay=${ - relay?.name || "" - } --domain=${siteURL} --token=${identityToken}`, - [name, relay, identityToken, siteURL] - ); - - return ( - - - - setName(e.target.value)} - placeholder="Enter gateway name..." - isError={!isNameValid} - /> - - - { - if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { - navigate({ - search: (prev) => ({ ...prev, selectedTab: "relays", action: "deploy-relay" }) - }); - return; - } - - setRelay(newValue as SingleValue<{ id: string; name: string }>); - }} - isLoading={isRelaysLoading} - options={[ - { - id: "_create", - name: "Deploy New Relay" - }, - ...(relays || []) - ]} - placeholder="Select relay..." - getOptionLabel={(option) => option.name} - getOptionValue={(option) => option.id} - components={{ Option: RelayOption }} - /> - - - - handleIdentityChange( - e as SingleValue<{ - id: string; - name: string; - }> - ) - } - isLoading={isIdentitiesLoading} - placeholder="Select identity..." - options={identityMembershipOrgs.map((membership) => membership.identity)} - getOptionValue={(option) => option.id} - getOptionLabel={(option) => option.name} - /> - - -
- - { - navigator.clipboard.writeText(command); - - createNotification({ - text: "Command copied to clipboard", - type: "info" - }); - }} - className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} - isDisabled={!isCommandReady} - > - - -
-
- Install the Infisical CLI - - - - - ); -}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx deleted file mode 100644 index 61501c40f..000000000 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; - -import { createNotification } from "@app/components/notifications"; -import { - FilterableSelect, - FormLabel, - IconButton, - Input, - Modal, - ModalContent -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { - useAddIdentityTokenAuth, - useCreateTokenIdentityTokenAuth, - useGetIdentityMembershipOrgs, - useGetIdentityTokenAuth -} from "@app/hooks/api"; -import { slugSchema } from "@app/lib/schemas"; - -type Props = { - isOpen: boolean; - onOpenChange: (isOpen: boolean) => void; -}; - -export const DeployRelayModal = ({ isOpen, onOpenChange }: Props) => { - const { protocol, hostname, port } = window.location; - const portSuffix = port && port !== "80" ? `:${port}` : ""; - const siteURL = `${protocol}//${hostname}${portSuffix}`; - - const [name, setName] = useState(""); - const [host, setHost] = useState(""); - const [identity, setIdentity] = useState(null); - const [identityToken, setIdentityToken] = useState(""); - - useEffect(() => { - if (!isOpen) { - setName(""); - setHost(""); - setIdentity(null); - setIdentityToken(""); - } - }, [isOpen]); - - const { currentOrg } = useOrganization(); - const organizationId = currentOrg?.id || ""; - - const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = - useGetIdentityMembershipOrgs({ - organizationId, - limit: 20000 - }); - const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; - - const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); - const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); - const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); - - useEffect(() => { - const generateToken = async () => { - if (!identity) return; - - try { - const { data: identityTokenAuth } = await refetch(); - if (!identityTokenAuth) { - await addIdentityTokenAuth({ - identityId: identity.id, - organizationId, - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] - }); - createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" - }); - } - - const token = await createToken({ - identityId: identity.id, - name: "relay token (autogenerated)" - }); - setIdentityToken(token.accessToken); - createNotification({ - text: "Automatically generated a token for this identity.", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for identity", - type: "error" - }); - setIdentityToken(""); - } - }; - - generateToken(); - }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); - - const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { - setIdentity(selectedIdentity); - setIdentityToken(""); - }; - - const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); - - const isCommandReady = useMemo( - () => !!name && !!host && isNameValid && !!identityToken, - [name, isNameValid, host, identityToken] - ); - - const command = useMemo( - () => - `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`, - [name, siteURL, host, identityToken] - ); - - return ( - - - - setName(e.target.value)} - placeholder="Enter relay name..." - isError={!isNameValid} - /> - - - setHost(e.target.value)} placeholder="0.0.0.0" /> - - - - handleIdentityChange( - e as SingleValue<{ - id: string; - name: string; - }> - ) - } - isLoading={isIdentitiesLoading} - placeholder="Select identity..." - options={identityMembershipOrgs.map((membership) => membership.identity)} - getOptionValue={(option) => option.id} - getOptionLabel={(option) => option.name} - /> - - -
- - { - navigator.clipboard.writeText(command); - - createNotification({ - text: "Command copied to clipboard", - type: "info" - }); - }} - className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} - isDisabled={!isCommandReady} - > - - -
- - Install the Infisical CLI - - -
-
- ); -}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx new file mode 100644 index 000000000..279dc61c9 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -0,0 +1,297 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth, + useGetRelays +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +import { RelayOption } from "./RelayOption"; + +const formSchema = z.object({ + name: slugSchema({ field: "name" }), + instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), + relay: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Relay is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Relay is required" }), + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +export const GatewayCliDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [instanceDomain, setInstanceDomain] = useState(siteURL); + const [relay, setRelay] = useState(null); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { data: relays, isPending: isRelaysLoading } = useGetRelays(); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + const validation = formSchema.safeParse({ name, relay, identity, instanceDomain }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: "gateway token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + const command = useMemo(() => { + const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; + return `infisical gateway start --name=${name} --relay=${ + relay?.name || "" + }${domainFlag} --token=${identityToken}`; + }, [name, relay, identityToken, instanceDomain]); + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(command); + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter gateway name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + { + if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { + navigate({ + search: (prev) => ({ ...prev, selectedTab: "relays", action: "deploy-relay" }) + }); + return; + } + + setRelay(newValue as SingleValue<{ id: string; name: string }>); + }} + isLoading={isRelaysLoading} + options={[ + { + id: "_create", + name: "Deploy New Relay" + }, + ...(relays || []) + ]} + placeholder="Select relay..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id} + components={{ Option: RelayOption }} + /> + {errors.relay &&

{errors.relay}

} + + + setInstanceDomain(e.target.value)} + placeholder="https://app.infisical.com" + isError={Boolean(errors.instanceDomain)} + /> + {errors.instanceDomain && ( +

{errors.instanceDomain}

+ )} + + + + setIdentity( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx new file mode 100644 index 000000000..170ea2eab --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +import { Modal, ModalContent } from "@app/components/v2"; +import { GatewayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect"; + +import { GatewayCliDeploymentMethod } from "./GatewayCliDeploymentMethod"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const GatewayDeploymentInfoMap = { + cli: { name: "CLI", image: "SSH.png", component: GatewayCliDeploymentMethod } +} as const; + +export type GatewayDeploymentMethod = keyof typeof GatewayDeploymentInfoMap; + +const Content = () => { + const [selectedMethod, setSelectedMethod] = useState(null); + + if (selectedMethod) { + const ComponentToRender = GatewayDeploymentInfoMap[selectedMethod]?.component; + if (ComponentToRender) { + return ; + } + } + + return ; +}; + +export const GatewayDeployModal = ({ isOpen, onOpenChange }: Props) => { + return ( + + + + + + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx new file mode 100644 index 000000000..0a1fd5e12 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx @@ -0,0 +1,56 @@ +import { useMemo } from "react"; + +import { + GatewayDeploymentInfoMap, + GatewayDeploymentMethod +} from "@app/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal"; + +type Props = { + onSelect: (method: GatewayDeploymentMethod) => void; +}; + +export const GatewayDeploymentMethodSelect = ({ onSelect }: Props) => { + const deploymentOptions = useMemo( + () => + (Object.keys(GatewayDeploymentInfoMap) as GatewayDeploymentMethod[]).map((method) => ({ + method, + name: GatewayDeploymentInfoMap[method].name, + image: GatewayDeploymentInfoMap[method].image + })), + [] + ); + + const handleResourceSelect = (method: GatewayDeploymentMethod) => { + onSelect(method); + }; + + return ( +
+ {deploymentOptions.map((option) => { + const { image, name } = option; + + return ( + + ); + })} +
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index 8c62bae1c..9ca3e1318 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -46,7 +46,7 @@ import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; -import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; +import { RelayDeployModal } from "./components/RelayDeployModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -258,7 +258,7 @@ export const RelayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteRelay()} /> - handlePopUpToggle("deployRelay", isOpen)} /> diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx new file mode 100644 index 000000000..543d31bae --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx @@ -0,0 +1,268 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +const formSchema = z.object({ + name: slugSchema({ field: "name" }), + host: z.string().min(1, "Host is required"), + + instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +export const RelayCliDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [host, setHost] = useState(""); + + const [instanceDomain, setInstanceDomain] = useState(siteURL); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + const validation = formSchema.safeParse({ name, host, instanceDomain, identity }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: "relay token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + const handleIdentityChange = ( + selectedIdentity: SingleValue<{ + id: string; + name: string; + }> + ) => { + setIdentity(selectedIdentity); + }; + + const command = useMemo(() => { + const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; + return `infisical relay start --name=${name}${domainFlag} --host=${host} --token=${identityToken}`; + }, [name, instanceDomain, host, identityToken]); + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(command); + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + setHost(e.target.value)} + placeholder="0.0.0.0" + isError={Boolean(errors.host)} + /> + {errors.host &&

{errors.host}

} + + + setInstanceDomain(e.target.value)} + placeholder="https://app.infisical.com" + isError={Boolean(errors.instanceDomain)} + /> + {errors.instanceDomain && ( +

{errors.instanceDomain}

+ )} + + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx new file mode 100644 index 000000000..496ecb762 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +import { Modal, ModalContent } from "@app/components/v2"; +import { RelayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect"; + +import { RelayCliDeploymentMethod } from "./RelayCliDeploymentMethod"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const RelayDeploymentInfoMap = { + cli: { name: "CLI", image: "SSH.png", component: RelayCliDeploymentMethod } +} as const; + +export type RelayDeploymentMethod = keyof typeof RelayDeploymentInfoMap; + +const Content = () => { + const [selectedMethod, setSelectedMethod] = useState(null); + + if (selectedMethod) { + const ComponentToRender = RelayDeploymentInfoMap[selectedMethod]?.component; + if (ComponentToRender) { + return ; + } + } + + return ; +}; + +export const RelayDeployModal = ({ isOpen, onOpenChange }: Props) => { + return ( + + + + + + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx new file mode 100644 index 000000000..cb21499e4 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx @@ -0,0 +1,56 @@ +import { useMemo } from "react"; + +import { + RelayDeploymentInfoMap, + RelayDeploymentMethod +} from "@app/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal"; + +type Props = { + onSelect: (method: RelayDeploymentMethod) => void; +}; + +export const RelayDeploymentMethodSelect = ({ onSelect }: Props) => { + const deploymentOptions = useMemo( + () => + (Object.keys(RelayDeploymentInfoMap) as RelayDeploymentMethod[]).map((method) => ({ + method, + name: RelayDeploymentInfoMap[method].name, + image: RelayDeploymentInfoMap[method].image + })), + [] + ); + + const handleResourceSelect = (method: RelayDeploymentMethod) => { + onSelect(method); + }; + + return ( +
+ {deploymentOptions.map((option) => { + const { image, name } = option; + + return ( + + ); + })} +
+ ); +}; From e6a76187a28216825d535f6eaf74845c84e5fc80 Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 22 Oct 2025 03:35:33 -0400 Subject: [PATCH 057/224] improve UI & greptile review fixes --- .../components/GatewayCliDeploymentMethod.tsx | 10 +++++----- .../components/GatewayDeploymentMethodSelect.tsx | 10 ++++------ .../NetworkingPage/components/RelayTab/RelayTab.tsx | 2 +- .../RelayTab/components/RelayCliDeploymentMethod.tsx | 10 +++++----- .../components/RelayDeploymentMethodSelect.tsx | 10 ++++------ 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx index 279dc61c9..a98853bbb 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -125,25 +125,25 @@ export const GatewayCliDeploymentMethod = () => { accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] }); createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" }); } const token = await createToken({ identityId: validatedIdentity.id, - name: "gateway token (autogenerated)" + name: `gateway token for ${name} (autogenerated)` }); setIdentityToken(token.accessToken); createNotification({ - text: "Automatically generated a token for this identity.", + text: "Automatically generated a token for the selected identity.", type: "info" }); setStep("command"); } catch (err) { console.error(err); createNotification({ - text: "Failed to generate token for identity", + text: "Failed to generate token for the selected identity", type: "error" }); setIdentityToken(""); diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx index 0a1fd5e12..099c7d791 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx @@ -27,21 +27,19 @@ export const GatewayDeploymentMethodSelect = ({ onSelect }: Props) => { return (
{deploymentOptions.map((option) => { - const { image, name } = option; + const { image, name, method } = option; return (
-
-

Price

-

- {subscription.status === "trialing" - ? "$0.00 / month" - : `${formatAmount(data.amount)} / ${data.interval}`} -

-
+ {subscription.slug !== "enterprise" ? ( +
+

Price

+

+ {subscription.status === "trialing" ? ( + "$0.00 / month" + ) : ( + <> + {formatAmount(totalAmount)} / {data.interval} + {(subscription.slug === "pro" || subscription.slug === "pro-annual") && ( + 1 ? "users" : "user"} and ${data.identities} ${data.identities > 1 ? "machine identities" : "machine identity"}.`} + className="max-w-lg" + > + + + )} + + )} +

+
+ ) : null}

Subscription renews on

From f1e1e5b0c793126b4c1837623ab74d84c78ef29d Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 22 Oct 2025 22:18:45 +0530 Subject: [PATCH 067/224] fix: SDK contribution docs --- .../contributing/getting-started/overview.mdx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/docs/contributing/getting-started/overview.mdx b/docs/contributing/getting-started/overview.mdx index 35912fc8c..1d79002a4 100644 --- a/docs/contributing/getting-started/overview.mdx +++ b/docs/contributing/getting-started/overview.mdx @@ -7,20 +7,20 @@ To set a strong foundation, this section outlines how we, the community and memb should approach the development and contribution process. ## Code-bases + Infisical has two major code-bases. One for the platform code, and one for SDKs. The contribution process has some key differences between the two, so we've split the documentation into two sections: - The [Infisical Platform](https://github.com/Infisical/infisical), the Infisical platform itself. -- The [Infisical SDK](https://infisical.com/docs/sdks/overview), the official Infisical client SDKs. - - - - - The Infisical platform is the core of the Infisical ecosystem. - - - The SDKs are the official Infisical client libraries, used by developers to easily interact with the Infisical platform. - - +- The Infisical SDKs, please refer to the each individual SDK documentation for more information. + - [Node.js SDK](https://github.com/Infisical/node-sdk-v2) + - [Python SDK](https://github.com/Infisical/python-sdk-official) + - [Java SDK](https://github.com/Infisical/java-sdk) + - [.NET SDK](https://github.com/Infisical/infisical-dotnet-sdk) + - [Go SDK](https://github.com/Infisical/go-sdk) + - [C++ SDK](https://github.com/Infisical/infisical-cpp-sdk) + - [PHP SDK](https://github.com/Infisical/php-sdk) + - [Rust SDK](https://github.com/Infisical/rust-sdk) + - [Ruby SDK](https://github.com/infisical/sdk) ## Community @@ -45,15 +45,12 @@ If you're ever in doubt about whether or not a proposed feature aligns with Infi ## Writing and submitting code -Anyone can contribute code to Infisical. To get started, check out the local development guides for each language. - -- Local development guide for Platform is [here](/contributing/platform/developing). -- Local development guide for SDK is [here](/contributing/sdk/developing). +Anyone can contribute code to Infisical. To get started, check out the local development guide for the platform: +- Local development guide for Platform is [here](/contributing/platform/developing). ## Licensing Most of Infisical's code is under the MIT license, though some paid feature restrictions are covered by a proprietary license. Any third party components incorporated into our code are licensed under the original license provided by the applicable component owner. - From 01126ad3fd82e6dd7a4535a37d7630a77193f6eb Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 22 Oct 2025 22:21:51 +0530 Subject: [PATCH 068/224] fix: greptile review --- docs/contributing/getting-started/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/getting-started/overview.mdx b/docs/contributing/getting-started/overview.mdx index 1d79002a4..396ed8b21 100644 --- a/docs/contributing/getting-started/overview.mdx +++ b/docs/contributing/getting-started/overview.mdx @@ -11,7 +11,7 @@ should approach the development and contribution process. Infisical has two major code-bases. One for the platform code, and one for SDKs. The contribution process has some key differences between the two, so we've split the documentation into two sections: - The [Infisical Platform](https://github.com/Infisical/infisical), the Infisical platform itself. -- The Infisical SDKs, please refer to the each individual SDK documentation for more information. +- The Infisical SDKs, please refer to each individual SDK documentation for more information. - [Node.js SDK](https://github.com/Infisical/node-sdk-v2) - [Python SDK](https://github.com/Infisical/python-sdk-official) - [Java SDK](https://github.com/Infisical/java-sdk) From 5887b64a9e4091aecd48e7a07685e0e5d8d2b87a Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Wed, 22 Oct 2025 14:35:59 -0300 Subject: [PATCH 069/224] Fix getImportReplicatedFolder function to retrieve only the id part of it --- .../components/SecretImportListView/SecretImportListView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx index be492f494..2dbc95020 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx @@ -130,7 +130,8 @@ export const SecretImportListView = ({ const [items, setItems] = useState(secretImports ?? []); const getImportReplicatedFolder = (importPath: string) => { - const cleanImportPath = importPath.replace("/__reserve_replication_", ""); + if (!importPath.includes("/__reserve_replication_")) return; + const cleanImportPath = importPath.split("/__reserve_replication_")[1]; const replicatedFolder = items?.find(({ id }) => id === cleanImportPath); return replicatedFolder; }; From b4a787e2c90901fe18a6afdc0407a27268781cfd Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Wed, 22 Oct 2025 23:06:32 +0530 Subject: [PATCH 070/224] fix: update SDK documentation references to repositories --- docs/contributing/getting-started/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/getting-started/overview.mdx b/docs/contributing/getting-started/overview.mdx index 396ed8b21..1784b77e8 100644 --- a/docs/contributing/getting-started/overview.mdx +++ b/docs/contributing/getting-started/overview.mdx @@ -11,7 +11,7 @@ should approach the development and contribution process. Infisical has two major code-bases. One for the platform code, and one for SDKs. The contribution process has some key differences between the two, so we've split the documentation into two sections: - The [Infisical Platform](https://github.com/Infisical/infisical), the Infisical platform itself. -- The Infisical SDKs, please refer to each individual SDK documentation for more information. +- The Infisical SDKs, please refer to each individual SDK repositories for more information. - [Node.js SDK](https://github.com/Infisical/node-sdk-v2) - [Python SDK](https://github.com/Infisical/python-sdk-official) - [Java SDK](https://github.com/Infisical/java-sdk) From df050096886e306d645e94eb7065d5025087ca23 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Wed, 22 Oct 2025 14:36:54 -0300 Subject: [PATCH 071/224] Add default return on getImportReplicatedFolder --- .../components/SecretImportListView/SecretImportListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx index 2dbc95020..ab4a968d9 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx @@ -130,7 +130,7 @@ export const SecretImportListView = ({ const [items, setItems] = useState(secretImports ?? []); const getImportReplicatedFolder = (importPath: string) => { - if (!importPath.includes("/__reserve_replication_")) return; + if (!importPath.includes("/__reserve_replication_")) return importPath; const cleanImportPath = importPath.split("/__reserve_replication_")[1]; const replicatedFolder = items?.find(({ id }) => id === cleanImportPath); return replicatedFolder; From 22bff72c2af33fb49c4de757c5cec1e4dbac4c36 Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Wed, 22 Oct 2025 14:41:43 -0300 Subject: [PATCH 072/224] Add default return on getImportReplicatedFolder --- .../components/SecretImportListView/SecretImportListView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx index ab4a968d9..763872e9e 100644 --- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx +++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretImportListView/SecretImportListView.tsx @@ -130,7 +130,7 @@ export const SecretImportListView = ({ const [items, setItems] = useState(secretImports ?? []); const getImportReplicatedFolder = (importPath: string) => { - if (!importPath.includes("/__reserve_replication_")) return importPath; + if (!importPath.includes("/__reserve_replication_")) return undefined; const cleanImportPath = importPath.split("/__reserve_replication_")[1]; const replicatedFolder = items?.find(({ id }) => id === cleanImportPath); return replicatedFolder; From 1f5deb9f56a47103bf44dfee1dab87977f54a84e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 22 Oct 2025 22:13:49 +0400 Subject: [PATCH 073/224] Update identity-aws-auth-validators.ts --- .../identity-aws-auth/identity-aws-auth-validators.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts index 098bdcf9a..527d508f5 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts @@ -4,7 +4,10 @@ import { z } from "zod"; const twelveDigitRegex = new RE2(/^\d{12}$/); // akhilmhdh: change this to a normal function later. Checked no redosable at the moment -const arnRegex = new RE2(/^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|\*)$/); + +const arnRegex = new RE2( + /^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|assume-role\/[a-zA-Z0-9_.@+*/-]+|\*)$/ +); export const validateAccountIds = z .string() @@ -52,7 +55,7 @@ export const validatePrincipalArns = z }, { message: - "Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', or 'arn:aws:iam::123456789012:*'." + "Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', 'arn:aws:iam::123456789012:assume-role/RoleName', or 'arn:aws:iam::123456789012:*'." } ) // Transform to normalize the spaces around commas From 3280cf5e30c2a3f4a7553834bccb5dab9c6b68bb Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 22 Oct 2025 22:59:24 +0400 Subject: [PATCH 074/224] fix: aws auth STS assume role --- .../src/services/identity-aws-auth/identity-aws-auth-fns.ts | 6 +++--- .../identity-aws-auth/identity-aws-auth-validators.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts index d0fb4d323..3732cf5c0 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts @@ -2,7 +2,7 @@ interface PrincipalArnEntity { Partition: string; Service: "iam" | "sts"; AccountNumber: string; - Type: "user" | "role" | "instance-profile"; + Type: "user" | "role" | "instance-profile" | "assumed-role"; Path: string; FriendlyName: string; SessionInfo: string; // Only populated for assumed-role @@ -49,7 +49,7 @@ export const extractPrincipalArnEntity = (arn: string): PrincipalArnEntity => { } // assumed roles use a special format where the friendly name is the role name const [roleName, sessionId] = rest; - finalType = "role"; // treat assumed role case as role + finalType = "assumed-role"; friendlyName = roleName; sessionInfo = sessionId; break; @@ -87,5 +87,5 @@ export const extractPrincipalArnEntity = (arn: string): PrincipalArnEntity => { export const extractPrincipalArn = (arn: string) => { const entity = extractPrincipalArnEntity(arn); - return `arn:aws:iam::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}`; + return `arn:aws:${entity.Service}::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}${entity.SessionInfo ? `/${entity.SessionInfo}` : ""}`; }; diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts index 527d508f5..183f3cab5 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-validators.ts @@ -6,7 +6,7 @@ const twelveDigitRegex = new RE2(/^\d{12}$/); // akhilmhdh: change this to a normal function later. Checked no redosable at the moment const arnRegex = new RE2( - /^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|assume-role\/[a-zA-Z0-9_.@+*/-]+|\*)$/ + /^arn:aws:(iam|sts)::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|assumed-role\/[a-zA-Z0-9_.@+*/-]+\/[a-zA-Z0-9_.@+*/-]+|\*)$/ ); export const validateAccountIds = z @@ -55,7 +55,7 @@ export const validatePrincipalArns = z }, { message: - "Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', 'arn:aws:iam::123456789012:assume-role/RoleName', or 'arn:aws:iam::123456789012:*'." + "Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', or 'arn:aws:iam::123456789012:*,', 'arn:aws:sts::123456789012:assumed-role/RoleName/sessionId'." } ) // Transform to normalize the spaces around commas From 9978b3b60a6f9fae8bf0c9a985a3d5c262911362 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 23 Oct 2025 03:00:24 +0800 Subject: [PATCH 075/224] misc: add support for translation preview for vault migration --- .../components/VaultPolicyAnalyzer.utils.ts | 191 ++++++++++++++++++ .../components/VaultPolicyImportModal.tsx | 94 +++++++-- .../components/VaultPolicyPreview.tsx | 92 +++++++++ 3 files changed, 361 insertions(+), 16 deletions(-) create mode 100644 frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyAnalyzer.utils.ts create mode 100644 frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyPreview.tsx diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyAnalyzer.utils.ts b/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyAnalyzer.utils.ts new file mode 100644 index 000000000..9a37e2311 --- /dev/null +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyAnalyzer.utils.ts @@ -0,0 +1,191 @@ +import { VaultMount } from "./VaultPolicyImportModal.utils"; + +export type PolicyBlock = { + id: string; + path: string; + capabilities: string[]; + rawText: string; + startLine: number; + endLine: number; + canTranslate: boolean; + reason?: string; +}; + +export type PolicyLine = { + id: string; + text: string; + lineNumber: number; + type: "comment" | "empty" | "part-of-block" | "other"; + belongsToBlock?: string; +}; + +const hasTranslatableCapabilities = (capabilities: string[], isMetadataPath: boolean): boolean => { + if (isMetadataPath) { + // For metadata/folder paths, only these capabilities create permissions + return capabilities.some((cap) => + ["create", "update", "patch", "delete"].includes(cap.toLowerCase()) + ); + } + // For data/secret paths, all these capabilities create permissions + return capabilities.some((cap) => + ["create", "list", "read", "update", "patch", "delete"].includes(cap.toLowerCase()) + ); +}; + +const canTranslateBlock = ( + path: string, + capabilities: string[], + mounts: VaultMount[] +): { canTranslate: boolean; reason?: string } => { + if (path === "*" || path === "+") { + return { canTranslate: true }; + } + + const isWildcardMount = path.startsWith("*/") || path.startsWith("+/"); + if (isWildcardMount) { + // Check if it's a metadata path + const isMetadata = path.includes("/metadata/"); + if (!hasTranslatableCapabilities(capabilities, isMetadata)) { + return { + canTranslate: false, + reason: isMetadata + ? "Cannot translate list/read capabilities for metadata (Infisical only supports translation for create, update, delete)" + : "No translatable capabilities found" + }; + } + return { canTranslate: true }; + } + + const sortedMounts = [...mounts].sort((a, b) => b.path.length - a.path.length); + const mount = sortedMounts.find((m) => path.startsWith(m.path)); + + if (!mount) { + return { + canTranslate: false, + reason: "KV mount path not found in your Vault configuration" + }; + } + + // Check if it's a KV secret engine + if (mount.type !== "kv" && mount.type !== "generic") { + return { + canTranslate: false, + reason: `Only KV secret engines are supported (found: ${mount.type})` + }; + } + + // Check if it's a metadata path and has valid capabilities + const isKvV2 = mount.version === "2"; + const isMetadata = isKvV2 && path.includes("/metadata/"); + + if (!hasTranslatableCapabilities(capabilities, isMetadata)) { + return { + canTranslate: false, + reason: isMetadata + ? "Cannot translate list/read capabilities for metadata (Infisical only supports translation for create, update, delete)" + : "No translatable capabilities found" + }; + } + + return { canTranslate: true }; +}; + +export const analyzeVaultPolicy = ( + hclPolicy: string, + mounts: VaultMount[] +): { + blocks: PolicyBlock[]; + lines: PolicyLine[]; + translatableCount: number; + nonTranslatableCount: number; +} => { + const blocks: PolicyBlock[] = []; + const lines: PolicyLine[] = []; + let blockIdCounter = 0; + + const policyLines = hclPolicy.split("\n"); + + // Track which lines belong to which blocks + const lineToBlockMap = new Map(); + + // Step 1: Extract all path blocks + try { + const pathRegex = /path\s+"([^"]+)"\s*\{[^}]*capabilities\s*=\s*\[([^\]]+)\][^}]*\}/gi; + let match = pathRegex.exec(hclPolicy); + + while (match !== null) { + const [fullMatch, path, capabilitiesStr] = match; + + const capabilities = capabilitiesStr + .split(",") + .map((c) => c.trim().replace(/["'\s]/g, "")) + .filter((c) => c.length > 0); + + // Find the line numbers for this block + const matchIndex = match.index; + const textBeforeMatch = hclPolicy.substring(0, matchIndex); + const textIncludingMatch = hclPolicy.substring(0, matchIndex + fullMatch.length); + const startLine = textBeforeMatch.split("\n").length; + const endLine = textIncludingMatch.split("\n").length; + + // Determine if this block can be translated + const { canTranslate, reason } = canTranslateBlock(path, capabilities, mounts); + + const blockId = `block-${blockIdCounter}`; + blockIdCounter += 1; + blocks.push({ + id: blockId, + path, + capabilities, + rawText: fullMatch, + startLine, + endLine, + canTranslate, + reason + }); + + // Mark these lines as belonging to this block + for (let i = startLine; i <= endLine; i += 1) { + lineToBlockMap.set(i, blockId); + } + + match = pathRegex.exec(hclPolicy); + } + } catch (err) { + console.error("Error analyzing HCL policy:", err); + } + + // Step 2: Process each line + policyLines.forEach((lineText, index) => { + const lineNumber = index + 1; + const trimmedLine = lineText.trim(); + const blockId = lineToBlockMap.get(lineNumber); + + let type: PolicyLine["type"] = "other"; + if (trimmedLine.startsWith("#") || trimmedLine.startsWith("//")) { + type = "comment"; + } else if (trimmedLine === "") { + type = "empty"; + } else if (blockId) { + type = "part-of-block"; + } + + lines.push({ + id: `line-${lineNumber}`, + text: lineText, + lineNumber, + type, + belongsToBlock: blockId + }); + }); + + const translatableCount = blocks.filter((b) => b.canTranslate).length; + const nonTranslatableCount = blocks.filter((b) => !b.canTranslate).length; + + return { + blocks, + lines, + translatableCount, + nonTranslatableCount + }; +}; diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyImportModal.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyImportModal.tsx index d126791f0..3b095e8ff 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyImportModal.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/VaultPolicyImportModal.tsx @@ -21,7 +21,9 @@ import { } from "@app/hooks/api/migration/queries"; import { TFormSchema } from "./ProjectRoleModifySection.utils"; +import { analyzeVaultPolicy, PolicyBlock, PolicyLine } from "./VaultPolicyAnalyzer.utils"; import { parseVaultPolicyToInfisical } from "./VaultPolicyImportModal.utils"; +import { VaultPolicyPreview } from "./VaultPolicyPreview"; type Props = { isOpen: boolean; @@ -39,6 +41,10 @@ const Content = ({ onClose }: ContentProps) => { const [hclPolicy, setHclPolicy] = useState(""); const [shouldFetchPolicies, setShouldFetchPolicies] = useState(false); const [shouldFetchMounts, setShouldFetchMounts] = useState(false); + const [analysisResult, setAnalysisResult] = useState<{ + blocks: PolicyBlock[]; + lines: PolicyLine[]; + } | null>(null); const { data: namespaces, isLoading: isLoadingNamespaces } = useGetVaultNamespaces(); const { data: policies, isLoading: isLoadingPolicies } = useGetVaultPolicies( @@ -68,6 +74,49 @@ const Content = ({ onClose }: ContentProps) => { } }, [selectedPolicy, policies]); + // Automatically analyze policy when it changes (with debouncing) + useEffect(() => { + if (!hclPolicy.trim() || !mounts || mounts.length === 0) { + setAnalysisResult(null); + return undefined; + } + + const timeoutId = setTimeout(() => { + const result = analyzeVaultPolicy(hclPolicy, mounts); + setAnalysisResult(result); + }, 300); // Debounce for 300ms + + return () => { + clearTimeout(timeoutId); + }; + }, [hclPolicy, mounts]); + + const renderEmptyState = () => { + if (!selectedNamespace) { + return ( +

+

Select a namespace to enable preview

+
+ ); + } + + if (isLoadingMounts) { + return
Loading mounts...
; + } + + if (!mounts || mounts.length === 0) { + return ( +
+

No KV mounts found

+

This namespace has no KV secret engines configured.

+

Policy translation requires KV mounts.

+
+ ); + } + + return
Enter a policy to see translation preview
; + }; + const handleTranslateAndApply = () => { if (!hclPolicy.trim()) { createNotification({ type: "error", text: "Please provide a Vault HCL policy" }); @@ -211,27 +260,40 @@ const Content = ({ onClose }: ContentProps) => { - - <> -