From d9a0cf8dd5f8da0170658b0f7ab24fa1859f1d29 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 6 Feb 2025 17:42:30 +0100 Subject: [PATCH 01/41] Update changelog with January 2025 entries --- docs/changelog/overview.mdx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog/overview.mdx b/docs/changelog/overview.mdx index e5bec4d6c..b2f66a621 100644 --- a/docs/changelog/overview.mdx +++ b/docs/changelog/overview.mdx @@ -4,6 +4,17 @@ title: "Changelog" The changelog below reflects new product developments and updates on a monthly basis. +## January 2025 +- Added [Secret Syncs](https://infisical.com/docs/integrations/secret-syncs/overview) for AWS Parameter Store, GitHub, and GCP Secret Manager +- Added [ephemeral Terraform resource](https://infisical.com/docs/integrations/frameworks/terraform#terraform-provider) support for Infisical secrets. +- Added support for Azure authentication via Azure CLI +_ Added support for password setup when signed with SSO +- Improved documentation of [Audit Log Streams](https://infisical.com/docs/documentation/platform/audit-log-streams) +- Enabled OIDC multi-value string for bound claims. +- Fixed issue with service account impersonation for GCP secret manager integration. +- Fixed secret sharing within an organization +- Fixed Python SDK + ## December 2024 - Added [GCP KMS](https://infisical.com/docs/documentation/platform/kms/overview) integration support. - Added support for [K8s CSI integration](https://infisical.com/docs/integrations/platforms/kubernetes-csi) and ability to point K8s operator to specific secret versions. From 9ee9d1c0e7a900d1e13856cd09dfbf7a49134837 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 7 Feb 2025 08:57:30 +0100 Subject: [PATCH 02/41] fixes --- docs/changelog/overview.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/changelog/overview.mdx b/docs/changelog/overview.mdx index b2f66a621..8a9153115 100644 --- a/docs/changelog/overview.mdx +++ b/docs/changelog/overview.mdx @@ -5,15 +5,15 @@ title: "Changelog" The changelog below reflects new product developments and updates on a monthly basis. ## January 2025 -- Added [Secret Syncs](https://infisical.com/docs/integrations/secret-syncs/overview) for AWS Parameter Store, GitHub, and GCP Secret Manager +- Added [Secret Syncs](https://infisical.com/docs/integrations/secret-syncs/overview) for AWS Parameter Store, GitHub, and GCP Secret Manager. - Added [ephemeral Terraform resource](https://infisical.com/docs/integrations/frameworks/terraform#terraform-provider) support for Infisical secrets. -- Added support for Azure authentication via Azure CLI -_ Added support for password setup when signed with SSO -- Improved documentation of [Audit Log Streams](https://infisical.com/docs/documentation/platform/audit-log-streams) +- Added support for Azure authentication via Azure CLI. +_ Added support for password setup when signed with SSO. +- Improved documentation of [Audit Log Streams](https://infisical.com/docs/documentation/platform/audit-log-streams). - Enabled OIDC multi-value string for bound claims. - Fixed issue with service account impersonation for GCP secret manager integration. -- Fixed secret sharing within an organization -- Fixed Python SDK +- Fixed secret sharing within an organization. +- Fixed Python SDK. ## December 2024 - Added [GCP KMS](https://infisical.com/docs/documentation/platform/kms/overview) integration support. @@ -23,7 +23,7 @@ _ Added support for password setup when signed with SSO - Added Group View Page for improved team management. - Added instance URL to email verification for Infisical accounts. - Added ability to copy full path of nested folders. -- Added custom templating support for K8s operator, allowing flexible secret key mapping and additional fields +- Added custom templating support for K8s operator, allowing flexible secret key mapping and additional fields. - Optimized secrets versions table performance. ## November 2024 From f27d483be09c8d297834c14f60ad451e931c1002 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 7 Feb 2025 18:12:58 +0100 Subject: [PATCH 03/41] Update changelog --- docs/changelog/overview.mdx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/changelog/overview.mdx b/docs/changelog/overview.mdx index 8a9153115..a20f52171 100644 --- a/docs/changelog/overview.mdx +++ b/docs/changelog/overview.mdx @@ -5,15 +5,16 @@ title: "Changelog" The changelog below reflects new product developments and updates on a monthly basis. ## January 2025 -- Added [Secret Syncs](https://infisical.com/docs/integrations/secret-syncs/overview) for AWS Parameter Store, GitHub, and GCP Secret Manager. -- Added [ephemeral Terraform resource](https://infisical.com/docs/integrations/frameworks/terraform#terraform-provider) support for Infisical secrets. -- Added support for Azure authentication via Azure CLI. -_ Added support for password setup when signed with SSO. -- Improved documentation of [Audit Log Streams](https://infisical.com/docs/documentation/platform/audit-log-streams). -- Enabled OIDC multi-value string for bound claims. -- Fixed issue with service account impersonation for GCP secret manager integration. -- Fixed secret sharing within an organization. -- Fixed Python SDK. + +- Released new integration architecture with decoupled authentication, replacing native integrations with [App Connections](https://infisical.com/docs/integrations/app-connections/overview) and [Secret Syncs](https://infisical.com/docs/integrations/secret-syncs/overview). Initial support for AWS Parameter Store, GitHub, and GCP Secret Manager with improved API and Terraform integration capabilities. +- Added support for OIDC group mapping in [Keycloak](https://infisical.com/docs/documentation/platform/sso/keycloak-oidc/overview), enabling automatic mapping of Keycloak groups to Infisical for role-based access control. +- Enhanced [Kubernetes operator](https://infisical.com/docs/integrations/platforms/kubernetes/overview#kubernetes-operator) with namespaced group support, bi-directional secret sync (push to Infisical), [dynamic secrets](https://infisical.com/docs/documentation/platform/dynamic-secrets/overview#dynamic-secrets) capabilities, and support for multiple operator instances. +- Restructured navigation with dedicated sections for Secrets Management, [Certificate Management (PKI)](https://infisical.com/docs/documentation/platform/pki/overview), [Key Management (KMS)](https://infisical.com/docs/documentation/platform/kms/overview#key-management-service-kms), and [SSH Key Management](https://infisical.com/docs/documentation/platform/ssh). +- Added [ephemeral Terraform resource](https://infisical.com/docs/integrations/frameworks/terraform#terraform-provider) support and improved secret sync architecture. +- Released [.NET provider](https://github.com/Infisical/infisical-dotnet-configuration) with first-party Azure authentication support and Azure CLI integration. +- Implemented secret Access Visibility allowing users to view all entities with access to specific secrets in the secret side panel. +- Added secret filtering by metadata and SSH assigned certificates (Version 1). + ## December 2024 - Added [GCP KMS](https://infisical.com/docs/documentation/platform/kms/overview) integration support. From 3e5a58eec43abbaac108ad74a8feda3977b6c9bf Mon Sep 17 00:00:00 2001 From: = Date: Thu, 28 Nov 2024 16:42:39 +0530 Subject: [PATCH 04/41] feat: added project level migrations for kms convernsion --- backend/e2e-test/vitest-environment-knex.ts | 35 ++++-- backend/src/@types/fastify.d.ts | 6 + backend/src/db/knexfile.ts | 35 +++--- .../20241127091918_webhook-to-kms.ts | 110 ++++++++++++++++++ .../20241128090536_secret-rotation-to-kms.ts | 89 ++++++++++++++ ...241128092853_dynamic-secret-root-to-kms.ts | 90 ++++++++++++++ backend/src/db/migrations/utils/env-config.ts | 49 ++++++++ .../src/db/migrations/utils/ring-buffer.ts | 19 +++ backend/src/db/migrations/utils/services.ts | 50 ++++++++ backend/src/ee/services/hsm/hsm-fns.ts | 10 +- backend/src/ee/services/hsm/hsm-service.ts | 33 +++--- backend/src/keystore/memory.ts | 38 ++++++ backend/src/lib/config/env.ts | 3 +- backend/src/main.ts | 42 ++++--- backend/src/server/app.ts | 9 +- backend/src/server/routes/index.ts | 12 +- backend/src/services/kms/kms-service.ts | 10 +- backend/src/services/project/project-types.ts | 13 ++- 18 files changed, 570 insertions(+), 83 deletions(-) create mode 100644 backend/src/db/migrations/20241127091918_webhook-to-kms.ts create mode 100644 backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts create mode 100644 backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts create mode 100644 backend/src/db/migrations/utils/env-config.ts create mode 100644 backend/src/db/migrations/utils/ring-buffer.ts create mode 100644 backend/src/db/migrations/utils/services.ts create mode 100644 backend/src/keystore/memory.ts diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 58f2bffeb..8158644c9 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -24,13 +24,13 @@ export default { transformMode: "ssr", async setup() { const logger = await initLogger(); - const cfg = initEnvConfig(logger); + const envConfig = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: cfg.DB_CONNECTION_URI, - dbRootCert: cfg.DB_ROOT_CERT + dbConnectionUri: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT }); - const redis = new Redis(cfg.REDIS_URL); + const redis = new Redis(envConfig.REDIS_URL); await redis.flushdb("SYNC"); try { @@ -42,6 +42,7 @@ export default { }, true ); + await db.migrate.latest({ directory: path.join(__dirname, "../src/db/migrations"), extension: "ts", @@ -52,14 +53,24 @@ export default { directory: path.join(__dirname, "../src/db/seeds"), extension: "ts" }); - const smtp = mockSmtpServer(); - const queue = queueServiceFactory(cfg.REDIS_URL, { dbConnectionUrl: cfg.DB_CONNECTION_URI }); - const keyStore = keyStoreFactory(cfg.REDIS_URL); - const hsmModule = initializeHsmModule(); + const smtp = mockSmtpServer(); + const queue = queueServiceFactory(envConfig.REDIS_URL, { dbConnectionUrl: envConfig.DB_CONNECTION_URI }); + const keyStore = keyStoreFactory(envConfig.REDIS_URL); + + const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); - const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule(), redis }); + const server = await main({ + db, + smtp, + logger, + queue, + keyStore, + hsmModule: hsmModule.getModule(), + redis, + envConfig + }); // @ts-expect-error type globalThis.testServer = server; @@ -73,8 +84,8 @@ export default { organizationId: seedData1.organization.id, accessVersion: 1 }, - cfg.AUTH_SECRET, - { expiresIn: cfg.JWT_AUTH_LIFETIME } + envConfig.AUTH_SECRET, + { expiresIn: envConfig.JWT_AUTH_LIFETIME } ); } catch (error) { // eslint-disable-next-line @@ -108,4 +119,4 @@ export default { } }; } -}; +}; \ No newline at end of file diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index f3298625e..b2a37755f 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -93,6 +93,12 @@ import { TUserEngagementServiceFactory } from "@app/services/user-engagement/use import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; +declare module "@fastify/request-context" { + interface RequestContextData { + requestId: string; + } +} + declare module "fastify" { interface Session { callbackPort: string; diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 8af2b59ab..3b5ef27e0 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -4,6 +4,7 @@ import "ts-node/register"; import dotenv from "dotenv"; import type { Knex } from "knex"; import path from "path"; +import { getMigrationEnvConfig } from "./migrations/utils/env-config"; // Update with your config settings. . dotenv.config({ @@ -13,20 +14,22 @@ dotenv.config({ path: path.join(__dirname, "../../../.env") }); +const envConfig = getMigrationEnvConfig(); + export default { development: { client: "postgres", connection: { - connectionString: process.env.DB_CONNECTION_URI, - host: process.env.DB_HOST, - port: process.env.DB_PORT, - user: process.env.DB_USER, - database: process.env.DB_NAME, - password: process.env.DB_PASSWORD, - ssl: process.env.DB_ROOT_CERT + connectionString: envConfig.DB_CONNECTION_URI, + host: envConfig.DB_HOST, + port: envConfig.DB_PORT, + user: envConfig.DB_USER, + database: envConfig.DB_NAME, + password: envConfig.DB_PASSWORD, + ssl: envConfig.DB_ROOT_CERT ? { rejectUnauthorized: true, - ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii") + ca: Buffer.from(envConfig.DB_ROOT_CERT, "base64").toString("ascii") } : false }, @@ -44,16 +47,16 @@ export default { production: { client: "postgres", connection: { - connectionString: process.env.DB_CONNECTION_URI, - host: process.env.DB_HOST, - port: process.env.DB_PORT, - user: process.env.DB_USER, - database: process.env.DB_NAME, - password: process.env.DB_PASSWORD, - ssl: process.env.DB_ROOT_CERT + connectionString: envConfig.DB_CONNECTION_URI, + host: envConfig.DB_HOST, + port: envConfig.DB_PORT, + user: envConfig.DB_USER, + database: envConfig.DB_NAME, + password: envConfig.DB_PASSWORD, + ssl: envConfig.DB_ROOT_CERT ? { rejectUnauthorized: true, - ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii") + ca: Buffer.from(envConfig.DB_ROOT_CERT, "base64").toString("ascii") } : false }, diff --git a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts new file mode 100644 index 000000000..b80d20d33 --- /dev/null +++ b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts @@ -0,0 +1,110 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); + const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + + const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (!hasEncryptedKey) t.binary("encryptedPassKey"); + if (!hasEncryptedUrl) t.binary("encryptedUrl"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + newRingBuffer>>(25); + + const webhooks = await knex(TableName.Webhook) + .where({}) + .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) + .select("url", "encryptedSecretKey", "iv", "tag", "keyEncoding", "urlCipherText", "urlIV", "urlTag", "id", "envId") + .select(knex.ref("projectId").withSchema(TableName.Environment)); + + const updatedWebhooks = await Promise.all( + webhooks.map(async (el) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: el.projectId + }); + projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + } + + let encryptedSecretKey = null; + if (el.encryptedSecretKey && el.iv && el.tag && el.keyEncoding) { + const decyptedSecretKey = infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.iv, + tag: el.tag, + ciphertext: el.encryptedSecretKey + }); + encryptedSecretKey = projectKmsService.encryptor({ plainText: Buffer.from(decyptedSecretKey, "utf8") }); + } + + const decryptedUrl = + el.urlIV && el.urlTag && el.urlCipherText && el.keyEncoding + ? infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.urlIV, + tag: el.urlTag, + ciphertext: el.urlCipherText + }) + : null; + + const encryptedUrl = projectKmsService.encryptor({ plainText: Buffer.from(decryptedUrl || el.url) }); + return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId }; + }) + ); + + for (let i = 0; i < updatedWebhooks.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.Webhook) + .insert( + updatedWebhooks.slice(i, i + BATCH_SIZE).map((el) => ({ + id: el.id, + envId: el.envId, + url: "", + encryptedUrl: el.encryptedUrl, + encryptedPassKey: el.encryptedSecretKey + })) + ) + .onConflict("id") + .merge(); + } + + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (!hasEncryptedUrl) t.binary("encryptedUrl").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); + const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + + const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (hasEncryptedKey) t.dropColumn("encryptedPassKey"); + if (hasEncryptedUrl) t.dropColumn("encryptedUrl"); + }); + } +} diff --git a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts new file mode 100644 index 000000000..2adbe52db --- /dev/null +++ b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts @@ -0,0 +1,89 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + + const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (!hasEncryptedRotationData) t.binary("encryptedRotationData"); + }); + } + + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + newRingBuffer>>(25); + + const secretRotations = await knex(TableName.SecretRotation) + .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) + .select(selectAllTableCols(TableName.SecretRotation)) + .select(knex.ref("projectId").withSchema(TableName.Environment)); + + const updatedRotationData = await Promise.all( + secretRotations.map(async (el) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: el.projectId + }); + projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + } + + const decryptedRotationData = + el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding + ? infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.encryptedDataIV, + tag: el.encryptedDataTag, + ciphertext: el.encryptedData + }) + : null; + + const encryptedRotationData = decryptedRotationData + ? projectKmsService.encryptor({ + plainText: Buffer.from(decryptedRotationData) + }) + : null; + return { ...el, encryptedRotationData }; + }) + ); + + for (let i = 0; i < updatedRotationData.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.SecretRotation) + .insert(updatedRotationData.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (!hasEncryptedRotationData) t.binary("encryptedRotationData").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + + const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (hasEncryptedRotationData) t.dropColumn("encryptedRotationData"); + }); + } +} diff --git a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts new file mode 100644 index 000000000..f17a32227 --- /dev/null +++ b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts @@ -0,0 +1,90 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); + + const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (!hasEncryptedInputColumn) t.binary("encryptedInput"); + }); + } + + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + newRingBuffer>>(25); + + const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret) + .leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) + .leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .select(selectAllTableCols(TableName.DynamicSecret)) + .select(knex.ref("projectId").withSchema(TableName.Environment)); + + const updatedDynamicSecrets = await Promise.all( + dynamicSecretRootCredentials.map(async (el) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: el.projectId + }); + projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + } + + const decryptedInputData = + el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding + ? infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.inputIV, + tag: el.inputTag, + ciphertext: el.inputCiphertext + }) + : null; + + const encryptedInput = decryptedInputData + ? projectKmsService.encryptor({ + plainText: Buffer.from(decryptedInputData) + }) + : null; + return { ...el, encryptedInput }; + }) + ); + + for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.DynamicSecret) + .insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (!hasEncryptedInputColumn) t.binary("encryptedInput").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); + + const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (hasEncryptedInputColumn) t.dropColumn("encryptedInput"); + }); + } +} diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts new file mode 100644 index 000000000..29bbb3f92 --- /dev/null +++ b/backend/src/db/migrations/utils/env-config.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; + +import { zpStr } from "@app/lib/zod"; + +const envSchema = z + .object({ + DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default( + `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}` + ), + DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()), + DB_HOST: zpStr(z.string().describe("Postgres database host").optional()), + DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"), + DB_USER: zpStr(z.string().describe("Postgres database username").optional()), + DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), + DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), + // TODO(akhilmhdh): will be changed to one + ENCRYPTION_KEY: zpStr(z.string().optional()), + ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()), + // HSM + 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) + }) + // 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: + Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined + })); + +export type TMigrationEnvConfig = z.infer; + +export const getMigrationEnvConfig = () => { + const parsedEnv = envSchema.safeParse(process.env); + if (!parsedEnv.success) { + // eslint-disable-next-line no-console + console.error("Invalid environment variables. Check the error below"); + // eslint-disable-next-line no-console + console.error(parsedEnv.error.issues); + process.exit(-1); + } + + return Object.freeze(parsedEnv.data); +}; diff --git a/backend/src/db/migrations/utils/ring-buffer.ts b/backend/src/db/migrations/utils/ring-buffer.ts new file mode 100644 index 000000000..d738031ea --- /dev/null +++ b/backend/src/db/migrations/utils/ring-buffer.ts @@ -0,0 +1,19 @@ +export const newRingBuffer = (bufferSize = 10) => { + const bufferItems: { id: string; item: T }[] = []; + let bufferIndex = 0; + + const push = (id: string, item: T) => { + if (bufferItems.length < bufferSize) { + bufferItems.push({ id, item }); + } else { + bufferItems[bufferIndex] = { id, item }; + } + bufferIndex = (bufferIndex + 1) % bufferSize; + }; + + const getItem = (id: string) => { + return bufferItems.find((i) => i.id === id)?.item; + }; + + return { push, getItem }; +}; diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts new file mode 100644 index 000000000..456f60c0a --- /dev/null +++ b/backend/src/db/migrations/utils/services.ts @@ -0,0 +1,50 @@ +import { Knex } from "knex"; + +import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; +import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; +import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; +import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; +import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; +import { kmsServiceFactory } from "@app/services/kms/kms-service"; +import { orgDALFactory } from "@app/services/org/org-dal"; +import { projectDALFactory } from "@app/services/project/project-dal"; + +import { TMigrationEnvConfig } from "./env-config"; + +type TDependencies = { + envConfig: TMigrationEnvConfig; + db: Knex; + keyStore: TKeyStoreFactory; +}; + +export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { + const hsmModule = initializeHsmModule(envConfig); + hsmModule.initialize(); + + const hsmService = hsmServiceFactory({ + hsmModule: hsmModule.getModule(), + envConfig + }); + + const orgDAL = orgDALFactory(db); + const kmsRootConfigDAL = kmsRootConfigDALFactory(db); + const kmsDAL = kmskeyDALFactory(db); + const internalKmsDAL = internalKmsDALFactory(db); + const projectDAL = projectDALFactory(db); + + const kmsService = kmsServiceFactory({ + kmsRootConfigDAL, + keyStore, + kmsDAL, + internalKmsDAL, + orgDAL, + projectDAL, + hsmService, + envConfig + }); + + await hsmService.startService(); + + return { kmsService }; +}; diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index 3124e1012..ef975a371 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -1,25 +1,23 @@ import * as pkcs11js from "pkcs11js"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmModule } from "./hsm-types"; -export const initializeHsmModule = () => { - const appCfg = getConfig(); - +export const initializeHsmModule = (envConfig: Pick) => { // Create a new instance of PKCS11 module const pkcs11 = new pkcs11js.PKCS11(); let isInitialized = false; const initialize = () => { - if (!appCfg.isHsmConfigured) { + if (!envConfig.isHsmConfigured) { return; } try { // Load the PKCS#11 module - pkcs11.load(appCfg.HSM_LIB_PATH!); + pkcs11.load(envConfig.HSM_LIB_PATH!); // Initialize the module pkcs11.C_Initialize(); diff --git a/backend/src/ee/services/hsm/hsm-service.ts b/backend/src/ee/services/hsm/hsm-service.ts index a1a0773fc..d35d17a24 100644 --- a/backend/src/ee/services/hsm/hsm-service.ts +++ b/backend/src/ee/services/hsm/hsm-service.ts @@ -1,12 +1,13 @@ import pkcs11js from "pkcs11js"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmKeyType, HsmModule } from "./hsm-types"; type THsmServiceFactoryDep = { hsmModule: HsmModule; + envConfig: Pick; }; export type THsmServiceFactory = ReturnType; @@ -15,9 +16,7 @@ type SyncOrAsync = T | Promise; type SessionCallback = (session: pkcs11js.Handle) => SyncOrAsync; // eslint-disable-next-line no-empty-pattern -export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsmServiceFactoryDep) => { - const appCfg = getConfig(); - +export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envConfig }: THsmServiceFactoryDep) => { // Constants for buffer structures const IV_LENGTH = 16; // Luna HSM typically expects 16-byte IV for cbc const BLOCK_SIZE = 16; @@ -63,11 +62,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm throw new Error("No slots available"); } - if (appCfg.HSM_SLOT >= slots.length) { - throw new Error(`HSM slot ${appCfg.HSM_SLOT} not found or not initialized`); + if (envConfig.HSM_SLOT >= slots.length) { + throw new Error(`HSM slot ${envConfig.HSM_SLOT} not found or not initialized`); } - const slotId = slots[appCfg.HSM_SLOT]; + const slotId = slots[envConfig.HSM_SLOT]; const startTime = Date.now(); while (Date.now() - startTime < MAX_TIMEOUT) { @@ -78,7 +77,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm // Login try { - pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, appCfg.HSM_PIN); + pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, envConfig.HSM_PIN); logger.info("HSM: Successfully authenticated"); break; } catch (error) { @@ -86,7 +85,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm if (error instanceof pkcs11js.Pkcs11Error) { if (error.code === pkcs11js.CKR_PIN_INCORRECT) { // We throw instantly here to prevent further attempts, because if too many attempts are made, the HSM will potentially wipe all key material - logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`); + logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${envConfig.HSM_SLOT}`); throw new Error("HSM: Incorrect HSM Pin detected. Please check the HSM configuration."); } if (error.code === pkcs11js.CKR_USER_ALREADY_LOGGED_IN) { @@ -133,7 +132,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm }; const $findKey = (sessionHandle: pkcs11js.Handle, type: HsmKeyType) => { - const label = type === HsmKeyType.HMAC ? `${appCfg.HSM_KEY_LABEL}_HMAC` : appCfg.HSM_KEY_LABEL; + const label = type === HsmKeyType.HMAC ? `${envConfig.HSM_KEY_LABEL}_HMAC` : envConfig.HSM_KEY_LABEL; const keyType = type === HsmKeyType.HMAC ? pkcs11js.CKK_GENERIC_SECRET : pkcs11js.CKK_AES; const template = [ @@ -360,7 +359,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm }; const isActive = async () => { - if (!isInitialized || !appCfg.isHsmConfigured) { + if (!isInitialized || !envConfig.isHsmConfigured) { return false; } @@ -372,11 +371,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm logger.error(err, "HSM: Error testing PKCS#11 module"); } - return appCfg.isHsmConfigured && isInitialized && pkcs11TestPassed; + return envConfig.isHsmConfigured && isInitialized && pkcs11TestPassed; }; const startService = async () => { - if (!appCfg.isHsmConfigured || !pkcs11 || !isInitialized) return; + if (!envConfig.isHsmConfigured || !pkcs11 || !isInitialized) return; try { await $withSession(async (sessionHandle) => { @@ -395,7 +394,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_AES }, { type: pkcs11js.CKA_VALUE_LEN, value: AES_KEY_SIZE / 8 }, - { type: pkcs11js.CKA_LABEL, value: appCfg.HSM_KEY_LABEL! }, + { type: pkcs11js.CKA_LABEL, value: envConfig.HSM_KEY_LABEL! }, { type: pkcs11js.CKA_ENCRYPT, value: true }, // Allow encryption { type: pkcs11js.CKA_DECRYPT, value: true }, // Allow decryption ...genericAttributes @@ -410,7 +409,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm keyTemplate ); - logger.info(`HSM: Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`); + logger.info(`HSM: Master key created successfully with label: ${envConfig.HSM_KEY_LABEL}`); } // Check if HMAC key exists, create if not @@ -419,7 +418,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_GENERIC_SECRET }, { type: pkcs11js.CKA_VALUE_LEN, value: HMAC_KEY_SIZE / 8 }, // 256-bit key - { type: pkcs11js.CKA_LABEL, value: `${appCfg.HSM_KEY_LABEL!}_HMAC` }, + { type: pkcs11js.CKA_LABEL, value: `${envConfig.HSM_KEY_LABEL!}_HMAC` }, { type: pkcs11js.CKA_SIGN, value: true }, // Allow signing { type: pkcs11js.CKA_VERIFY, value: true }, // Allow verification ...genericAttributes @@ -434,7 +433,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm hmacKeyTemplate ); - logger.info(`HSM: HMAC key created successfully with label: ${appCfg.HSM_KEY_LABEL}_HMAC`); + logger.info(`HSM: HMAC key created successfully with label: ${envConfig.HSM_KEY_LABEL}_HMAC`); } // Get slot info to check supported mechanisms diff --git a/backend/src/keystore/memory.ts b/backend/src/keystore/memory.ts new file mode 100644 index 000000000..1fe78cf7e --- /dev/null +++ b/backend/src/keystore/memory.ts @@ -0,0 +1,38 @@ +import { Lock } from "@app/lib/red-lock"; + +import { TKeyStoreFactory } from "./keystore"; + +export const inMemoryKeyStore = (): TKeyStoreFactory => { + const store: Record = {}; + + return { + setItem: async (key, value) => { + store[key] = value; + return "OK"; + }, + setItemWithExpiry: async (key, value) => { + store[key] = value; + return "OK"; + }, + deleteItem: async (key) => { + delete store[key]; + return 1; + }, + getItem: async (key) => { + const value = store[key]; + if (typeof value === "string") { + return value; + } + return null; + }, + incrementBy: async () => { + return 1; + }, + acquireLock: () => { + return Promise.resolve({ + release: () => {} + }) as Promise; + }, + waitTillReady: async () => {} + }; +}; diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 7f0f31728..801e937a2 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -258,7 +258,8 @@ const envSchema = z SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",") })); -let envCfg: Readonly>; +export type TEnvConfig = Readonly>; +let envCfg: TEnvConfig; export const getConfig = () => envCfg; // cannot import singleton logger directly as it needs config to load various transport diff --git a/backend/src/main.ts b/backend/src/main.ts index 850298f89..076c1df65 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -20,21 +20,21 @@ dotenv.config(); const run = async () => { const logger = await initLogger(); - const appCfg = initEnvConfig(logger); + const envConfig = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: appCfg.DB_CONNECTION_URI, - dbRootCert: appCfg.DB_ROOT_CERT, - readReplicas: appCfg.DB_READ_REPLICAS?.map((el) => ({ + dbConnectionUri: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT, + readReplicas: envConfig.DB_READ_REPLICAS?.map((el) => ({ dbRootCert: el.DB_ROOT_CERT, dbConnectionUri: el.DB_CONNECTION_URI })) }); - const auditLogDb = appCfg.AUDIT_LOGS_DB_CONNECTION_URI + const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ - dbConnectionUri: appCfg.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: appCfg.AUDIT_LOGS_DB_ROOT_CERT + dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT }) : undefined; @@ -57,20 +57,30 @@ const run = async () => { const smtp = smtpServiceFactory(formatSmtpConfig()); - const queue = queueServiceFactory(appCfg.REDIS_URL, { - dbConnectionUrl: appCfg.DB_CONNECTION_URI, - dbRootCert: appCfg.DB_ROOT_CERT + const queue = queueServiceFactory(envConfig.REDIS_URL, { + dbConnectionUrl: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT }); await queue.initialize(); - const keyStore = keyStoreFactory(appCfg.REDIS_URL); - const redis = new Redis(appCfg.REDIS_URL); + const keyStore = keyStoreFactory(envConfig.REDIS_URL); + const redis = new Redis(envConfig.REDIS_URL); - const hsmModule = initializeHsmModule(); + const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); - const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore, redis }); + const server = await main({ + db, + auditLogDb, + hsmModule: hsmModule.getModule(), + smtp, + logger, + queue, + keyStore, + redis, + envConfig + }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line @@ -90,8 +100,8 @@ const run = async () => { }); await server.listen({ - port: appCfg.PORT, - host: appCfg.HOST, + port: envConfig.PORT, + host: envConfig.HOST, listenTextResolver: (address) => { void bootstrap(); return address; diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index ce1be4a04..577b115ae 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -17,7 +17,7 @@ import { Knex } from "knex"; import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig, IS_PACKAGED } from "@app/lib/config/env"; +import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env"; import { CustomLogger } from "@app/lib/logger/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TQueueServiceFactory } from "@app/queue"; @@ -43,10 +43,11 @@ type TMain = { keyStore: TKeyStoreFactory; hsmModule: HsmModule; redis: Redis; + envConfig: TEnvConfig; }; // Run the server! -export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis }: TMain) => { +export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis, envConfig }: TMain) => { const appCfg = getConfig(); const server = fastify({ @@ -127,7 +128,7 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key }) }); - await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule }); + await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule, envConfig }); await server.register(registerServeUI, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, @@ -142,4 +143,4 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key await queue.shutdown(); process.exit(1); } -}; +}; \ No newline at end of file diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 8cebbdebd..86490e975 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -85,7 +85,7 @@ import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certi import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig } from "@app/lib/config/env"; +import { getConfig, TEnvConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { readLimit } from "@app/server/config/rateLimiter"; import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue"; @@ -244,7 +244,8 @@ export const registerRoutes = async ( hsmModule, smtp: smtpService, queue: queueService, - keyStore + keyStore, + envConfig }: { auditLogDb?: Knex; db: Knex; @@ -252,6 +253,7 @@ export const registerRoutes = async ( smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory; + envConfig: TEnvConfig; } ) => { const appCfg = getConfig(); @@ -391,7 +393,8 @@ export const registerRoutes = async ( const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore }); const hsmService = hsmServiceFactory({ - hsmModule + hsmModule, + envConfig }); const kmsService = kmsServiceFactory({ @@ -401,7 +404,8 @@ export const registerRoutes = async ( internalKmsDAL, orgDAL, projectDAL, - hsmService + hsmService, + envConfig }); const externalKmsService = externalKmsServiceFactory({ diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index c41783860..7f8c8cf2c 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -13,7 +13,7 @@ import { } from "@app/ee/services/external-kms/providers/model"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { randomSecureBytes } from "@app/lib/crypto"; import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; import { generateHash } from "@app/lib/crypto/encryption"; @@ -48,6 +48,7 @@ type TKmsServiceFactoryDep = { keyStore: Pick; internalKmsDAL: Pick; hsmService: THsmServiceFactory; + envConfig: Pick; }; export type TKmsServiceFactory = ReturnType; @@ -61,6 +62,7 @@ const KMS_VERSION_BLOB_LENGTH = 3; const KmsSanitizedSchema = KmsKeysSchema.extend({ isExternal: z.boolean() }); export const kmsServiceFactory = ({ + envConfig, kmsDAL, kmsRootConfigDAL, keyStore, @@ -635,10 +637,8 @@ export const kmsServiceFactory = ({ }; const $getBasicEncryptionKey = () => { - const appCfg = getConfig(); - - const encryptionKey = appCfg.ENCRYPTION_KEY || appCfg.ROOT_ENCRYPTION_KEY; - const isBase64 = !appCfg.ENCRYPTION_KEY; + const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY; + const isBase64 = !envConfig.ENCRYPTION_KEY; if (!encryptionKey) throw new Error( "Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?" diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2c6b8e2da..83a59b6af 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -4,8 +4,17 @@ import { ProjectType, TProjectKeys } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { CaStatus } from "../certificate-authority/certificate-authority-types"; -import { KmsType } from "../kms/kms-types"; + +enum KmsType { + External = "external", + Internal = "internal" +} + +enum CaStatus { + ACTIVE = "active", + DISABLED = "disabled", + PENDING_CERTIFICATE = "pending-certificate" +} export enum ProjectFilterType { ID = "id", From 5f6870fda888c23f8bcaefd8f4cde1745954458c Mon Sep 17 00:00:00 2001 From: = Date: Fri, 29 Nov 2024 14:23:34 +0530 Subject: [PATCH 05/41] feat: updated codebase for new field changes made on project level enc migration --- .../20241127091918_webhook-to-kms.ts | 21 ++++++-- .../20241128090536_secret-rotation-to-kms.ts | 30 +++++++---- ...241128092853_dynamic-secret-root-to-kms.ts | 31 +++++++---- backend/src/db/migrations/utils/services.ts | 3 ++ backend/src/db/schemas/dynamic-secrets.ts | 5 +- backend/src/db/schemas/secret-rotations.ts | 5 +- backend/src/db/schemas/webhooks.ts | 6 ++- .../dynamic-secret-lease-dal.ts | 12 +---- .../dynamic-secret-lease-queue.ts | 48 +++++++++++------ .../dynamic-secret-lease-service.ts | 46 +++++++++-------- .../dynamic-secret/dynamic-secret-service.ts | 51 +++++++++---------- .../secret-rotation-queue.ts | 49 ++++++------------ .../secret-rotation-service.ts | 21 +++++--- backend/src/server/routes/index.ts | 16 ++++-- backend/src/server/routes/sanitizedSchemas.ts | 6 +-- backend/src/services/secret/secret-queue.ts | 13 ++++- backend/src/services/webhook/webhook-fns.ts | 42 +++++++-------- .../src/services/webhook/webhook-service.ts | 43 ++++++++-------- backend/tsconfig.json | 4 +- 19 files changed, 254 insertions(+), 198 deletions(-) diff --git a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts index b80d20d33..89458aaad 100644 --- a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts +++ b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts @@ -33,7 +33,18 @@ export async function up(knex: Knex): Promise { const webhooks = await knex(TableName.Webhook) .where({}) .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) - .select("url", "encryptedSecretKey", "iv", "tag", "keyEncoding", "urlCipherText", "urlIV", "urlTag", "id", "envId") + .select( + "url", + "encryptedSecretKey", + "iv", + "tag", + "keyEncoding", + "urlCipherText", + "urlIV", + "urlTag", + knex.ref("id").withSchema(TableName.Webhook), + "envId" + ) .select(knex.ref("projectId").withSchema(TableName.Environment)); const updatedWebhooks = await Promise.all( @@ -55,7 +66,9 @@ export async function up(knex: Knex): Promise { tag: el.tag, ciphertext: el.encryptedSecretKey }); - encryptedSecretKey = projectKmsService.encryptor({ plainText: Buffer.from(decyptedSecretKey, "utf8") }); + encryptedSecretKey = projectKmsService.encryptor({ + plainText: Buffer.from(decyptedSecretKey, "utf8") + }).cipherTextBlob; } const decryptedUrl = @@ -68,7 +81,9 @@ export async function up(knex: Knex): Promise { }) : null; - const encryptedUrl = projectKmsService.encryptor({ plainText: Buffer.from(decryptedUrl || el.url) }); + const encryptedUrl = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedUrl || el.url) + }).cipherTextBlob; return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId }; }) ); diff --git a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts index 2adbe52db..a39e78c13 100644 --- a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts @@ -3,6 +3,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; @@ -21,6 +22,7 @@ export async function up(knex: Knex): Promise { }); } + await initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); @@ -33,31 +35,39 @@ export async function up(knex: Knex): Promise { .select(knex.ref("projectId").withSchema(TableName.Environment)); const updatedRotationData = await Promise.all( - secretRotations.map(async (el) => { - let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + secretRotations.map(async ({ projectId, ...el }) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); if (!projectKmsService) { projectKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, - projectId: el.projectId + projectId }); - projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + projectEncryptionRingBuffer.push(projectId, projectKmsService); } const decryptedRotationData = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding ? infisicalSymmetricDecrypt({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error keyEncoding: el.keyEncoding as SecretKeyEncoding, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.encryptedDataIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error tag: el.encryptedDataTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error ciphertext: el.encryptedData }) - : null; + : ""; - const encryptedRotationData = decryptedRotationData - ? projectKmsService.encryptor({ - plainText: Buffer.from(decryptedRotationData) - }) - : null; + const encryptedRotationData = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedRotationData) + }).cipherTextBlob; return { ...el, encryptedRotationData }; }) ); diff --git a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts index f17a32227..2fd187373 100644 --- a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts @@ -3,6 +3,7 @@ import { Knex } from "knex"; import { inMemoryKeyStore } from "@app/keystore/memory"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; @@ -21,6 +22,7 @@ export async function up(knex: Knex): Promise { }); } + await initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); @@ -34,31 +36,40 @@ export async function up(knex: Knex): Promise { .select(knex.ref("projectId").withSchema(TableName.Environment)); const updatedDynamicSecrets = await Promise.all( - dynamicSecretRootCredentials.map(async (el) => { - let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); if (!projectKmsService) { projectKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, - projectId: el.projectId + projectId }); - projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + projectEncryptionRingBuffer.push(projectId, projectKmsService); } const decryptedInputData = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding ? infisicalSymmetricDecrypt({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error keyEncoding: el.keyEncoding as SecretKeyEncoding, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error iv: el.inputIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error tag: el.inputTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error ciphertext: el.inputCiphertext }) - : null; + : ""; + + const encryptedInput = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedInputData) + }).cipherTextBlob; - const encryptedInput = decryptedInputData - ? projectKmsService.encryptor({ - plainText: Buffer.from(decryptedInputData) - }) - : null; return { ...el, encryptedInput }; }) ); diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts index 456f60c0a..4709248f5 100644 --- a/backend/src/db/migrations/utils/services.ts +++ b/backend/src/db/migrations/utils/services.ts @@ -19,6 +19,8 @@ type TDependencies = { }; export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { + // eslint-disable-next-line no-param-reassign + db.replicaNode = () => db; const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); @@ -45,6 +47,7 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore } }); await hsmService.startService(); + await kmsService.startService(); return { kmsService }; }; diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index b27da396c..d1e81f942 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const DynamicSecretsSchema = z.object({ @@ -23,7 +25,8 @@ export const DynamicSecretsSchema = z.object({ status: z.string().nullable().optional(), statusDetails: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedInput: zodBuffer }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/secret-rotations.ts b/backend/src/db/schemas/secret-rotations.ts index b491edc46..a3cd04ebb 100644 --- a/backend/src/db/schemas/secret-rotations.ts +++ b/backend/src/db/schemas/secret-rotations.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const SecretRotationsSchema = z.object({ @@ -22,7 +24,8 @@ export const SecretRotationsSchema = z.object({ keyEncoding: z.string().nullable().optional(), envId: z.string().uuid(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedRotationData: zodBuffer }); export type TSecretRotations = z.infer; diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index a7aac2933..8b0801f0d 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const WebhooksSchema = z.object({ @@ -25,7 +27,9 @@ export const WebhooksSchema = z.object({ urlCipherText: z.string().nullable().optional(), urlIV: z.string().nullable().optional(), urlTag: z.string().nullable().optional(), - type: z.string().default("general").nullable().optional() + type: z.string().default("general").nullable().optional(), + encryptedPassKey: zodBuffer.nullable().optional(), + encryptedUrl: zodBuffer }); export type TWebhooks = z.infer; diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts index 810628030..e9f00f401 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts @@ -37,11 +37,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"), db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"), db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"), - db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"), - db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"), - db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"), - db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"), - db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"), + db.ref("encryptedInput").withSchema(TableName.DynamicSecret).as("dynEncryptedInput"), db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"), db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"), db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"), @@ -59,11 +55,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { type: doc.dynType, defaultTTL: doc.dynDefaultTTL, maxTTL: doc.dynMaxTTL, - inputIV: doc.dynInputIV, - inputTag: doc.dynInputTag, - inputCiphertext: doc.dynInputCiphertext, - algorithm: doc.dynAlgorithm, - keyEncoding: doc.dynKeyEncoding, + encryptedInput: doc.dynEncryptedInput, folderId: doc.dynFolderId, status: doc.dynStatus, statusDetails: doc.dynStatusDetails, diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts index 9bdb1c24e..fa1a80ac3 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts @@ -1,8 +1,10 @@ -import { SecretKeyEncoding } from "@app/db/schemas"; import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; +import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal"; import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types"; @@ -14,6 +16,8 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = { dynamicSecretLeaseDAL: Pick; dynamicSecretDAL: Pick; dynamicSecretProviders: Record; + kmsService: Pick; + folderDAL: Pick; }; export type TDynamicSecretLeaseQueueServiceFactory = ReturnType; @@ -22,7 +26,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ queueService, dynamicSecretDAL, dynamicSecretProviders, - dynamicSecretLeaseDAL + dynamicSecretLeaseDAL, + kmsService, + folderDAL }: TDynamicSecretLeaseQueueServiceFactoryDep) => { const pruneDynamicSecret = async (dynamicSecretCfgId: string) => { await queueService.queue( @@ -76,15 +82,21 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" }); + const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId); + if (!folder) + throw new NotFoundError({ + message: `Failed to find folder with ${dynamicSecretLease.dynamicSecret.folderId}` + }); + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: folder.projectId + }); + const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId); @@ -100,16 +112,22 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting) throw new DisableRotationErrors({ message: "Document not deleted" }); + const folder = await folderDAL.findById(dynamicSecretCfg.folderId); + if (!folder) + throw new NotFoundError({ + message: `Failed to find folder with ${dynamicSecretCfg.folderId}` + }); + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: folder.projectId + }); + const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId }); if (dynamicSecretLeases.length) { const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id))); diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 830c1aa57..39e8ae7e2 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import ms from "ms"; -import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; +import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { @@ -9,9 +9,10 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -37,6 +38,7 @@ type TDynamicSecretLeaseServiceFactoryDep = { folderDAL: Pick; permissionService: Pick; projectDAL: Pick; + kmsService: Pick; }; export type TDynamicSecretLeaseServiceFactory = ReturnType; @@ -49,7 +51,8 @@ export const dynamicSecretLeaseServiceFactory = ({ permissionService, dynamicSecretQueueService, projectDAL, - licenseService + licenseService, + kmsService }: TDynamicSecretLeaseServiceFactoryDep) => { const create = async ({ environmentSlug, @@ -104,13 +107,14 @@ export const dynamicSecretLeaseServiceFactory = ({ throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -160,6 +164,11 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const plan = await licenseService.getPlan(actorOrgId); if (!plan?.dynamicSecret) { throw new BadRequestError({ @@ -181,12 +190,7 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -240,6 +244,11 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); if (!folder) throw new NotFoundError({ @@ -253,12 +262,7 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const revokeResponse = await selectedProvider diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 631d5b6ba..eac5e2ecf 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -1,15 +1,16 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; +import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -42,6 +43,7 @@ type TDynamicSecretServiceFactoryDep = { folderDAL: Pick; projectDAL: Pick; permissionService: Pick; + kmsService: Pick; }; export type TDynamicSecretServiceFactory = ReturnType; @@ -54,7 +56,8 @@ export const dynamicSecretServiceFactory = ({ dynamicSecretProviders, permissionService, dynamicSecretQueueService, - projectDAL + projectDAL, + kmsService }: TDynamicSecretServiceFactoryDep) => { const create = async ({ path, @@ -108,16 +111,15 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(provider.inputs); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); - const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs)); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const dynamicSecretCfg = await dynamicSecretDAL.create({ type: provider.type, version: 1, - inputIV: encryptedInput.iv, - inputTag: encryptedInput.tag, - inputCiphertext: encryptedInput.ciphertext, - algorithm: encryptedInput.algorithm, - keyEncoding: encryptedInput.encoding, + encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob, maxTTL, defaultTTL, folderId: folder.id, @@ -180,15 +182,15 @@ export const dynamicSecretServiceFactory = ({ if (existingDynamicSecret) throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" }); } + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; const newInput = { ...decryptedStoredInput, ...(inputs || {}) }; const updatedInput = await selectedProvider.validateProviderInputs(newInput); @@ -196,13 +198,8 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(newInput); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); - const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput)); const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, { - inputIV: encryptedInput.iv, - inputTag: encryptedInput.tag, - inputCiphertext: encryptedInput.ciphertext, - algorithm: encryptedInput.algorithm, - keyEncoding: encryptedInput.encoding, + encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) }).cipherTextBlob, maxTTL, defaultTTL, name: newName ?? name, @@ -315,13 +312,13 @@ export const dynamicSecretServiceFactory = ({ if (!dynamicSecretCfg) { throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` }); } + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index 355507ecf..fdc493b9f 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -5,13 +5,9 @@ import { IAMClient } from "@aws-sdk/client-iam"; -import { SecretKeyEncoding, SecretType } from "@app/db/schemas"; +import { SecretType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { - encryptSymmetric128BitHexKeyUTF8, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; +import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -135,20 +131,15 @@ export const secretRotationQueueFactory = ({ // deep copy const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate; + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: secretRotation.projectId + }); - // now get the encrypted variable values - // in includes the inputs, the previous outputs - // internal mapping variables etc - const { encryptedDataTag, encryptedDataIV, encryptedData, keyEncoding } = secretRotation; - if (!encryptedDataTag || !encryptedDataIV || !encryptedData || !keyEncoding) { - throw new DisableRotationErrors({ message: "No inputs found" }); - } - const decryptedData = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: encryptedData, - iv: encryptedDataIV, - tag: encryptedDataTag - }); + const decryptedData = secretManagerDecryptor({ + cipherTextBlob: secretRotation.encryptedRotationData + }).toString(); const variables = JSON.parse(decryptedData) as TSecretRotationEncData; // rotation set cycle @@ -303,11 +294,9 @@ export const secretRotationQueueFactory = ({ outputs: newCredential.outputs, internal: newCredential.internal }); - const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables)); - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: secretRotation.projectId - }); + const encryptedRotationData = secretManagerEncryptor({ + plainText: Buffer.from(JSON.stringify(variables)) + }).cipherTextBlob; const numberOfSecretsRotated = rotationOutputs.length; if (shouldUseSecretV2Bridge) { @@ -323,11 +312,7 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedData: encVarData.ciphertext, - encryptedDataIV: encVarData.iv, - encryptedDataTag: encVarData.tag, - keyEncoding: encVarData.encoding, - algorithm: encVarData.algorithm, + encryptedRotationData, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" @@ -371,11 +356,7 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedData: encVarData.ciphertext, - encryptedDataIV: encVarData.iv, - encryptedDataTag: encVarData.tag, - keyEncoding: encVarData.encoding, - algorithm: encVarData.algorithm, + encryptedRotationData, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index c456e1581..02da4b7ea 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -2,9 +2,11 @@ import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; -import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretDALFactory } from "@app/services/secret/secret-dal"; @@ -30,6 +32,7 @@ type TSecretRotationServiceFactoryDep = { permissionService: Pick; secretRotationQueue: TSecretRotationQueueFactory; projectBotService: Pick; + kmsService: Pick; }; export type TSecretRotationServiceFactory = ReturnType; @@ -44,7 +47,8 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL, projectBotService, - secretV2BridgeDAL + secretV2BridgeDAL, + kmsService }: TSecretRotationServiceFactoryDep) => { const getProviderTemplates = async ({ actor, @@ -156,7 +160,11 @@ export const secretRotationServiceFactory = ({ inputs: formattedInputs, creds: [] }; - const encData = infisicalSymmetricEncypt(JSON.stringify(unencryptedData)); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const secretRotation = await secretRotationDAL.transaction(async (tx) => { const doc = await secretRotationDAL.create( { @@ -164,11 +172,8 @@ export const secretRotationServiceFactory = ({ secretPath, interval, envId: folder.envId, - encryptedDataTag: encData.tag, - encryptedDataIV: encData.iv, - encryptedData: encData.ciphertext, - algorithm: encData.algorithm, - keyEncoding: encData.encoding + encryptedRotationData: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(unencryptedData)) }) + .cipherTextBlob }, tx ); diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 86490e975..5ec01027f 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -973,7 +973,8 @@ export const registerRoutes = async ( permissionService, webhookDAL, projectEnvDAL, - projectDAL + projectDAL, + kmsService }); const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService }); @@ -1153,7 +1154,8 @@ export const registerRoutes = async ( secretDAL, folderDAL, projectBotService, - secretV2BridgeDAL + secretV2BridgeDAL, + kmsService }); const integrationService = integrationServiceFactory({ @@ -1293,7 +1295,9 @@ export const registerRoutes = async ( queueService, dynamicSecretLeaseDAL, dynamicSecretProviders, - dynamicSecretDAL + dynamicSecretDAL, + folderDAL, + kmsService }); const dynamicSecretService = dynamicSecretServiceFactory({ projectDAL, @@ -1303,7 +1307,8 @@ export const registerRoutes = async ( dynamicSecretProviders, folderDAL, permissionService, - licenseService + licenseService, + kmsService }); const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({ projectDAL, @@ -1313,7 +1318,8 @@ export const registerRoutes = async ( dynamicSecretLeaseDAL, dynamicSecretProviders, folderDAL, - licenseService + licenseService, + kmsService }); const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ auditLogDAL, diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 67f01c9ba..ea5519b55 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -201,11 +201,7 @@ export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({ }); export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ - inputIV: true, - inputTag: true, - inputCiphertext: true, - keyEncoding: true, - algorithm: true + encryptedInput: true }); export const SanitizedAuditLogStreamSchema = z.object({ diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index dc973c0b1..00b0e7da8 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -1488,7 +1488,18 @@ export const secretQueueFactory = ({ }); queueService.start(QueueName.SecretWebhook, async (job) => { - await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: job.data.projectId + }); + + await fnTriggerWebhook({ + ...job.data, + projectEnvDAL, + webhookDAL, + projectDAL, + secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString() + }); }); return { diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index 58f51f880..e46f9db2a 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -3,9 +3,8 @@ import crypto from "node:crypto"; import { AxiosError } from "axios"; import picomatch from "picomatch"; -import { SecretKeyEncoding, TWebhooks } from "@app/db/schemas"; +import { TWebhooks } from "@app/db/schemas"; import { request } from "@app/lib/config/request"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -16,28 +15,14 @@ import { WebhookType } from "./webhook-types"; const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000; -export const decryptWebhookDetails = (webhook: TWebhooks) => { - const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook; +export const decryptWebhookDetails = (webhook: TWebhooks, decryptor: (value: Buffer) => string) => { + const { encryptedPassKey, encryptedUrl } = webhook; + + const decryptedUrl = decryptor(encryptedUrl); let decryptedSecretKey = ""; - let decryptedUrl = url; - - if (encryptedSecretKey) { - decryptedSecretKey = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: encryptedSecretKey, - iv: iv as string, - tag: tag as string - }); - } - - if (urlCipherText) { - decryptedUrl = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: urlCipherText, - iv: urlIV as string, - tag: urlTag as string - }); + if (encryptedPassKey) { + decryptedSecretKey = decryptor(encryptedPassKey); } return { @@ -46,10 +31,14 @@ export const decryptWebhookDetails = (webhook: TWebhooks) => { }; }; -export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record) => { +export const triggerWebhookRequest = async ( + webhook: TWebhooks, + decryptor: (value: Buffer) => string, + data: Record +) => { const headers: Record = {}; const payload = { ...data, timestamp: Date.now() }; - const { secretKey, url } = decryptWebhookDetails(webhook); + const { secretKey, url } = decryptWebhookDetails(webhook, decryptor); if (secretKey) { const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); @@ -124,6 +113,7 @@ export type TFnTriggerWebhookDTO = { webhookDAL: Pick; projectEnvDAL: Pick; projectDAL: Pick; + secretManagerDecryptor: (value: Buffer) => string; }; // this is reusable function @@ -134,7 +124,8 @@ export const fnTriggerWebhook = async ({ projectId, webhookDAL, projectEnvDAL, - projectDAL + projectDAL, + secretManagerDecryptor }: TFnTriggerWebhookDTO) => { const webhooks = await webhookDAL.findAllWebhooks(projectId, environment); const toBeTriggeredHooks = webhooks.filter( @@ -148,6 +139,7 @@ export const fnTriggerWebhook = async ({ toBeTriggeredHooks.map((hook) => triggerWebhookRequest( hook, + secretManagerDecryptor, getWebhookPayload("secrets.modified", { workspaceName: project.name, workspaceId: projectId, diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index 26136aaf6..bb078e0f1 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -3,9 +3,10 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TWebhookDALFactory } from "./webhook-dal"; @@ -23,6 +24,7 @@ type TWebhookServiceFactoryDep = { projectEnvDAL: TProjectEnvDALFactory; projectDAL: Pick; permissionService: Pick; + kmsService: Pick; }; export type TWebhookServiceFactory = ReturnType; @@ -31,7 +33,8 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService, - projectDAL + projectDAL, + kmsService }: TWebhookServiceFactoryDep) => { const createWebhook = async ({ actor, @@ -60,30 +63,20 @@ export const webhookServiceFactory = ({ message: `Environment with slug '${environment}' in project with ID '${projectId}' not found` }); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const insertDoc: TWebhooksInsert = { - url: "", // deprecated - we are moving away from plaintext URLs envId: env.id, isDisabled: false, secretPath: secretPath || "/", - type + type, + encryptedUrl: secretManagerEncryptor({ plainText: Buffer.from(webhookUrl) }).cipherTextBlob }; if (webhookSecretKey) { - const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey); - insertDoc.encryptedSecretKey = ciphertext; - insertDoc.iv = iv; - insertDoc.tag = tag; - insertDoc.algorithm = algorithm; - insertDoc.keyEncoding = encoding; - } - - if (webhookUrl) { - const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookUrl); - insertDoc.urlCipherText = ciphertext; - insertDoc.urlIV = iv; - insertDoc.urlTag = tag; - insertDoc.algorithm = algorithm; - insertDoc.keyEncoding = encoding; + insertDoc.encryptedPassKey = secretManagerEncryptor({ plainText: Buffer.from(webhookSecretKey) }).cipherTextBlob; } const webhook = await webhookDAL.create(insertDoc); @@ -140,12 +133,17 @@ export const webhookServiceFactory = ({ }); const project = await projectDAL.findById(webhook.projectId); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: project.id + }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; try { await triggerWebhookRequest( webhook, + (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString(), getWebhookPayload("test", { workspaceName: project.name, workspaceId: webhook.projectId, @@ -185,8 +183,13 @@ export const webhookServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); const webhooks = await webhookDAL.findAllWebhooks(projectId, environment, secretPath); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + return webhooks.map((w) => { - const { url } = decryptWebhookDetails(w); + const { url } = decryptWebhookDetails(w, (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()); return { ...w, url diff --git a/backend/tsconfig.json b/backend/tsconfig.json index fcf508922..90165acbe 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,7 +1,8 @@ { "ts-node": { // Do not forget to `npm i -D tsconfig-paths` - "require": ["tsconfig-paths/register"] + "require": ["tsconfig-paths/register"], + "files": true }, "compilerOptions": { "target": "esnext", @@ -19,6 +20,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, "skipLibCheck": true, "baseUrl": ".", "paths": { From cc28ebd387cf4d7016686170e339f0e0447277d9 Mon Sep 17 00:00:00 2001 From: = Date: Fri, 29 Nov 2024 21:17:08 +0530 Subject: [PATCH 06/41] feat: made the non used columns nullable --- backend/src/db/migrations/20241127091918_webhook-to-kms.ts | 2 ++ .../migrations/20241128092853_dynamic-secret-root-to-kms.ts | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts index 89458aaad..12ef58287 100644 --- a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts +++ b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts @@ -14,12 +14,14 @@ const BATCH_SIZE = 500; export async function up(knex: Knex): Promise { const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + const hasUrl = await knex.schema.hasColumn(TableName.Webhook, "url"); const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); if (hasWebhookTable) { await knex.schema.alterTable(TableName.Webhook, (t) => { if (!hasEncryptedKey) t.binary("encryptedPassKey"); if (!hasEncryptedUrl) t.binary("encryptedUrl"); + if (hasUrl) t.string("url").nullable().alter(); }); } diff --git a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts index 2fd187373..833c87c92 100644 --- a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts @@ -14,11 +14,17 @@ import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; export async function up(knex: Knex): Promise { const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); + const hasInputCiphertextColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext"); + const hasInputIVColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV"); + const hasInputTagColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag"); const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); if (hasDynamicSecretTable) { await knex.schema.alterTable(TableName.DynamicSecret, (t) => { if (!hasEncryptedInputColumn) t.binary("encryptedInput"); + if (hasInputCiphertextColumn) t.text("inputCiphertext").nullable().alter(); + if (hasInputIVColumn) t.string("inputIV").nullable().alter(); + if (hasInputTagColumn) t.string("inputTag").nullable().alter(); }); } From cbbafcfa42728d612c1ba6373421a7dd2d6e9683 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 1 Dec 2024 01:21:21 +0530 Subject: [PATCH 07/41] feat: completed org migration in kms and updated to remove orgDAL functions --- .../20241127091918_webhook-to-kms.ts | 4 +- .../20241128090536_secret-rotation-to-kms.ts | 2 +- ...241128092853_dynamic-secret-root-to-kms.ts | 4 +- .../20241129175559_directory-config-to-kms.ts | 482 ++++++++++++++++++ .../20241129180030_identity-k8-auth-to-kms.ts | 185 +++++++ ...0241129180053_identity-oidc-auth-to-kms.ts | 133 +++++ backend/src/db/schemas/dynamic-secrets.ts | 6 +- .../db/schemas/identity-kubernetes-auths.ts | 18 +- backend/src/db/schemas/identity-oidc-auths.ts | 11 +- backend/src/db/schemas/ldap-configs.ts | 25 +- backend/src/db/schemas/oidc-configs.ts | 16 +- backend/src/db/schemas/saml-configs.ts | 7 +- backend/src/db/schemas/webhooks.ts | 2 +- backend/src/ee/routes/v1/ldap-router.ts | 7 +- backend/src/ee/routes/v1/oidc-router.ts | 36 +- .../ee/routes/v1/project-template-router.ts | 2 +- backend/src/ee/routes/v1/saml-router.ts | 6 +- .../v1/user-additional-privilege-router.ts | 2 +- ...ity-project-additional-privilege-router.ts | 2 +- ...project-additional-privilege-v2-service.ts | 2 +- ...ty-project-additional-privilege-service.ts | 2 +- .../ldap-config/ldap-config-service.ts | 169 +----- .../ee/services/oidc/oidc-config-service.ts | 153 +----- .../services/permission/project-permission.ts | 2 +- .../project-template-service.ts | 2 +- .../project-template-types.ts | 2 +- ...oject-user-additional-privilege-service.ts | 2 +- .../saml-config/saml-config-service.ts | 200 ++------ backend/src/server/routes/index.ts | 16 +- .../sanitizedSchema/directory-config.ts | 42 ++ .../identitiy-additional-privilege.ts | 0 .../permission.ts | 0 .../user-additional-privilege.ts | 0 backend/src/server/routes/sanitizedSchemas.ts | 9 +- .../v1/identity-kubernetes-auth-router.ts | 20 +- .../routes/v1/identity-oidc-auth-router.ts | 16 +- .../identity-kubernetes-auth-service.ts | 223 ++------ .../identity-oidc-auth-service.ts | 157 +----- .../project-role/project-role-service.ts | 2 +- 39 files changed, 1144 insertions(+), 825 deletions(-) create mode 100644 backend/src/db/migrations/20241129175559_directory-config-to-kms.ts create mode 100644 backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts create mode 100644 backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts create mode 100644 backend/src/server/routes/sanitizedSchema/directory-config.ts rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/identitiy-additional-privilege.ts (100%) rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/permission.ts (100%) rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/user-additional-privilege.ts (100%) diff --git a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts index 12ef58287..830d53ace 100644 --- a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts +++ b/backend/src/db/migrations/20241127091918_webhook-to-kms.ts @@ -34,7 +34,7 @@ export async function up(knex: Knex): Promise { const webhooks = await knex(TableName.Webhook) .where({}) - .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) + .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) .select( "url", "encryptedSecretKey", @@ -84,7 +84,7 @@ export async function up(knex: Knex): Promise { : null; const encryptedUrl = projectKmsService.encryptor({ - plainText: Buffer.from(decryptedUrl || el.url) + plainText: Buffer.from(decryptedUrl || el.url || "") }).cipherTextBlob; return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId }; }) diff --git a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts index a39e78c13..808f56d07 100644 --- a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts @@ -30,7 +30,7 @@ export async function up(knex: Knex): Promise { newRingBuffer>>(25); const secretRotations = await knex(TableName.SecretRotation) - .leftJoin(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) + .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) .select(selectAllTableCols(TableName.SecretRotation)) .select(knex.ref("projectId").withSchema(TableName.Environment)); diff --git a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts index 833c87c92..a9caceb12 100644 --- a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts @@ -36,8 +36,8 @@ export async function up(knex: Knex): Promise { newRingBuffer>>(25); const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret) - .leftJoin(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) - .leftJoin(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) + .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .select(selectAllTableCols(TableName.DynamicSecret)) .select(knex.ref("projectId").withSchema(TableName.Environment)); diff --git a/backend/src/db/migrations/20241129175559_directory-config-to-kms.ts b/backend/src/db/migrations/20241129175559_directory-config-to-kms.ts new file mode 100644 index 000000000..853dfba2e --- /dev/null +++ b/backend/src/db/migrations/20241129175559_directory-config-to-kms.ts @@ -0,0 +1,482 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptSamlConfig = async (knex: Knex) => { + const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); + const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); + const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint"); + if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + newRingBuffer>>(25); + + const samlConfigs = await knex(TableName.SamlConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.SamlConfig}.orgId`) + .select(selectAllTableCols(TableName.SamlConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ); + + const updatedSamlConfigs = await Promise.all( + samlConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedEntryPoint = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedEntryPoint && el.entryPointIV && el.entryPointTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.entryPointIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.entryPointTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedEntryPoint + }) + : ""; + + const decryptedIssuer = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedIssuer && el.issuerIV && el.issuerTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.issuerIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.issuerTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedIssuer + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCert && el.certIV && el.certTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.certIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.certTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCert + }) + : ""; + + const encryptedSamlIssuer = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedIssuer) + }).cipherTextBlob; + const encryptedSamlCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + const encryptedSamlEntryPoint = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedEntryPoint) + }).cipherTextBlob; + return { ...el, encryptedSamlCertificate, encryptedSamlEntryPoint, encryptedSamlIssuer }; + } + ) + ); + + for (let i = 0; i < updatedSamlConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.SamlConfig) + .insert(updatedSamlConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint").notNullable().alter(); + if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer").notNullable().alter(); + if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate").notNullable().alter(); + }); + } +}; + +const reencryptLdapConfig = async (knex: Knex) => { + const hasEncryptedLdapBindDNColum = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); + const hasEncryptedLdapBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); + const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); + + const hasEncryptedCACertColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedCACert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertTag"); + const hasEncryptedBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindPass"); + const hasBindPassIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassIV"); + const hasBindPassTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassTag"); + const hasEncryptedBindDNColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindDN"); + const hasBindDNIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNIV"); + const hasBindDNTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNTag"); + + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (hasEncryptedCACertColumn) t.text("encryptedCACert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + if (hasEncryptedBindPassColumn) t.string("encryptedBindPass").nullable().alter(); + if (hasBindPassIVColumn) t.string("bindPassIV").nullable().alter(); + if (hasBindPassTagColumn) t.string("bindPassTag").nullable().alter(); + if (hasEncryptedBindDNColumn) t.string("encryptedBindDN").nullable().alter(); + if (hasBindDNIVColumn) t.string("bindDNIV").nullable().alter(); + if (hasBindDNTagColumn) t.string("bindDNTag").nullable().alter(); + + if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN"); + if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + newRingBuffer>>(25); + + const ldapConfigs = await knex(TableName.LdapConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.LdapConfig}.orgId`) + .select(selectAllTableCols(TableName.LdapConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ); + + const updatedLdapConfigs = await Promise.all( + ldapConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedBindDN = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedBindDN && el.bindDNIV && el.bindDNTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.bindDNIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.bindDNTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedBindDN + }) + : ""; + + const decryptedBindPass = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedBindPass && el.bindPassIV && el.bindPassTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.bindPassIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.bindPassTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedBindPass + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCACert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCACert + }) + : ""; + + const encryptedLdapBindDN = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedBindDN) + }).cipherTextBlob; + const encryptedLdapBindPass = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedBindPass) + }).cipherTextBlob; + const encryptedLdapCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + return { ...el, encryptedLdapBindPass, encryptedLdapBindDN, encryptedLdapCaCertificate }; + } + ) + ); + + for (let i = 0; i < updatedLdapConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.LdapConfig) + .insert(updatedLdapConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass").notNullable().alter(); + if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN").notNullable().alter(); + if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate").notNullable().alter(); + }); + } +}; + +const reencryptOidcConfig = async (knex: Knex) => { + const hasEncryptedOidcClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); + const hasEncryptedOidcClientSecretColumn = await knex.schema.hasColumn( + TableName.OidcConfig, + "encryptedOidcClientSecret" + ); + + const hasEncryptedClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientId"); + const hasClientIdIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdIV"); + const hasClientIdTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdTag"); + const hasEncryptedClientSecretColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientSecret"); + const hasClientSecretIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretIV"); + const hasClientSecretTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretTag"); + + const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); + + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (hasEncryptedClientIdColumn) t.text("encryptedClientId").nullable().alter(); + if (hasClientIdIVColumn) t.string("clientIdIV").nullable().alter(); + if (hasClientIdTagColumn) t.string("clientIdTag").nullable().alter(); + if (hasEncryptedClientSecretColumn) t.text("encryptedClientSecret").nullable().alter(); + if (hasClientSecretIVColumn) t.string("clientSecretIV").nullable().alter(); + if (hasClientSecretTagColumn) t.string("clientSecretTag").nullable().alter(); + + if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId"); + if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + newRingBuffer>>(25); + + const oidcConfigs = await knex(TableName.OidcConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.OidcConfig}.orgId`) + .select(selectAllTableCols(TableName.OidcConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ); + + const updatedOidcConfigs = await Promise.all( + oidcConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedClientId = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedClientId && el.clientIdIV && el.clientIdTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.clientIdIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.clientIdTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedClientId + }) + : ""; + + const decryptedClientSecret = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedClientSecret && el.clientSecretIV && el.clientSecretTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.clientSecretIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.clientSecretTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedClientSecret + }) + : ""; + + const encryptedOidcClientId = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedClientId) + }).cipherTextBlob; + const encryptedOidcClientSecret = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedClientSecret) + }).cipherTextBlob; + return { ...el, encryptedOidcClientId, encryptedOidcClientSecret }; + } + ) + ); + + for (let i = 0; i < updatedOidcConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.OidcConfig) + .insert(updatedOidcConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId").notNullable().alter(); + if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret").notNullable().alter(); + }); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptSamlConfig(knex); + await reencryptLdapConfig(knex); + await reencryptOidcConfig(knex); +} + +const dropSamlConfigColumns = async (knex: Knex) => { + const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); + const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); + const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (hasEncryptedEntrypointColumn) t.dropColumn("encryptedSamlEntryPoint"); + if (hasEncryptedIssuerColumn) t.dropColumn("encryptedSamlIssuer"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedSamlCertificate"); + }); + } +}; + +const dropLdapConfigColumns = async (knex: Knex) => { + const hasEncryptedBindDN = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); + const hasEncryptedBindPass = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); + const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); + + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (hasEncryptedBindDN) t.dropColumn("encryptedLdapBindDN"); + if (hasEncryptedBindPass) t.dropColumn("encryptedLdapBindPass"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedLdapCaCertificate"); + }); + } +}; + +const dropOidcConfigColumns = async (knex: Knex) => { + const hasEncryptedClientId = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); + const hasEncryptedClientSecret = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientSecret"); + const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); + + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (hasEncryptedClientId) t.dropColumn("encryptedOidcClientId"); + if (hasEncryptedClientSecret) t.dropColumn("encryptedOidcClientSecret"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropSamlConfigColumns(knex); + await dropLdapConfigColumns(knex); + await dropOidcConfigColumns(knex); +} diff --git a/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts new file mode 100644 index 000000000..2bbe8022b --- /dev/null +++ b/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts @@ -0,0 +1,185 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptIdentityK8sAuth = async (knex: Knex) => { + const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesTokenReviewerJwt" + ); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesCaCertificate" + ); + const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); + + const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "encryptedCaCert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertTag"); + const hasEncryptedTokenReviewerJwtColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedTokenReviewerJwt" + ); + const hasTokenReviewerJwtIVColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "tokenReviewerJwtIV" + ); + const hasTokenReviewerJwtTagColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "tokenReviewerJwtTag" + ); + + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + if (hasEncryptedTokenReviewerJwtColumn) t.text("encryptedTokenReviewerJwt").nullable().alter(); + if (hasTokenReviewerJwtIVColumn) t.string("tokenReviewerJwtIV").nullable().alter(); + if (hasTokenReviewerJwtTagColumn) t.string("tokenReviewerJwtTag").nullable().alter(); + + if (!hasEncryptedKubernetesTokenReviewerJwt) t.binary("encryptedKubernetesTokenReviewerJwt"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedKubernetesCaCertificate"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + newRingBuffer>>(25); + + const identityKubernetesConfigs = await knex(TableName.IdentityKubernetesAuth) + .join( + TableName.IdentityOrgMembership, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityKubernetesAuth}.identityId` + ) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) + .select(selectAllTableCols(TableName.IdentityKubernetesAuth)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), + knex.ref("orgId").withSchema(TableName.OrgBot) + ); + + const updatedIdentityKubernetesConfigs = await Promise.all( + identityKubernetesConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId + }); + orgEncryptionRingBuffer.push(orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedTokenReviewerJwt = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.tokenReviewerJwtIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.tokenReviewerJwtTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedTokenReviewerJwt + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedTokenReviewerJwt) + }).cipherTextBlob; + const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + return { ...el, encryptedKubernetesCaCertificate, encryptedKubernetesTokenReviewerJwt }; + } + ) + ); + + for (let i = 0; i < updatedIdentityKubernetesConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.IdentityKubernetesAuth) + .insert(updatedIdentityKubernetesConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (!hasEncryptedKubernetesTokenReviewerJwt) + t.binary("encryptedKubernetesTokenReviewerJwt").notNullable().alter(); + }); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptIdentityK8sAuth(knex); +} + +const dropIdentityK8sColumns = async (knex: Knex) => { + const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesTokenReviewerJwt" + ); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesCaCertificate" + ); + const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); + + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (hasEncryptedKubernetesTokenReviewerJwt) t.dropColumn("encryptedKubernetesTokenReviewerJwt"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedKubernetesCaCertificate"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropIdentityK8sColumns(knex); +} diff --git a/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts new file mode 100644 index 000000000..4aed557d5 --- /dev/null +++ b/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts @@ -0,0 +1,133 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { newRingBuffer } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptIdentityOidcAuth = async (knex: Knex) => { + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityOidcAuth, + "encryptedCaCertificate" + ); + const hasidentityOidcAuthTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); + + const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "encryptedCaCert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertTag"); + + if (hasidentityOidcAuthTable) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + + if (!hasEncryptedCertificateColumn) t.binary("encryptedCaCertificate"); + }); + } + + await initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + newRingBuffer>>(25); + + const identityOidcConfig = await knex(TableName.IdentityOidcAuth) + .join( + TableName.IdentityOrgMembership, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityOidcAuth}.identityId` + ) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) + .select(selectAllTableCols(TableName.IdentityOidcAuth)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), + knex.ref("orgId").withSchema(TableName.OrgBot) + ); + + const updatedIdentityOidcConfigs = await Promise.all( + identityOidcConfig.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId + }); + orgEncryptionRingBuffer.push(orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + return { ...el, encryptedCaCertificate }; + } + ) + ); + + for (let i = 0; i < updatedIdentityOidcConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.IdentityOidcAuth) + .insert(updatedIdentityOidcConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptIdentityOidcAuth(knex); +} + +const dropIdentityOidcColumns = async (knex: Knex) => { + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityOidcAuth, + "encryptedCaCertificate" + ); + const hasidentityOidcTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); + + if (hasidentityOidcTable) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedCaCertificate"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropIdentityOidcColumns(knex); +} diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index d1e81f942..eaddea8fe 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -16,9 +16,9 @@ export const DynamicSecretsSchema = z.object({ type: z.string(), defaultTTL: z.string(), maxTTL: z.string().nullable().optional(), - inputIV: z.string(), - inputCiphertext: z.string(), - inputTag: z.string(), + inputIV: z.string().nullable().optional(), + inputCiphertext: z.string().nullable().optional(), + inputTag: z.string().nullable().optional(), algorithm: z.string().default("aes-256-gcm"), keyEncoding: z.string().default("utf8"), folderId: z.string().uuid(), diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index ed99dec86..85f210ff1 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const IdentityKubernetesAuthsSchema = z.object({ @@ -17,15 +19,17 @@ export const IdentityKubernetesAuthsSchema = z.object({ updatedAt: z.date(), identityId: z.string().uuid(), kubernetesHost: z.string(), - encryptedCaCert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), - encryptedTokenReviewerJwt: z.string(), - tokenReviewerJwtIV: z.string(), - tokenReviewerJwtTag: z.string(), + encryptedCaCert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), + encryptedTokenReviewerJwt: z.string().nullable().optional(), + tokenReviewerJwtIV: z.string().nullable().optional(), + tokenReviewerJwtTag: z.string().nullable().optional(), allowedNamespaces: z.string(), allowedNames: z.string(), - allowedAudience: z.string() + allowedAudience: z.string(), + encryptedKubernetesTokenReviewerJwt: zodBuffer, + encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() }); export type TIdentityKubernetesAuths = z.infer; diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts index 3d7d38c41..ebde5e7dc 100644 --- a/backend/src/db/schemas/identity-oidc-auths.ts +++ b/backend/src/db/schemas/identity-oidc-auths.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const IdentityOidcAuthsSchema = z.object({ @@ -15,15 +17,16 @@ export const IdentityOidcAuthsSchema = z.object({ accessTokenTrustedIps: z.unknown(), identityId: z.string().uuid(), oidcDiscoveryUrl: z.string(), - encryptedCaCert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), + encryptedCaCert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), boundIssuer: z.string(), boundAudiences: z.string(), boundClaims: z.unknown(), boundSubject: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedCaCertificate: zodBuffer.nullable().optional() }); export type TIdentityOidcAuths = z.infer; diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index 460c2cff6..94bf0dd03 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const LdapConfigsSchema = z.object({ @@ -12,22 +14,25 @@ export const LdapConfigsSchema = z.object({ orgId: z.string().uuid(), isActive: z.boolean(), url: z.string(), - encryptedBindDN: z.string(), - bindDNIV: z.string(), - bindDNTag: z.string(), - encryptedBindPass: z.string(), - bindPassIV: z.string(), - bindPassTag: z.string(), + encryptedBindDN: z.string().nullable().optional(), + bindDNIV: z.string().nullable().optional(), + bindDNTag: z.string().nullable().optional(), + encryptedBindPass: z.string().nullable().optional(), + bindPassIV: z.string().nullable().optional(), + bindPassTag: z.string().nullable().optional(), searchBase: z.string(), - encryptedCACert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), + encryptedCACert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), groupSearchBase: z.string().default(""), groupSearchFilter: z.string().default(""), searchFilter: z.string().default(""), - uniqueUserAttribute: z.string().default("") + uniqueUserAttribute: z.string().default(""), + encryptedLdapBindDN: zodBuffer, + encryptedLdapBindPass: zodBuffer, + encryptedLdapCaCertificate: zodBuffer }); export type TLdapConfigs = z.infer; diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts index d7bf2f00f..55eb5607b 100644 --- a/backend/src/db/schemas/oidc-configs.ts +++ b/backend/src/db/schemas/oidc-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const OidcConfigsSchema = z.object({ @@ -15,13 +17,13 @@ export const OidcConfigsSchema = z.object({ jwksUri: z.string().nullable().optional(), tokenEndpoint: z.string().nullable().optional(), userinfoEndpoint: z.string().nullable().optional(), - encryptedClientId: z.string(), + encryptedClientId: z.string().nullable().optional(), configurationType: z.string(), - clientIdIV: z.string(), - clientIdTag: z.string(), - encryptedClientSecret: z.string(), - clientSecretIV: z.string(), - clientSecretTag: z.string(), + clientIdIV: z.string().nullable().optional(), + clientIdTag: z.string().nullable().optional(), + encryptedClientSecret: z.string().nullable().optional(), + clientSecretIV: z.string().nullable().optional(), + clientSecretTag: z.string().nullable().optional(), allowedEmailDomains: z.string().nullable().optional(), isActive: z.boolean(), createdAt: z.date(), @@ -29,6 +31,8 @@ export const OidcConfigsSchema = z.object({ orgId: z.string().uuid(), lastUsed: z.date().nullable().optional(), manageGroupMemberships: z.boolean().default(false) + encryptedOidcClientId: zodBuffer, + encryptedOidcClientSecret: zodBuffer }); export type TOidcConfigs = z.infer; diff --git a/backend/src/db/schemas/saml-configs.ts b/backend/src/db/schemas/saml-configs.ts index 67171469a..350e84492 100644 --- a/backend/src/db/schemas/saml-configs.ts +++ b/backend/src/db/schemas/saml-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const SamlConfigsSchema = z.object({ @@ -23,7 +25,10 @@ export const SamlConfigsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), orgId: z.string().uuid(), - lastUsed: z.date().nullable().optional() + lastUsed: z.date().nullable().optional(), + encryptedSamlEntryPoint: zodBuffer, + encryptedSamlIssuer: zodBuffer, + encryptedSamlCertificate: zodBuffer }); export type TSamlConfigs = z.infer; diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index 8b0801f0d..60f031fff 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -12,7 +12,7 @@ import { TImmutableDBKeys } from "./models"; export const WebhooksSchema = z.object({ id: z.string().uuid(), secretPath: z.string().default("/"), - url: z.string(), + url: z.string().nullable().optional(), lastStatus: z.string().nullable().optional(), lastRunErrorMessage: z.string().nullable().optional(), isDisabled: z.boolean().default(false), diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 735ba632c..2057677cf 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -14,7 +14,7 @@ import { FastifyRequest } from "fastify"; import LdapStrategy from "passport-ldapauth"; import { z } from "zod"; -import { LdapConfigsSchema, LdapGroupMapsSchema } from "@app/db/schemas"; +import { LdapGroupMapsSchema } from "@app/db/schemas"; import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types"; import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; import { getConfig } from "@app/lib/config/env"; @@ -22,6 +22,7 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { SanitizedLdapConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerLdapRouter = async (server: FastifyZodProvider) => { @@ -187,7 +188,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { caCert: z.string().trim().default("") }), response: { - 200: LdapConfigsSchema + 200: SanitizedLdapConfigSchema } }, handler: async (req) => { @@ -228,7 +229,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: LdapConfigsSchema + 200: SanitizedLdapConfigSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index 71daa3446..df5c61fe4 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -11,13 +11,28 @@ import fastifySession from "@fastify/session"; import RedisStore from "connect-redis"; import { z } from "zod"; -import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs"; +import { OidcConfigsSchema } from "@app/db/schemas"; import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types"; import { getConfig } from "@app/lib/config/env"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ + id: true, + issuer: true, + authorizationEndpoint: true, + configurationType: true, + discoveryURL: true, + jwksUri: true, + tokenEndpoint: true, + userinfoEndpoint: true, + orgId: true, + isActive: true, + allowedEmailDomains: true, + manageGroupMemberships: true +}); + export const registerOidcRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" }); @@ -142,7 +157,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { orgSlug: z.string().trim() }), response: { - 200: OidcConfigsSchema.pick({ + 200: SanitizedOidcConfigSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -214,7 +229,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ orgSlug: z.string() })), response: { - 200: OidcConfigsSchema.pick({ + 200: SanitizedOidcConfigSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -327,20 +342,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { } }), response: { - 200: OidcConfigsSchema.pick({ - id: true, - issuer: true, - authorizationEndpoint: true, - configurationType: true, - discoveryURL: true, - jwksUri: true, - tokenEndpoint: true, - userinfoEndpoint: true, - orgId: true, - isActive: true, - allowedEmailDomains: true, - manageGroupMemberships: true - }) + 200: SanitizedOidcConfigSchema } }, diff --git a/backend/src/ee/routes/v1/project-template-router.ts b/backend/src/ee/routes/v1/project-template-router.ts index 60f93d65d..cabf65337 100644 --- a/backend/src/ee/routes/v1/project-template-router.ts +++ b/backend/src/ee/routes/v1/project-template-router.ts @@ -9,7 +9,7 @@ import { ProjectTemplates } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { AuthMode } from "@app/services/auth/auth-type"; const MAX_JSON_SIZE_LIMIT_IN_BYTES = 32_768; diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 933015a66..71facb22a 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -12,13 +12,13 @@ import { MultiSamlStrategy } from "@node-saml/passport-saml"; import { FastifyRequest } from "fastify"; import { z } from "zod"; -import { SamlConfigsSchema } from "@app/db/schemas"; import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { SanitizedSamlConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; type TSAMLConfig = { @@ -298,7 +298,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { cert: z.string() }), response: { - 200: SamlConfigsSchema + 200: SanitizedSamlConfigSchema } }, handler: async (req) => { @@ -333,7 +333,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: SamlConfigsSchema + 200: SanitizedSamlConfigSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index bb3e179dd..de37a4cde 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege"; +import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/sanitizedSchema/user-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts index 7934c3f90..d9c3a05b5 100644 --- a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege"; +import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchema/identitiy-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts index 3a38c0d65..eb9c66c1c 100644 --- a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; +import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts index 16c0cc212..d74f9c504 100644 --- a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index cafc7abf0..e22b18e1b 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,25 +1,18 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { OrgMembershipStatus, SecretKeyEncoding, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -59,7 +52,6 @@ type TLdapConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; - orgBotDAL: Pick; groupDAL: Pick; groupProjectDAL: Pick; projectKeyDAL: Pick; @@ -84,6 +76,7 @@ type TLdapConfigServiceFactoryDep = { licenseService: Pick; tokenService: Pick; smtpService: Pick; + kmsService: Pick; }; export type TLdapConfigServiceFactory = ReturnType; @@ -93,7 +86,6 @@ export const ldapConfigServiceFactory = ({ ldapGroupMapDAL, orgDAL, orgMembershipDAL, - orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -105,7 +97,8 @@ export const ldapConfigServiceFactory = ({ permissionService, licenseService, tokenService, - smtpService + smtpService, + kmsService }: TLdapConfigServiceFactoryDep) => { const createLdapCfg = async ({ actor, @@ -133,77 +126,23 @@ export const ldapConfigServiceFactory = ({ message: "Failed to create LDAP configuration due to plan restriction. Upgrade plan to create LDAP configuration." }); - - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); - const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const ldapConfig = await ldapConfigDAL.create({ orgId, isActive, url, - encryptedBindDN, - bindDNIV, - bindDNTag, - encryptedBindPass, - bindPassIV, - bindPassTag, uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, groupSearchFilter, - encryptedCACert, - caCertIV, - caCertTag + encryptedLdapCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, + encryptedLdapBindDN: encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob, + encryptedLdapBindPass: encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob }); return ldapConfig; @@ -246,38 +185,21 @@ export const ldapConfigServiceFactory = ({ uniqueUserAttribute }; - const orgBot = await orgBotDAL.findOne({ orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot in organization with ID '${orgId}' not found`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); if (bindDN !== undefined) { - const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); - updateQuery.encryptedBindDN = encryptedBindDN; - updateQuery.bindDNIV = bindDNIV; - updateQuery.bindDNTag = bindDNTag; + updateQuery.encryptedLdapBindDN = encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob; } if (bindPass !== undefined) { - const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); - updateQuery.encryptedBindPass = encryptedBindPass; - updateQuery.bindPassIV = bindPassIV; - updateQuery.bindPassTag = bindPassTag; + updateQuery.encryptedLdapBindPass = encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob; } if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCACert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedLdapCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } const [ldapConfig] = await ldapConfigDAL.update({ orgId }, updateQuery); @@ -293,61 +215,24 @@ export const ldapConfigServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found in organization with ID ${ldapConfig.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: ldapConfig.orgId }); - const { - encryptedBindDN, - bindDNIV, - bindDNTag, - encryptedBindPass, - bindPassIV, - bindPassTag, - encryptedCACert, - caCertIV, - caCertTag - } = ldapConfig; - let bindDN = ""; - if (encryptedBindDN && bindDNIV && bindDNTag) { - bindDN = decryptSymmetric({ - ciphertext: encryptedBindDN, - key, - tag: bindDNTag, - iv: bindDNIV - }); + if (ldapConfig.encryptedLdapBindDN) { + bindDN = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindDN }).toString(); } let bindPass = ""; - if (encryptedBindPass && bindPassIV && bindPassTag) { - bindPass = decryptSymmetric({ - ciphertext: encryptedBindPass, - key, - tag: bindPassTag, - iv: bindPassIV - }); + if (ldapConfig.encryptedLdapBindPass) { + bindPass = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindPass }).toString(); } let caCert = ""; - if (encryptedCACert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCACert, - key, - tag: caCertTag, - iv: caCertIV - }); + if (ldapConfig.encryptedLdapCaCertificate) { + caCert = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapCaCertificate }).toString(); } return { diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 0c037a2d3..d4870c53d 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; -import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; @@ -14,21 +14,14 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -70,7 +63,6 @@ type TOidcConfigServiceFactoryDep = { "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; orgMembershipDAL: Pick; - orgBotDAL: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; @@ -91,6 +83,7 @@ type TOidcConfigServiceFactoryDep = { projectDAL: Pick; projectBotDAL: Pick; auditLogService: Pick; + kmsService: Pick; }; export type TOidcConfigServiceFactory = ReturnType; @@ -103,7 +96,6 @@ export const oidcConfigServiceFactory = ({ licenseService, permissionService, tokenService, - orgBotDAL, smtpService, oidcConfigDAL, userGroupMembershipDAL, @@ -112,7 +104,8 @@ export const oidcConfigServiceFactory = ({ projectKeyDAL, projectDAL, projectBotDAL, - auditLogService + auditLogService, + kmsService }: TOidcConfigServiceFactoryDep) => { const getOidc = async (dto: TGetOidcCfgDTO) => { const org = await orgDAL.findOne({ slug: dto.orgSlug }); @@ -143,43 +136,19 @@ export const oidcConfigServiceFactory = ({ }); } - // decrypt and return cfg - const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot for organization with ID '${oidcCfg.orgId}' not found`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: oidcCfg.orgId }); - const { encryptedClientId, clientIdIV, clientIdTag, encryptedClientSecret, clientSecretIV, clientSecretTag } = - oidcCfg; - let clientId = ""; - if (encryptedClientId && clientIdIV && clientIdTag) { - clientId = decryptSymmetric({ - ciphertext: encryptedClientId, - key, - tag: clientIdTag, - iv: clientIdIV - }); + if (oidcCfg.encryptedOidcClientId) { + clientId = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientId }).toString(); } let clientSecret = ""; - if (encryptedClientSecret && clientSecretIV && clientSecretTag) { - clientSecret = decryptSymmetric({ - key, - tag: clientSecretTag, - iv: clientSecretIV, - ciphertext: encryptedClientSecret - }); + if (oidcCfg.encryptedOidcClientSecret) { + clientSecret = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientSecret }).toString(); } return { @@ -540,12 +509,10 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); - const orgBot = await orgBotDAL.findOne({ orgId: org.id }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot for organization with ID '${org.id}' not found`, - name: "OrgBotNotFound" - }); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: org.id + }); const serverCfg = await getServerCfg(); if (isActive && !serverCfg.trustOidcEmails) { @@ -558,13 +525,6 @@ export const oidcConfigServiceFactory = ({ } } - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - const updateQuery: TOidcConfigsUpdate = { allowedEmailDomains, configurationType, @@ -580,22 +540,11 @@ export const oidcConfigServiceFactory = ({ }; if (clientId !== undefined) { - const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); - updateQuery.encryptedClientId = encryptedClientId; - updateQuery.clientIdIV = clientIdIV; - updateQuery.clientIdTag = clientIdTag; + updateQuery.encryptedOidcClientId = encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob; } if (clientSecret !== undefined) { - const { - ciphertext: encryptedClientSecret, - iv: clientSecretIV, - tag: clientSecretTag - } = encryptSymmetric(clientSecret, key); - - updateQuery.encryptedClientSecret = encryptedClientSecret; - updateQuery.clientSecretIV = clientSecretIV; - updateQuery.clientSecretTag = clientSecretTag; + updateQuery.encryptedOidcClientSecret = encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob; } const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery); @@ -647,61 +596,11 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: org.id }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: org.id, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: org.id }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); - const { - ciphertext: encryptedClientSecret, - iv: clientSecretIV, - tag: clientSecretTag - } = encryptSymmetric(clientSecret, key); - const oidcCfg = await oidcConfigDAL.create({ issuer, isActive, @@ -713,13 +612,9 @@ export const oidcConfigServiceFactory = ({ tokenEndpoint, userinfoEndpoint, orgId: org.id, - encryptedClientId, - clientIdIV, - clientIdTag, - encryptedClientSecret, - clientSecretIV, - clientSecretTag, manageGroupMemberships + encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob, + encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob }); return oidcCfg; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index e9ba49127..657e9ce3a 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -6,7 +6,7 @@ import { CASL_ACTION_SCHEMA_NATIVE_ENUM } from "@app/ee/services/permission/permission-schemas"; import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { PermissionConditionSchema } from "./permission-types"; diff --git a/backend/src/ee/services/project-template/project-template-service.ts b/backend/src/ee/services/project-template/project-template-service.ts index 5afa58caf..b2430ac14 100644 --- a/backend/src/ee/services/project-template/project-template-service.ts +++ b/backend/src/ee/services/project-template/project-template-service.ts @@ -15,7 +15,7 @@ import { } from "@app/ee/services/project-template/project-template-types"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; -import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; +import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"; import { TProjectTemplateDALFactory } from "./project-template-dal"; diff --git a/backend/src/ee/services/project-template/project-template-types.ts b/backend/src/ee/services/project-template/project-template-types.ts index 6b600f386..c2764dc53 100644 --- a/backend/src/ee/services/project-template/project-template-types.ts +++ b/backend/src/ee/services/project-template/project-template-types.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { TProjectEnvironments } from "@app/db/schemas"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; export type TProjectTemplateEnvironment = Pick; diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index 14586d5e2..6f87663b2 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 068a45520..f22e2ad58 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -1,29 +1,15 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { - OrgMembershipStatus, - SecretKeyEncoding, - TableName, - TSamlConfigs, - TSamlConfigsUpdate, - TUsers -} from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TSamlConfigs, TSamlConfigsUpdate, TUsers } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -52,21 +38,19 @@ type TSamlConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; - identityMetadataDAL: Pick; orgMembershipDAL: Pick; - orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; + kmsService: Pick; }; export type TSamlConfigServiceFactory = ReturnType; export const samlConfigServiceFactory = ({ samlConfigDAL, - orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -75,7 +59,8 @@ export const samlConfigServiceFactory = ({ licenseService, tokenService, smtpService, - identityMetadataDAL + identityMetadataDAL, + kmsService }: TSamlConfigServiceFactoryDep) => { const createSamlCfg = async ({ cert, @@ -99,70 +84,18 @@ export const samlConfigServiceFactory = ({ "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to create SSO configuration." }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key); - const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); - const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); const samlConfig = await samlConfigDAL.create({ orgId, authProvider, isActive, - encryptedEntryPoint, - entryPointIV, - entryPointTag, - encryptedIssuer, - issuerIV, - issuerTag, - encryptedCert, - certIV, - certTag + encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob, + encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob, + encryptedSamlCertificate: encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob }); return samlConfig; @@ -190,40 +123,21 @@ export const samlConfigServiceFactory = ({ }); const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; - const orgBot = await orgBotDAL.findOne({ orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${orgId}'`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); if (entryPoint !== undefined) { - const { - ciphertext: encryptedEntryPoint, - iv: entryPointIV, - tag: entryPointTag - } = encryptSymmetric(entryPoint, key); - updateQuery.encryptedEntryPoint = encryptedEntryPoint; - updateQuery.entryPointIV = entryPointIV; - updateQuery.entryPointTag = entryPointTag; + updateQuery.encryptedSamlEntryPoint = encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob; } + if (issuer !== undefined) { - const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); - updateQuery.encryptedIssuer = encryptedIssuer; - updateQuery.issuerIV = issuerIV; - updateQuery.issuerTag = issuerTag; + updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob; } + if (cert !== undefined) { - const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); - updateQuery.encryptedCert = encryptedCert; - updateQuery.certIV = certIV; - updateQuery.certTag = certTag; + updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob; } const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); @@ -233,14 +147,14 @@ export const samlConfigServiceFactory = ({ }; const getSaml = async (dto: TGetSamlCfgDTO) => { - let ssoConfig: TSamlConfigs | undefined; + let samlConfig: TSamlConfigs | undefined; if (dto.type === "org") { - ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); - if (!ssoConfig) return; + samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); + if (!samlConfig) return; } else if (dto.type === "orgSlug") { const org = await orgDAL.findOne({ slug: dto.orgSlug }); if (!org) return; - ssoConfig = await samlConfigDAL.findOne({ orgId: org.id }); + samlConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: // We made this change because saml config ids were not moved over during the migration @@ -259,81 +173,51 @@ export const samlConfigServiceFactory = ({ const id = UUIDToMongoId[dto.id] ?? dto.id; - ssoConfig = await samlConfigDAL.findById(id); + samlConfig = await samlConfigDAL.findById(id); } - if (!ssoConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); + if (!samlConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); // when dto is type id means it's internally used if (dto.type === "org") { const { permission } = await permissionService.getOrgPermission( dto.actor, dto.actorId, - ssoConfig.orgId, + samlConfig.orgId, dto.actorAuthMethod, dto.actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } - const { - entryPointTag, - entryPointIV, - encryptedEntryPoint, - certTag, - certIV, - encryptedCert, - issuerTag, - issuerIV, - encryptedIssuer - } = ssoConfig; - - const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found in organization with ID '${ssoConfig.orgId}'`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: samlConfig.orgId }); let entryPoint = ""; - if (encryptedEntryPoint && entryPointIV && entryPointTag) { - entryPoint = decryptSymmetric({ - ciphertext: encryptedEntryPoint, - key, - tag: entryPointTag, - iv: entryPointIV - }); + if (samlConfig.encryptedSamlEntryPoint) { + entryPoint = decryptor({ cipherTextBlob: samlConfig.encryptedSamlEntryPoint }).toString(); } let issuer = ""; - if (encryptedIssuer && issuerTag && issuerIV) { - issuer = decryptSymmetric({ - key, - tag: issuerTag, - iv: issuerIV, - ciphertext: encryptedIssuer - }); + if (samlConfig.encryptedSamlIssuer) { + issuer = decryptor({ cipherTextBlob: samlConfig.encryptedSamlIssuer }).toString(); } let cert = ""; - if (encryptedCert && certTag && certIV) { - cert = decryptSymmetric({ key, tag: certTag, iv: certIV, ciphertext: encryptedCert }); + if (samlConfig.encryptedSamlCertificate) { + cert = decryptor({ cipherTextBlob: samlConfig.encryptedSamlCertificate }).toString(); } return { - id: ssoConfig.id, - organization: ssoConfig.orgId, - orgId: ssoConfig.orgId, - authProvider: ssoConfig.authProvider, - isActive: ssoConfig.isActive, + id: samlConfig.id, + organization: samlConfig.orgId, + orgId: samlConfig.orgId, + authProvider: samlConfig.authProvider, + isActive: samlConfig.isActive, entryPoint, issuer, cert, - lastUsed: ssoConfig.lastUsed + lastUsed: samlConfig.lastUsed }; }; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 5ec01027f..10da81bf5 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -451,7 +451,6 @@ export const registerRoutes = async ( const samlService = samlConfigServiceFactory({ identityMetadataDAL, permissionService, - orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -459,7 +458,8 @@ export const registerRoutes = async ( samlConfigDAL, licenseService, tokenService, - smtpService + smtpService, + kmsService }); const groupService = groupServiceFactory({ userDAL, @@ -510,7 +510,6 @@ export const registerRoutes = async ( ldapGroupMapDAL, orgDAL, orgMembershipDAL, - orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -522,7 +521,8 @@ export const registerRoutes = async ( permissionService, licenseService, tokenService, - smtpService + smtpService, + kmsService }); const telemetryService = telemetryServiceFactory({ @@ -1244,9 +1244,9 @@ export const registerRoutes = async ( identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - orgBotDAL, permissionService, - licenseService + licenseService, + kmsService }); const identityGcpAuthService = identityGcpAuthServiceFactory({ identityGcpAuthDAL, @@ -1278,7 +1278,7 @@ export const registerRoutes = async ( identityAccessTokenDAL, permissionService, licenseService, - orgBotDAL + kmsService }); const identityJwtAuthService = identityJwtAuthServiceFactory({ @@ -1347,7 +1347,7 @@ export const registerRoutes = async ( licenseService, tokenService, smtpService, - orgBotDAL, + kmsService, permissionService, oidcConfigDAL, projectBotDAL, diff --git a/backend/src/server/routes/sanitizedSchema/directory-config.ts b/backend/src/server/routes/sanitizedSchema/directory-config.ts new file mode 100644 index 000000000..61be4d9cf --- /dev/null +++ b/backend/src/server/routes/sanitizedSchema/directory-config.ts @@ -0,0 +1,42 @@ +import { LdapConfigsSchema, OidcConfigsSchema, SamlConfigsSchema } from "@app/db/schemas"; + +export const SanitizedSamlConfigSchema = SamlConfigsSchema.pick({ + id: true, + orgId: true, + isActive: true, + lastUsed: true, + createdAt: true, + updatedAt: true, + authProvider: true +}); + +export const SanitizedLdapConfigSchema = LdapConfigsSchema.pick({ + updatedAt: true, + createdAt: true, + isActive: true, + orgId: true, + id: true, + url: true, + searchBase: true, + searchFilter: true, + groupSearchBase: true, + uniqueUserAttribute: true, + groupSearchFilter: true +}); + +export const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ + id: true, + orgId: true, + isActive: true, + createdAt: true, + updatedAt: true, + lastUsed: true, + issuer: true, + jwksUri: true, + discoveryURL: true, + tokenEndpoint: true, + userinfoEndpoint: true, + configurationType: true, + allowedEmailDomains: true, + authorizationEndpoint: true +}); diff --git a/backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts b/backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts rename to backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts diff --git a/backend/src/server/routes/santizedSchemas/permission.ts b/backend/src/server/routes/sanitizedSchema/permission.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/permission.ts rename to backend/src/server/routes/sanitizedSchema/permission.ts diff --git a/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts b/backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/user-additional-privilege.ts rename to backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index ea5519b55..4d645ac4b 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -11,7 +11,7 @@ import { } from "@app/db/schemas"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "./santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "./sanitizedSchema/permission"; // sometimes the return data must be santizied to avoid leaking important values // always prefer pick over omit in zod @@ -201,7 +201,12 @@ export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({ }); export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ - encryptedInput: true + encryptedInput: true, + keyEncoding: true, + inputCiphertext: true, + inputIV: true, + inputTag: true, + algorithm: true }); export const SanitizedAuditLogStreamSchema = z.object({ diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 3b3025179..263fa478e 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -8,13 +8,19 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; -const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.omit({ - encryptedCaCert: true, - caCertIV: true, - caCertTag: true, - encryptedTokenReviewerJwt: true, - tokenReviewerJwtIV: true, - tokenReviewerJwtTag: true +const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({ + id: true, + accessTokenTTL: true, + accessTokenMaxTTL: true, + accessTokenNumUsesLimit: true, + accessTokenTrustedIps: true, + createdAt: true, + updatedAt: true, + identityId: true, + kubernetesHost: true, + allowedNamespaces: true, + allowedNames: true, + allowedAudience: true }).extend({ caCert: z.string(), tokenReviewerJwt: z.string() diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 431ed3f4f..799784e45 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -13,9 +13,19 @@ import { } from "@app/services/identity-oidc-auth/identity-oidc-auth-validators"; const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.omit({ - encryptedCaCert: true, - caCertIV: true, - caCertTag: true + id: true, + accessTokenTTL: true, + accessTokenMaxTTL: true, + accessTokenNumUsesLimit: true, + accessTokenTrustedIps: true, + identityId: true, + oidcDiscoveryUrl: true, + boundIssuer: true, + boundAudiences: true, + boundClaims: true, + boundSubject: true, + createdAt: true, + updatedAt: true }).extend({ caCert: z.string() }); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 4508a255d..a5677894d 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -3,28 +3,21 @@ import axios, { AxiosError } from "axios"; import https from "https"; import jwt from "jsonwebtoken"; -import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TIdentityKubernetesAuthDALFactory } from "./identity-kubernetes-auth-dal"; import { extractK8sUsername } from "./identity-kubernetes-auth-fns"; import { @@ -43,9 +36,9 @@ type TIdentityKubernetesAuthServiceFactoryDep = { >; identityAccessTokenDAL: Pick; identityOrgMembershipDAL: Pick; - orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; + kmsService: Pick; }; export type TIdentityKubernetesAuthServiceFactory = ReturnType; @@ -54,9 +47,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - orgBotDAL, permissionService, - licenseService + licenseService, + kmsService }: TIdentityKubernetesAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => { const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); @@ -75,42 +68,21 @@ export const identityKubernetesAuthServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = - identityKubernetesAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); } let tokenReviewerJwt = ""; - if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { - tokenReviewerJwt = decryptSymmetric({ - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { + tokenReviewerJwt = decryptor({ + cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString(); } const { data } = await axios @@ -297,79 +269,25 @@ export const identityKubernetesAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: identityMembershipOrg.orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const { - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag - } = encryptSymmetric(tokenReviewerJwt, key); - const identityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => { const doc = await identityKubernetesAuthDAL.create( { identityId: identityMembershipOrg.identityId, kubernetesHost, - encryptedCaCert, - caCertIV, - caCertTag, - encryptedTokenReviewerJwt, - tokenReviewerJwtIV, - tokenReviewerJwtTag, allowedNamespaces, allowedNames, allowedAudience, accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) + accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), + encryptedKubernetesTokenReviewerJwt: encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob, + encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob }, tx ); @@ -455,61 +373,34 @@ export const identityKubernetesAuthServiceFactory = ({ : undefined }; - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCaCert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } if (tokenReviewerJwt !== undefined) { - const { - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag - } = encryptSymmetric(tokenReviewerJwt, key); - updateQuery.encryptedTokenReviewerJwt = encryptedTokenReviewerJwt; - updateQuery.tokenReviewerJwtIV = tokenReviewerJwtIV; - updateQuery.tokenReviewerJwtTag = tokenReviewerJwtTag; + updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({ + plainText: Buffer.from(tokenReviewerJwt) + }).cipherTextBlob; } const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery); - const updatedCACert = - updatedKubernetesAuth.encryptedCaCert && updatedKubernetesAuth.caCertIV && updatedKubernetesAuth.caCertTag - ? decryptSymmetric({ - ciphertext: updatedKubernetesAuth.encryptedCaCert, - iv: updatedKubernetesAuth.caCertIV, - tag: updatedKubernetesAuth.caCertTag, - key - }) - : ""; + const updatedCACert = updatedKubernetesAuth.encryptedKubernetesCaCertificate + ? decryptor({ + cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesCaCertificate + }).toString() + : ""; - const updatedTokenReviewerJwt = - updatedKubernetesAuth.encryptedTokenReviewerJwt && - updatedKubernetesAuth.tokenReviewerJwtIV && - updatedKubernetesAuth.tokenReviewerJwtTag - ? decryptSymmetric({ - ciphertext: updatedKubernetesAuth.encryptedTokenReviewerJwt, - iv: updatedKubernetesAuth.tokenReviewerJwtIV, - tag: updatedKubernetesAuth.tokenReviewerJwtTag, - key - }) - : ""; + const updatedTokenReviewerJwt = updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt + ? decryptor({ + cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString() + : ""; return { ...updatedKubernetesAuth, @@ -545,41 +436,21 @@ export const identityKubernetesAuthServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = - identityKubernetesAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); } let tokenReviewerJwt = ""; - if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { - tokenReviewerJwt = decryptSymmetric({ - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { + tokenReviewerJwt = decryptor({ + cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString(); } return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId }; diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index a1dbed46b..ff7256a9c 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -4,20 +4,12 @@ import https from "https"; import jwt from "jsonwebtoken"; import { JwksClient } from "jwks-rsa"; -import { IdentityAuthMethod, SecretKeyEncoding, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; -import { generateAsymmetricKeyPair } from "@app/lib/crypto"; -import { - decryptSymmetric, - encryptSymmetric, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -25,7 +17,8 @@ import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; -import { TOrgBotDALFactory } from "../org/org-bot-dal"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal"; import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns"; import { @@ -42,7 +35,7 @@ type TIdentityOidcAuthServiceFactoryDep = { identityAccessTokenDAL: Pick; permissionService: Pick; licenseService: Pick; - orgBotDAL: Pick; + kmsService: Pick; }; export type TIdentityOidcAuthServiceFactory = ReturnType; @@ -53,7 +46,7 @@ export const identityOidcAuthServiceFactory = ({ permissionService, licenseService, identityAccessTokenDAL, - orgBotDAL + kmsService }: TIdentityOidcAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: oidcJwt }: TLoginOidcAuthDTO) => { const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); @@ -70,31 +63,14 @@ export const identityOidcAuthServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag } = identityOidcAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityOidcAuth.encryptedCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString(); } const requestAgent = new https.Agent({ ca: caCert, rejectUnauthorized: !!caCert }); @@ -264,64 +240,17 @@ export const identityOidcAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: identityMembershipOrg.orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const identityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => { const doc = await identityOidcAuthDAL.create( { identityId: identityMembershipOrg.identityId, oidcDiscoveryUrl, - encryptedCaCert, - caCertIV, - caCertTag, + encryptedCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, boundIssuer, boundAudiences, boundClaims, @@ -415,38 +344,19 @@ export const identityOidcAuthServiceFactory = ({ : undefined }; - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCaCert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } const updatedOidcAuth = await identityOidcAuthDAL.updateById(identityOidcAuth.id, updateQuery); - const updatedCACert = - updatedOidcAuth.encryptedCaCert && updatedOidcAuth.caCertIV && updatedOidcAuth.caCertTag - ? decryptSymmetric({ - ciphertext: updatedOidcAuth.encryptedCaCert, - iv: updatedOidcAuth.caCertIV, - tag: updatedOidcAuth.caCertTag, - key - }) - : ""; + const updatedCACert = updatedOidcAuth.encryptedCaCertificate + ? decryptor({ cipherTextBlob: updatedOidcAuth.encryptedCaCertificate }).toString() + : ""; return { ...updatedOidcAuth, @@ -476,27 +386,14 @@ export const identityOidcAuthServiceFactory = ({ const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const caCert = decryptSymmetric({ - ciphertext: identityOidcAuth.encryptedCaCert, - iv: identityOidcAuth.caCertIV, - tag: identityOidcAuth.caCertTag, - key - }); + const caCert = identityOidcAuth.encryptedCaCertificate + ? decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString() + : ""; return { ...identityOidcAuth, orgId: identityMembershipOrg.orgId, caCert }; }; diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 09bc6460d..1d695a5ff 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -9,7 +9,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorAuthMethod } from "../auth/auth-type"; import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal"; From 1e4164e1c24b882c22c2f231eafc0a6afa4dd175 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 1 Dec 2024 23:00:07 +0530 Subject: [PATCH 08/41] feat: resolved migration failing due to json --- .../migrations/20241129180030_identity-k8-auth-to-kms.ts | 7 ++++++- .../migrations/20241129180053_identity-oidc-auth-to-kms.ts | 6 +++++- backend/src/server/routes/v1/identity-oidc-auth-router.ts | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts index 2bbe8022b..b1b3a7ec1 100644 --- a/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts @@ -137,7 +137,12 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { plainText: Buffer.from(decryptedCertificate) }).cipherTextBlob; - return { ...el, encryptedKubernetesCaCertificate, encryptedKubernetesTokenReviewerJwt }; + return { + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedKubernetesCaCertificate, + encryptedKubernetesTokenReviewerJwt + }; } ) ); diff --git a/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts index 4aed557d5..e4947b296 100644 --- a/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts @@ -96,7 +96,11 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { plainText: Buffer.from(decryptedCertificate) }).cipherTextBlob; - return { ...el, encryptedCaCertificate }; + return { + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedCaCertificate + }; } ) ); diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 799784e45..7ce0b05b7 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -12,7 +12,7 @@ import { validateOidcBoundClaimsField } from "@app/services/identity-oidc-auth/identity-oidc-auth-validators"; -const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.omit({ +const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({ id: true, accessTokenTTL: true, accessTokenMaxTTL: true, From 9eed67c21b444d04efd63b02cd64c67644e48706 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 9 Jan 2025 20:09:54 +0530 Subject: [PATCH 09/41] feat: updated migration to latest --- backend/src/@types/fastify.d.ts | 2 +- ...91918_webhook-to-kms.ts => 20250109104500_webhook-to-kms.ts} | 0 ...ation-to-kms.ts => 20250109104501_secret-rotation-to-kms.ts} | 0 ...auth-to-kms.ts => 20250109104502_identity-k8-auth-to-kms.ts} | 0 ...th-to-kms.ts => 20250109104502_identity-oidc-auth-to-kms.ts} | 0 ...t-to-kms.ts => 20250109104503_dynamic-secret-root-to-kms.ts} | 0 ...nfig-to-kms.ts => 20250109104508_directory-config-to-kms.ts} | 0 backend/src/server/app.ts | 2 +- 8 files changed, 2 insertions(+), 2 deletions(-) rename backend/src/db/migrations/{20241127091918_webhook-to-kms.ts => 20250109104500_webhook-to-kms.ts} (100%) rename backend/src/db/migrations/{20241128090536_secret-rotation-to-kms.ts => 20250109104501_secret-rotation-to-kms.ts} (100%) rename backend/src/db/migrations/{20241129180030_identity-k8-auth-to-kms.ts => 20250109104502_identity-k8-auth-to-kms.ts} (100%) rename backend/src/db/migrations/{20241129180053_identity-oidc-auth-to-kms.ts => 20250109104502_identity-oidc-auth-to-kms.ts} (100%) rename backend/src/db/migrations/{20241128092853_dynamic-secret-root-to-kms.ts => 20250109104503_dynamic-secret-root-to-kms.ts} (100%) rename backend/src/db/migrations/{20241129175559_directory-config-to-kms.ts => 20250109104508_directory-config-to-kms.ts} (100%) diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index b2a37755f..c02347038 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -95,7 +95,7 @@ import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integ declare module "@fastify/request-context" { interface RequestContextData { - requestId: string; + reqId: string; } } diff --git a/backend/src/db/migrations/20241127091918_webhook-to-kms.ts b/backend/src/db/migrations/20250109104500_webhook-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241127091918_webhook-to-kms.ts rename to backend/src/db/migrations/20250109104500_webhook-to-kms.ts diff --git a/backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241128090536_secret-rotation-to-kms.ts rename to backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts diff --git a/backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241129180030_identity-k8-auth-to-kms.ts rename to backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts diff --git a/backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241129180053_identity-oidc-auth-to-kms.ts rename to backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts diff --git a/backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241128092853_dynamic-secret-root-to-kms.ts rename to backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts diff --git a/backend/src/db/migrations/20241129175559_directory-config-to-kms.ts b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20241129175559_directory-config-to-kms.ts rename to backend/src/db/migrations/20250109104508_directory-config-to-kms.ts diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 577b115ae..26f556508 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -143,4 +143,4 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key await queue.shutdown(); process.exit(1); } -}; \ No newline at end of file +}; From 648fde8f3733acb8029133e0d1e2afbf7da3a9da Mon Sep 17 00:00:00 2001 From: = Date: Mon, 3 Feb 2025 23:26:43 +0530 Subject: [PATCH 10/41] feat: review changes --- .../migrations/20250109104500_webhook-to-kms.ts | 7 ++++--- .../20250109104501_secret-rotation-to-kms.ts | 7 ++++--- .../20250109104502_identity-k8-auth-to-kms.ts | 7 ++++--- .../20250109104502_identity-oidc-auth-to-kms.ts | 7 ++++--- ...20250109104503_dynamic-secret-root-to-kms.ts | 7 ++++--- .../20250109104508_directory-config-to-kms.ts | 17 ++++++++++------- backend/src/db/migrations/utils/ring-buffer.ts | 2 +- 7 files changed, 31 insertions(+), 23 deletions(-) diff --git a/backend/src/db/migrations/20250109104500_webhook-to-kms.ts b/backend/src/db/migrations/20250109104500_webhook-to-kms.ts index 830d53ace..6c4936613 100644 --- a/backend/src/db/migrations/20250109104500_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250109104500_webhook-to-kms.ts @@ -7,7 +7,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -30,7 +30,7 @@ export async function up(knex: Knex): Promise { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const webhooks = await knex(TableName.Webhook) .where({}) @@ -47,7 +47,8 @@ export async function up(knex: Knex): Promise { knex.ref("id").withSchema(TableName.Webhook), "envId" ) - .select(knex.ref("projectId").withSchema(TableName.Environment)); + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); const updatedWebhooks = await Promise.all( webhooks.map(async (el) => { diff --git a/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts index 808f56d07..fc88171bc 100644 --- a/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts @@ -8,7 +8,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -27,12 +27,13 @@ export async function up(knex: Knex): Promise { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const secretRotations = await knex(TableName.SecretRotation) .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) .select(selectAllTableCols(TableName.SecretRotation)) - .select(knex.ref("projectId").withSchema(TableName.Environment)); + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); const updatedRotationData = await Promise.all( secretRotations.map(async ({ projectId, ...el }) => { diff --git a/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts index b1b3a7ec1..1acc588cb 100644 --- a/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts @@ -8,7 +8,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -58,7 +58,7 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const identityKubernetesConfigs = await knex(TableName.IdentityKubernetesAuth) .join( @@ -74,7 +74,8 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), knex.ref("orgId").withSchema(TableName.OrgBot) - ); + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); const updatedIdentityKubernetesConfigs = await Promise.all( identityKubernetesConfigs.map( diff --git a/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts index e4947b296..936d5a41b 100644 --- a/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts @@ -8,7 +8,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -38,7 +38,7 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const identityOidcConfig = await knex(TableName.IdentityOidcAuth) .join( @@ -54,7 +54,8 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), knex.ref("orgId").withSchema(TableName.OrgBot) - ); + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); const updatedIdentityOidcConfigs = await Promise.all( identityOidcConfig.map( diff --git a/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts index a9caceb12..8d8e34673 100644 --- a/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts @@ -8,7 +8,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -33,13 +33,14 @@ export async function up(knex: Knex): Promise { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret) .join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) .select(selectAllTableCols(TableName.DynamicSecret)) - .select(knex.ref("projectId").withSchema(TableName.Environment)); + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); const updatedDynamicSecrets = await Promise.all( dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => { diff --git a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts index 853dfba2e..4addc2d81 100644 --- a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts @@ -8,7 +8,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types"; import { SecretKeyEncoding, TableName } from "../schemas"; import { getMigrationEnvConfig } from "./utils/env-config"; -import { newRingBuffer } from "./utils/ring-buffer"; +import { createCircularCache } from "./utils/ring-buffer"; import { getMigrationEncryptionServices } from "./utils/services"; const BATCH_SIZE = 500; @@ -31,7 +31,7 @@ const reencryptSamlConfig = async (knex: Knex) => { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const samlConfigs = await knex(TableName.SamlConfig) .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.SamlConfig}.orgId`) @@ -41,7 +41,8 @@ const reencryptSamlConfig = async (knex: Knex) => { knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ); + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); const updatedSamlConfigs = await Promise.all( samlConfigs.map( @@ -185,7 +186,7 @@ const reencryptLdapConfig = async (knex: Knex) => { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const ldapConfigs = await knex(TableName.LdapConfig) .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.LdapConfig}.orgId`) @@ -195,7 +196,8 @@ const reencryptLdapConfig = async (knex: Knex) => { knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ); + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); const updatedLdapConfigs = await Promise.all( ldapConfigs.map( @@ -334,7 +336,7 @@ const reencryptOidcConfig = async (knex: Knex) => { const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = - newRingBuffer>>(25); + createCircularCache>>(25); const oidcConfigs = await knex(TableName.OidcConfig) .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.OidcConfig}.orgId`) @@ -344,7 +346,8 @@ const reencryptOidcConfig = async (knex: Knex) => { knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ); + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); const updatedOidcConfigs = await Promise.all( oidcConfigs.map( diff --git a/backend/src/db/migrations/utils/ring-buffer.ts b/backend/src/db/migrations/utils/ring-buffer.ts index d738031ea..8e5c58662 100644 --- a/backend/src/db/migrations/utils/ring-buffer.ts +++ b/backend/src/db/migrations/utils/ring-buffer.ts @@ -1,4 +1,4 @@ -export const newRingBuffer = (bufferSize = 10) => { +export const createCircularCache = (bufferSize = 10) => { const bufferItems: { id: string; item: T }[] = []; let bufferIndex = 0; From b9dee1e6e82e0a0b39750f6243ec9306127b5776 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 3 Feb 2025 23:34:27 +0530 Subject: [PATCH 11/41] fix: merge conflicts --- backend/src/db/schemas/oidc-configs.ts | 2 +- backend/src/ee/services/oidc/oidc-config-service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts index 55eb5607b..76923aee8 100644 --- a/backend/src/db/schemas/oidc-configs.ts +++ b/backend/src/db/schemas/oidc-configs.ts @@ -30,7 +30,7 @@ export const OidcConfigsSchema = z.object({ updatedAt: z.date(), orgId: z.string().uuid(), lastUsed: z.date().nullable().optional(), - manageGroupMemberships: z.boolean().default(false) + manageGroupMemberships: z.boolean().default(false), encryptedOidcClientId: zodBuffer, encryptedOidcClientSecret: zodBuffer }); diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index d4870c53d..52c8dd597 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -612,7 +612,7 @@ export const oidcConfigServiceFactory = ({ tokenEndpoint, userinfoEndpoint, orgId: org.id, - manageGroupMemberships + manageGroupMemberships, encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob, encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob }); From 6671c42d0f4d09bd51c95fd8298a4c52f1c5efb4 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 4 Feb 2025 00:08:07 +0530 Subject: [PATCH 12/41] feat: made ldap cert nullable and optional --- .../src/db/migrations/20250109104508_directory-config-to-kms.ts | 2 +- backend/src/db/schemas/ldap-configs.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts index 4addc2d81..9771e57c5 100644 --- a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts @@ -296,7 +296,7 @@ const reencryptLdapConfig = async (knex: Knex) => { await knex.schema.alterTable(TableName.LdapConfig, (t) => { if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass").notNullable().alter(); if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN").notNullable().alter(); - if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate").notNullable().alter(); + if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate"); }); } }; diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index 94bf0dd03..778e7be6e 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -32,7 +32,7 @@ export const LdapConfigsSchema = z.object({ uniqueUserAttribute: z.string().default(""), encryptedLdapBindDN: zodBuffer, encryptedLdapBindPass: zodBuffer, - encryptedLdapCaCertificate: zodBuffer + encryptedLdapCaCertificate: zodBuffer.nullable().optional() }); export type TLdapConfigs = z.infer; From 8204e970a86202cf685273ababad3d540065a004 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 4 Feb 2025 00:21:16 +0530 Subject: [PATCH 13/41] fix: resolved be failing --- .../src/db/migrations/20250109104508_directory-config-to-kms.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts index 9771e57c5..9c9c0b5a3 100644 --- a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts @@ -296,7 +296,6 @@ const reencryptLdapConfig = async (knex: Knex) => { await knex.schema.alterTable(TableName.LdapConfig, (t) => { if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass").notNullable().alter(); if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN").notNullable().alter(); - if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate"); }); } }; From 9602b864d41c1302914499c2ff5a5a30173556d7 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 5 Feb 2025 22:59:21 +0530 Subject: [PATCH 14/41] feat: updated migration to be auto matic --- backend/e2e-test/vitest-environment-knex.ts | 5 +- backend/src/auto-start-migrations.ts | 79 +++++++++++++++++++ backend/src/db/instance.ts | 9 +++ .../20250109104500_webhook-to-kms.ts | 2 +- .../20250109104501_secret-rotation-to-kms.ts | 2 +- .../20250109104502_identity-k8-auth-to-kms.ts | 2 +- ...0250109104502_identity-oidc-auth-to-kms.ts | 2 +- ...250109104503_dynamic-secret-root-to-kms.ts | 2 +- .../20250109104508_directory-config-to-kms.ts | 6 +- backend/src/db/migrations/utils/services.ts | 1 - backend/src/keystore/keystore.ts | 5 ++ backend/src/lib/logger/logger.ts | 2 +- backend/src/main.ts | 24 +----- .../super-admin/super-admin-service.ts | 27 ++++--- docker-compose.dev.yml | 26 ++---- docker-compose.prod.yml | 17 +--- 16 files changed, 133 insertions(+), 78 deletions(-) create mode 100644 backend/src/auto-start-migrations.ts diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 8158644c9..9dfb38aeb 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -23,7 +23,7 @@ export default { name: "knex-env", transformMode: "ssr", async setup() { - const logger = await initLogger(); + const logger = initLogger(); const envConfig = initEnvConfig(logger); const db = initDbConnection({ dbConnectionUri: envConfig.DB_CONNECTION_URI, @@ -119,4 +119,5 @@ export default { } }; } -}; \ No newline at end of file +}; + diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts new file mode 100644 index 000000000..5177eaf4a --- /dev/null +++ b/backend/src/auto-start-migrations.ts @@ -0,0 +1,79 @@ +import path from "node:path"; + +import dotenv from "dotenv"; +import { Knex } from "knex"; +import { Logger } from "pino"; + +import { PgSqlLock } from "./keystore/keystore"; + +dotenv.config(); + +type TArgs = { + auditLogDb?: Knex; + applicationDb: Knex; + logger: Logger; +}; + +const migrationConfig = { + directory: path.join(__dirname, "./db/migrations"), + extension: "ts", + tableName: "infisical_migrations" +}; + +const migrationStatusCheckErrorHandler = (err: Error) => { + // happens for first time in which the migration table itself is not created yet + // error: select * from "infisical_migrations" - relation "infisical_migrations" does not exist + if (err?.message?.includes("does not exist")) { + return true; + } + throw err; +}; + +export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { + try { + const shouldRunMigration = Boolean( + await applicationDb.migrate.status(migrationConfig).catch(migrationStatusCheckErrorHandler) + ); // db.length - code.length + if (!shouldRunMigration) { + logger.info("No migrations pending: Skipping migration process."); + return; + } + + if (auditLogDb) { + await auditLogDb.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); + logger.info("Running audit log migrations."); + + const didPreviousInstanceRunMigration = !(await auditLogDb.migrate + .status(migrationConfig) + .catch(migrationStatusCheckErrorHandler)); + if (didPreviousInstanceRunMigration) { + logger.info("No audit log migrations pending: Applied by previous instance. Skipping migration process."); + return; + } + + await auditLogDb.migrate.latest(migrationConfig); + logger.info("Finished audit log migrations."); + }); + } + + await applicationDb.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); + logger.info("Running application migrations."); + + const didPreviousInstanceRunMigration = !(await applicationDb.migrate + .status(migrationConfig) + .catch(migrationStatusCheckErrorHandler)); + if (didPreviousInstanceRunMigration) { + logger.info("No application migrations pending: Applied by previous instance. Skipping migration process."); + return; + } + + await applicationDb.migrate.latest(migrationConfig); + logger.info("Finished application migrations."); + }); + } catch (err) { + logger.error(err, "Boot up migration failed"); + process.exit(1); + } +}; diff --git a/backend/src/db/instance.ts b/backend/src/db/instance.ts index d4a2a5b2c..5a8dd3d05 100644 --- a/backend/src/db/instance.ts +++ b/backend/src/db/instance.ts @@ -49,6 +49,9 @@ export const initDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); @@ -64,6 +67,9 @@ export const initDbConnection = ({ ca: Buffer.from(replicaDbCertificate, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); }); @@ -98,6 +104,9 @@ export const initAuditLogDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); diff --git a/backend/src/db/migrations/20250109104500_webhook-to-kms.ts b/backend/src/db/migrations/20250109104500_webhook-to-kms.ts index 6c4936613..0836e1b48 100644 --- a/backend/src/db/migrations/20250109104500_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250109104500_webhook-to-kms.ts @@ -25,7 +25,7 @@ export async function up(knex: Knex): Promise { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts index fc88171bc..9d10471ad 100644 --- a/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts @@ -22,7 +22,7 @@ export async function up(knex: Knex): Promise { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts index 1acc588cb..2aa1b61fd 100644 --- a/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts @@ -53,7 +53,7 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts index 936d5a41b..1b3a4d50c 100644 --- a/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts @@ -33,7 +33,7 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts index 8d8e34673..1abf132d3 100644 --- a/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts @@ -28,7 +28,7 @@ export async function up(knex: Knex): Promise { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts index 9c9c0b5a3..2497bba05 100644 --- a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts @@ -26,7 +26,7 @@ const reencryptSamlConfig = async (knex: Knex) => { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); @@ -181,7 +181,7 @@ const reencryptLdapConfig = async (knex: Knex) => { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); @@ -330,7 +330,7 @@ const reencryptOidcConfig = async (knex: Knex) => { }); } - await initLogger(); + initLogger(); const envConfig = getMigrationEnvConfig(); const keyStore = inMemoryKeyStore(); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts index 4709248f5..731f703e2 100644 --- a/backend/src/db/migrations/utils/services.ts +++ b/backend/src/db/migrations/utils/services.ts @@ -20,7 +20,6 @@ type TDependencies = { export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { // eslint-disable-next-line no-param-reassign - db.replicaNode = () => db; const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index dbfdfd063..2214a3970 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -2,6 +2,11 @@ import { Redis } from "ioredis"; import { Redlock, Settings } from "@app/lib/red-lock"; +export enum PgSqlLock { + BootUpMigration = 2023, + SuperAdminInit = 2024 +} + export type TKeyStoreFactory = ReturnType; // all the key prefixes used must be set here to avoid conflict diff --git a/backend/src/lib/logger/logger.ts b/backend/src/lib/logger/logger.ts index 9676496f7..170a0285f 100644 --- a/backend/src/lib/logger/logger.ts +++ b/backend/src/lib/logger/logger.ts @@ -98,7 +98,7 @@ const extractReqId = () => { } }; -export const initLogger = async () => { +export const initLogger = () => { const cfg = loggerConfig.parse(process.env); const targets: pino.TransportMultiOptions["targets"][number][] = [ { diff --git a/backend/src/main.ts b/backend/src/main.ts index 076c1df65..461601fc0 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -2,14 +2,13 @@ import "./lib/telemetry/instrumentation"; import dotenv from "dotenv"; import { Redis } from "ioredis"; -import path from "path"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; +import { runMigrations } from "./auto-start-migrations"; import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; -import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env"; -import { isMigrationMode } from "./lib/fn"; +import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; @@ -19,7 +18,7 @@ import { smtpServiceFactory } from "./services/smtp/smtp-service"; dotenv.config(); const run = async () => { - const logger = await initLogger(); + const logger = initLogger(); const envConfig = initEnvConfig(logger); const db = initDbConnection({ @@ -38,22 +37,7 @@ const run = async () => { }) : undefined; - // Case: App is running in packaged mode (binary), and migration mode is enabled. - // Run the migrations and exit the process after completion. - if (IS_PACKAGED && isMigrationMode()) { - try { - logger.info("Running Postgres migrations.."); - await db.migrate.latest({ - directory: path.join(__dirname, "./db/migrations") - }); - logger.info("Postgres migrations completed"); - } catch (err) { - logger.error(err, "Failed to run migrations"); - process.exit(1); - } - - process.exit(0); - } + await runMigrations({ applicationDb: db, auditLogDb, logger }); const smtp = smtpServiceFactory(formatSmtpConfig()); diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index b0fdd9c5c..a8f432d92 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { TKeyStoreFactory } from "@app/keystore/keystore"; +import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; @@ -87,17 +87,24 @@ export const superAdminServiceFactory = ({ // reset on initialized await keyStore.deleteItem(ADMIN_CONFIG_KEY); - const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); - if (serverCfg) return; + const serverCfg = await serverCfgDAL.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.SuperAdminInit]); + const serverCfgInDB = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID, tx); + if (serverCfgInDB) return serverCfgInDB; - const newCfg = await serverCfgDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: ADMIN_CONFIG_DB_UUID, - initialized: false, - allowSignUp: true, - defaultAuthOrgId: null + const newCfg = await serverCfgDAL.create( + { + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: ADMIN_CONFIG_DB_UUID, + initialized: false, + allowSignUp: true, + defaultAuthOrgId: null + }, + tx + ); + return newCfg; }); - return newCfg; + return serverCfg; }; const updateServerCfg = async ( diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 40d17c1b0..9f467d547 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -56,22 +56,8 @@ services: POSTGRES_USER: infisical POSTGRES_DB: infisical-test - db-migration: - container_name: infisical-db-migration - depends_on: - - db - build: - context: ./backend - dockerfile: Dockerfile.dev - env_file: .env - environment: - - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable - command: npm run migration:latest - volumes: - - ./backend/src:/app/src - backend: - container_name: infisical-dev-api + # container_name: infisical-dev-api build: context: ./backend dockerfile: Dockerfile.dev @@ -80,13 +66,11 @@ services: condition: service_started redis: condition: service_started - db-migration: - condition: service_completed_successfully env_file: - .env ports: - - 4000:4000 - - 9464:9464 # for OTEL collection of Prometheus metrics + - 4000-4010:4000 + # - 9464:9464 # for OTEL collection of Prometheus metrics environment: - NODE_ENV=development - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable @@ -192,7 +176,7 @@ services: depends_on: - openldap profiles: [ldap] - + keycloak: image: quay.io/keycloak/keycloak:26.1.0 restart: always @@ -202,7 +186,7 @@ services: command: start-dev ports: - 8088:8080 - profiles: [ sso ] + profiles: [sso] volumes: postgres-data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 77a1e04ab..d3526d841 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,18 +1,6 @@ version: "3" services: - db-migration: - container_name: infisical-db-migration - depends_on: - db: - condition: service_healthy - image: infisical/infisical:latest-postgres - env_file: .env - command: npm run migration:latest - pull_policy: always - networks: - - infisical - backend: container_name: infisical-backend restart: unless-stopped @@ -21,8 +9,6 @@ services: condition: service_healthy redis: condition: service_started - db-migration: - condition: service_completed_successfully image: infisical/infisical:latest-postgres pull_policy: always env_file: .env @@ -69,4 +55,5 @@ volumes: driver: local networks: - infisical: \ No newline at end of file + infisical: + From 46821ca2ee2167a7156c8c7a16fac297b98cce7e Mon Sep 17 00:00:00 2001 From: = Date: Wed, 5 Feb 2025 23:05:02 +0530 Subject: [PATCH 15/41] feat: migration automatic information on running migration without env --- backend/src/db/migrations/utils/env-config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts index 29bbb3f92..05ccae97b 100644 --- a/backend/src/db/migrations/utils/env-config.ts +++ b/backend/src/db/migrations/utils/env-config.ts @@ -41,6 +41,10 @@ export const getMigrationEnvConfig = () => { // eslint-disable-next-line no-console console.error("Invalid environment variables. Check the error below"); // eslint-disable-next-line no-console + console.error( + "Migration is now automatic at startup. Please remove this step from your workflow and start the application as normal." + ); + // eslint-disable-next-line no-console console.error(parsedEnv.error.issues); process.exit(-1); } From 501022752b10a1bb878be53702b9b6431f79b7eb Mon Sep 17 00:00:00 2001 From: = Date: Wed, 5 Feb 2025 23:14:17 +0530 Subject: [PATCH 16/41] fix: corrected docker compose --- docker-compose.dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9f467d547..680a655d8 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -57,7 +57,7 @@ services: POSTGRES_DB: infisical-test backend: - # container_name: infisical-dev-api + container_name: infisical-dev-api build: context: ./backend dockerfile: Dockerfile.dev @@ -69,8 +69,8 @@ services: env_file: - .env ports: - - 4000-4010:4000 - # - 9464:9464 # for OTEL collection of Prometheus metrics + - 4000:4000 + - 9464:9464 # for OTEL collection of Prometheus metrics environment: - NODE_ENV=development - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable From f4e19f8a2edaa4244f0393207d5a0db2a173996d Mon Sep 17 00:00:00 2001 From: = Date: Wed, 5 Feb 2025 23:36:35 +0530 Subject: [PATCH 17/41] feat: disabled eslint for snapshot dal files due to strange error due to some kinda conflicts --- .../src/ee/services/secret-snapshot/secret-snapshot-service.ts | 2 ++ backend/src/ee/services/secret-snapshot/snapshot-dal.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 2e4ed0f93..1c34f6b3d 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ +// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error import { ForbiddenError, subject } from "@casl/ability"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; diff --git a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts index 8a9eeab8c..d8240f27e 100644 --- a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts +++ b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-await-in-loop */ +/* eslint-disable no-await-in-loop,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ import { Knex } from "knex"; import { z } from "zod"; From 1b15cb4c35cd588930077a79e740c4cc7ff14c2e Mon Sep 17 00:00:00 2001 From: = Date: Thu, 6 Feb 2025 15:08:37 +0530 Subject: [PATCH 18/41] feat: updated kms init to use pgsql lock --- backend/src/db/migrations/utils/kms.ts | 105 ------------------------ backend/src/keystore/keystore.ts | 3 +- backend/src/services/kms/kms-service.ts | 73 ++++++---------- 3 files changed, 28 insertions(+), 153 deletions(-) delete mode 100644 backend/src/db/migrations/utils/kms.ts diff --git a/backend/src/db/migrations/utils/kms.ts b/backend/src/db/migrations/utils/kms.ts deleted file mode 100644 index 9ed090978..000000000 --- a/backend/src/db/migrations/utils/kms.ts +++ /dev/null @@ -1,105 +0,0 @@ -import slugify from "@sindresorhus/slugify"; -import { Knex } from "knex"; - -import { TableName } from "@app/db/schemas"; -import { randomSecureBytes } from "@app/lib/crypto"; -import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; -import { alphaNumericNanoId } from "@app/lib/nanoid"; - -const getInstanceRootKey = async (knex: Knex) => { - const encryptionKey = process.env.ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; - // if root key its base64 encoded - const isBase64 = !process.env.ENCRYPTION_KEY; - if (!encryptionKey) throw new Error("ENCRYPTION_KEY variable needed for migration"); - const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8"); - - const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000"; - const kmsRootConfig = await knex(TableName.KmsServerRootConfig).where({ id: KMS_ROOT_CONFIG_UUID }).first(); - const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); - if (kmsRootConfig) { - const decryptedRootKey = cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer); - // set the flag so that other instancen nodes can start - return decryptedRootKey; - } - - const newRootKey = randomSecureBytes(32); - const encryptedRootKey = cipher.encrypt(newRootKey, encryptionKeyBuffer); - await knex(TableName.KmsServerRootConfig).insert({ - encryptedRootKey, - // eslint-disable-next-line - // @ts-ignore id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID - }); - return encryptedRootKey; -}; - -export const getSecretManagerDataKey = async (knex: Knex, projectId: string) => { - const KMS_VERSION = "v01"; - const KMS_VERSION_BLOB_LENGTH = 3; - const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); - const project = await knex(TableName.Project).where({ id: projectId }).first(); - if (!project) throw new Error("Missing project id"); - - const ROOT_ENCRYPTION_KEY = await getInstanceRootKey(knex); - - let secretManagerKmsKey; - const projectSecretManagerKmsId = project?.kmsSecretManagerKeyId; - if (projectSecretManagerKmsId) { - const kmsDoc = await knex(TableName.KmsKey) - .leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`) - .where({ [`${TableName.KmsKey}.id` as "id"]: projectSecretManagerKmsId }) - .first(); - if (!kmsDoc) throw new Error("missing kms"); - secretManagerKmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY); - } else { - const [kmsDoc] = await knex(TableName.KmsKey) - .insert({ - name: slugify(alphaNumericNanoId(8).toLowerCase()), - orgId: project.orgId, - isReserved: false - }) - .returning("*"); - - secretManagerKmsKey = randomSecureBytes(32); - const encryptedKeyMaterial = cipher.encrypt(secretManagerKmsKey, ROOT_ENCRYPTION_KEY); - await knex(TableName.InternalKms).insert({ - version: 1, - encryptedKey: encryptedKeyMaterial, - encryptionAlgorithm: SymmetricEncryption.AES_GCM_256, - kmsKeyId: kmsDoc.id - }); - } - - const encryptedSecretManagerDataKey = project?.kmsSecretManagerEncryptedDataKey; - let dataKey: Buffer; - if (!encryptedSecretManagerDataKey) { - dataKey = randomSecureBytes(); - // the below versioning we do it automatically in kms service - const unversionedDataKey = cipher.encrypt(dataKey, secretManagerKmsKey); - const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 - await knex(TableName.Project) - .where({ id: projectId }) - .update({ - kmsSecretManagerEncryptedDataKey: Buffer.concat([unversionedDataKey, versionBlob]) - }); - } else { - const cipherTextBlob = encryptedSecretManagerDataKey.subarray(0, -KMS_VERSION_BLOB_LENGTH); - dataKey = cipher.decrypt(cipherTextBlob, secretManagerKmsKey); - } - - return { - encryptor: ({ plainText }: { plainText: Buffer }) => { - const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey); - - // Buffer#1 encrypted text + Buffer#2 version number - const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 - const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]); - return { cipherTextBlob }; - }, - decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: { cipherTextBlob: Buffer }) => { - const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH); - const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey); - return decryptedBlob; - } - }; -}; diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index 2214a3970..a5f3c24c0 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -4,7 +4,8 @@ import { Redlock, Settings } from "@app/lib/red-lock"; export enum PgSqlLock { BootUpMigration = 2023, - SuperAdminInit = 2024 + SuperAdminInit = 2024, + KmsRootKeyInit = 2025 } export type TKeyStoreFactory = ReturnType; diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 7f8c8cf2c..2f4a0b6cf 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -12,7 +12,7 @@ import { TExternalKmsProviderFns } from "@app/ee/services/external-kms/providers/model"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; -import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { TEnvConfig } from "@app/lib/config/env"; import { randomSecureBytes } from "@app/lib/crypto"; import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; @@ -44,7 +44,7 @@ type TKmsServiceFactoryDep = { kmsDAL: TKmsKeyDALFactory; projectDAL: Pick; orgDAL: Pick; - kmsRootConfigDAL: Pick; + kmsRootConfigDAL: Pick; keyStore: Pick; internalKmsDAL: Pick; hsmService: THsmServiceFactory; @@ -53,9 +53,6 @@ type TKmsServiceFactoryDep = { export type TKmsServiceFactory = ReturnType; -const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key"; -const KMS_ROOT_CREATION_WAIT_TIME = 10; - // akhilmhdh: Don't edit this value. This is measured for blob concatination in kms const KMS_VERSION = "v01"; const KMS_VERSION_BLOB_LENGTH = 3; @@ -874,54 +871,36 @@ export const kmsServiceFactory = ({ return { id, name, orgId, isExternal }; }; - // akhilmhdh: a copy of this is made in migrations/utils/kms const startService = async () => { - const lock = await keyStore.acquireLock([`KMS_ROOT_CFG_LOCK`], 3000, { retryCount: 3 }).catch(() => null); - if (!lock) { - await keyStore.waitTillReady({ - key: KMS_ROOT_CREATION_WAIT_KEY, - keyCheckCb: (val) => val === "true", - waitingCb: () => logger.info("KMS. Waiting for leader to finish creation of KMS Root Key") + const kmsRootConfig = await kmsRootConfigDAL.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.KmsRootKeyInit]); + // check if KMS root key was already generated and saved in DB + const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID, tx); + if (existingRootConfig) return existingRootConfig; + + logger.info("KMS: Generating new ROOT Key"); + const newRootKey = randomSecureBytes(32); + const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { + logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); + throw err; }); - } - // check if KMS root key was already generated and saved in DB - const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); - - // case 1: a root key already exists in the DB - if (kmsRootConfig) { - if (lock) await lock.release(); - logger.info(`KMS: Encrypted ROOT Key found from DB. Decrypting. [strategy=${kmsRootConfig.encryptionStrategy}]`); - - const decryptedRootKey = await $decryptRootKey(kmsRootConfig); - - // set the flag so that other instance nodes can start - await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); - logger.info("KMS: Loading ROOT Key into Memory."); - ROOT_ENCRYPTION_KEY = decryptedRootKey; - return; - } - - // case 2: no config is found, so we create a new root key with basic encryption - logger.info("KMS: Generating new ROOT Key"); - const newRootKey = randomSecureBytes(32); - const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { - logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); - throw err; + const newRootConfig = await kmsRootConfigDAL.create( + { + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: KMS_ROOT_CONFIG_UUID, + encryptedRootKey, + encryptionStrategy: RootKeyEncryptionStrategy.Software + }, + tx + ); + return newRootConfig; }); - await kmsRootConfigDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID, - encryptedRootKey, - encryptionStrategy: RootKeyEncryptionStrategy.Software - }); + const decryptedRootKey = await $decryptRootKey(kmsRootConfig); - // set the flag so that other instance nodes can start - await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); - logger.info("KMS: Saved and loaded ROOT Key into memory"); - if (lock) await lock.release(); - ROOT_ENCRYPTION_KEY = newRootKey; + logger.info("KMS: Loading ROOT Key into Memory."); + ROOT_ENCRYPTION_KEY = decryptedRootKey; }; const updateEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => { From ddc819dda136b3b66e7c929d00f0bf368e8ee69a Mon Sep 17 00:00:00 2001 From: = Date: Thu, 6 Feb 2025 16:31:13 +0530 Subject: [PATCH 19/41] feat: removed transaction from init --- .../src/services/kms/kms-root-config-dal.ts | 16 +++++++++++++++- backend/src/services/kms/kms-service.ts | 17 +++++++---------- .../super-admin/super-admin-service.ts | 19 ++++++++----------- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/backend/src/services/kms/kms-root-config-dal.ts b/backend/src/services/kms/kms-root-config-dal.ts index f448e2df8..8745d286e 100644 --- a/backend/src/services/kms/kms-root-config-dal.ts +++ b/backend/src/services/kms/kms-root-config-dal.ts @@ -1,10 +1,24 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; +import { Knex } from "knex"; export type TKmsRootConfigDALFactory = ReturnType; export const kmsRootConfigDALFactory = (db: TDbClient) => { const kmsOrm = ormify(db, TableName.KmsServerRootConfig); - return kmsOrm; + + const findById = async (id: string, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.KmsServerRootConfig) + .where({ id } as never) + .first("*"); + return result; + } catch (error) { + throw new DatabaseError({ error, name: "Find by id" }); + } + }; + + return { ...kmsOrm, findById }; }; diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index 2f4a0b6cf..f3f2ca5f0 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -875,7 +875,7 @@ export const kmsServiceFactory = ({ const kmsRootConfig = await kmsRootConfigDAL.transaction(async (tx) => { await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.KmsRootKeyInit]); // check if KMS root key was already generated and saved in DB - const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID, tx); + const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); if (existingRootConfig) return existingRootConfig; logger.info("KMS: Generating new ROOT Key"); @@ -885,15 +885,12 @@ export const kmsServiceFactory = ({ throw err; }); - const newRootConfig = await kmsRootConfigDAL.create( - { - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID, - encryptedRootKey, - encryptionStrategy: RootKeyEncryptionStrategy.Software - }, - tx - ); + const newRootConfig = await kmsRootConfigDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: KMS_ROOT_CONFIG_UUID, + encryptedRootKey, + encryptionStrategy: RootKeyEncryptionStrategy.Software + }); return newRootConfig; }); diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index a8f432d92..9a6075423 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -89,19 +89,16 @@ export const superAdminServiceFactory = ({ await keyStore.deleteItem(ADMIN_CONFIG_KEY); const serverCfg = await serverCfgDAL.transaction(async (tx) => { await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.SuperAdminInit]); - const serverCfgInDB = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID, tx); + const serverCfgInDB = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); if (serverCfgInDB) return serverCfgInDB; - const newCfg = await serverCfgDAL.create( - { - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: ADMIN_CONFIG_DB_UUID, - initialized: false, - allowSignUp: true, - defaultAuthOrgId: null - }, - tx - ); + const newCfg = await serverCfgDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: ADMIN_CONFIG_DB_UUID, + initialized: false, + allowSignUp: true, + defaultAuthOrgId: null + }); return newCfg; }); return serverCfg; From 2f0b353c4ebd0e65b9faf959d360dbcb76f939b7 Mon Sep 17 00:00:00 2001 From: = Date: Thu, 6 Feb 2025 20:00:40 +0530 Subject: [PATCH 20/41] feat: ensured env loading is only on migration fiels --- backend/src/db/knexfile.ts | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 3b5ef27e0..8af2b59ab 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -4,7 +4,6 @@ import "ts-node/register"; import dotenv from "dotenv"; import type { Knex } from "knex"; import path from "path"; -import { getMigrationEnvConfig } from "./migrations/utils/env-config"; // Update with your config settings. . dotenv.config({ @@ -14,22 +13,20 @@ dotenv.config({ path: path.join(__dirname, "../../../.env") }); -const envConfig = getMigrationEnvConfig(); - export default { development: { client: "postgres", connection: { - connectionString: envConfig.DB_CONNECTION_URI, - host: envConfig.DB_HOST, - port: envConfig.DB_PORT, - user: envConfig.DB_USER, - database: envConfig.DB_NAME, - password: envConfig.DB_PASSWORD, - ssl: envConfig.DB_ROOT_CERT + connectionString: process.env.DB_CONNECTION_URI, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + database: process.env.DB_NAME, + password: process.env.DB_PASSWORD, + ssl: process.env.DB_ROOT_CERT ? { rejectUnauthorized: true, - ca: Buffer.from(envConfig.DB_ROOT_CERT, "base64").toString("ascii") + ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii") } : false }, @@ -47,16 +44,16 @@ export default { production: { client: "postgres", connection: { - connectionString: envConfig.DB_CONNECTION_URI, - host: envConfig.DB_HOST, - port: envConfig.DB_PORT, - user: envConfig.DB_USER, - database: envConfig.DB_NAME, - password: envConfig.DB_PASSWORD, - ssl: envConfig.DB_ROOT_CERT + connectionString: process.env.DB_CONNECTION_URI, + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + database: process.env.DB_NAME, + password: process.env.DB_PASSWORD, + ssl: process.env.DB_ROOT_CERT ? { rejectUnauthorized: true, - ca: Buffer.from(envConfig.DB_ROOT_CERT, "base64").toString("ascii") + ca: Buffer.from(process.env.DB_ROOT_CERT, "base64").toString("ascii") } : false }, From 732484d3321a2207a127e7232371785dc030f646 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 10 Feb 2025 00:17:15 +0530 Subject: [PATCH 21/41] feat: updated migration to support .mjs instead of .ts --- backend/package.json | 26 +++++++++++++------------- backend/src/auto-start-migrations.ts | 28 +++++++++++++++++++++++++++- backend/src/db/knexfile.ts | 6 ++++-- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/backend/package.json b/backend/package.json index 43409e619..88c027d24 100644 --- a/backend/package.json +++ b/backend/package.json @@ -46,20 +46,20 @@ "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest", - "auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up", - "auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down", - "auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list", - "auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status", - "auditlog-migration:unlock": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:unlock", - "auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback", + "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:up", + "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:down", + "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:list", + "auditlog-migration:status": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:status", + "auditlog-migration:unlock": "knex --knexfile ./dist/db/auditlog-knexfile.ts migrate:unlock", + "auditlog-migration:rollback": "knex --knexfile ./dist/db/auditlog-knexfile.ts migrate:rollback", "migration:new": "tsx ./scripts/create-migration.ts", - "migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up", - "migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down", - "migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list", - "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest", - "migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status", - "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback", - "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./src/db/knexfile.ts migrate:unlock", + "migration:up": "npm run auditlog-migration:up && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:up", + "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:down", + "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:list", + "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", + "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:status", + "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.ts migrate:rollback", + "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.ts migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts index 5177eaf4a..81401855d 100644 --- a/backend/src/auto-start-migrations.ts +++ b/backend/src/auto-start-migrations.ts @@ -14,9 +14,10 @@ type TArgs = { logger: Logger; }; +const isProduction = process.env.NODE_ENV === "production"; const migrationConfig = { directory: path.join(__dirname, "./db/migrations"), - extension: "ts", + loadExtensions: [".mjs", ".ts"], tableName: "infisical_migrations" }; @@ -31,6 +32,31 @@ const migrationStatusCheckErrorHandler = (err: Error) => { export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { try { + // akhilmhdh(Feb 10 2025): 6 months from now remove this + if (isProduction) { + const migrationTable = migrationConfig.tableName; + const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); + if (hasMigrationTable) { + const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await applicationDb(migrationTable).update({ + name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + if (auditLogDb) { + const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); + if (hasMigrationTableInAuditLog) { + const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await auditLogDb(migrationTable).update({ + name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + } + } + const shouldRunMigration = Boolean( await applicationDb.migrate.status(migrationConfig).catch(migrationStatusCheckErrorHandler) ); // db.length - code.length diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 8af2b59ab..8cf80b744 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -38,7 +38,8 @@ export default { directory: "./seeds" }, migrations: { - tableName: "infisical_migrations" + tableName: "infisical_migrations", + loadExtensions: [".mjs"] } }, production: { @@ -62,7 +63,8 @@ export default { max: 10 }, migrations: { - tableName: "infisical_migrations" + tableName: "infisical_migrations", + loadExtensions: [".mjs"] } } } as Knex.Config; From 3ec14ca33aae574a52275cf5c58dcee44a55d499 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 10 Feb 2025 15:46:24 +0530 Subject: [PATCH 22/41] feat: updated migration command to pick mjs file --- backend/package.json | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/package.json b/backend/package.json index 88c027d24..ecf2905c9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,21 +45,21 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest", - "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:up", - "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:down", - "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:list", - "auditlog-migration:status": "knex --knexfile ./dist/db/auditlog-knexfile.ts --client pg migrate:status", - "auditlog-migration:unlock": "knex --knexfile ./dist/db/auditlog-knexfile.ts migrate:unlock", - "auditlog-migration:rollback": "knex --knexfile ./dist/db/auditlog-knexfile.ts migrate:rollback", + "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", + "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", + "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", + "auditlog-migration:status": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:status", + "auditlog-migration:unlock": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:unlock", + "auditlog-migration:rollback": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:rollback", "migration:new": "tsx ./scripts/create-migration.ts", "migration:up": "npm run auditlog-migration:up && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:up", - "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:down", - "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:list", + "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:down", + "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:list", "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", - "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.ts --client pg migrate:status", - "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.ts migrate:rollback", - "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.ts migrate:unlock", + "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:status", + "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.mjs migrate:rollback", + "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", From 6b3b13e40adbb12dd7190c1765057b38e1881da0 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 10 Feb 2025 15:51:41 +0530 Subject: [PATCH 23/41] feat: reorder to-kms migration to make it last --- ...9104500_webhook-to-kms.ts => 20250210101840_webhook-to-kms.ts} | 0 ...oot-to-kms.ts => 20250210101841_dynamic-secret-root-to-kms.ts} | 0 ...otation-to-kms.ts => 20250210101841_secret-rotation-to-kms.ts} | 0 ...8-auth-to-kms.ts => 20250210101842_identity-k8-auth-to-kms.ts} | 0 ...auth-to-kms.ts => 20250210101842_identity-oidc-auth-to-kms.ts} | 0 ...config-to-kms.ts => 20250210101845_directory-config-to-kms.ts} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename backend/src/db/migrations/{20250109104500_webhook-to-kms.ts => 20250210101840_webhook-to-kms.ts} (100%) rename backend/src/db/migrations/{20250109104503_dynamic-secret-root-to-kms.ts => 20250210101841_dynamic-secret-root-to-kms.ts} (100%) rename backend/src/db/migrations/{20250109104501_secret-rotation-to-kms.ts => 20250210101841_secret-rotation-to-kms.ts} (100%) rename backend/src/db/migrations/{20250109104502_identity-k8-auth-to-kms.ts => 20250210101842_identity-k8-auth-to-kms.ts} (100%) rename backend/src/db/migrations/{20250109104502_identity-oidc-auth-to-kms.ts => 20250210101842_identity-oidc-auth-to-kms.ts} (100%) rename backend/src/db/migrations/{20250109104508_directory-config-to-kms.ts => 20250210101845_directory-config-to-kms.ts} (100%) diff --git a/backend/src/db/migrations/20250109104500_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104500_webhook-to-kms.ts rename to backend/src/db/migrations/20250210101840_webhook-to-kms.ts diff --git a/backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104503_dynamic-secret-root-to-kms.ts rename to backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts diff --git a/backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104501_secret-rotation-to-kms.ts rename to backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts diff --git a/backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104502_identity-k8-auth-to-kms.ts rename to backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts diff --git a/backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104502_identity-oidc-auth-to-kms.ts rename to backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts diff --git a/backend/src/db/migrations/20250109104508_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts similarity index 100% rename from backend/src/db/migrations/20250109104508_directory-config-to-kms.ts rename to backend/src/db/migrations/20250210101845_directory-config-to-kms.ts From 6845ac0f5e55e082c55d215ffa163fc2249f480d Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Feb 2025 00:18:14 +0530 Subject: [PATCH 24/41] feat: added script to rename things in migration --- backend/package.json | 2 +- backend/src/auto-start-migrations.ts | 2 +- backend/src/db/rename-migrations-to-mjs.ts | 56 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 backend/src/db/rename-migrations-to-mjs.ts diff --git a/backend/package.json b/backend/package.json index ecf2905c9..4f8647eab 100644 --- a/backend/package.json +++ b/backend/package.json @@ -56,7 +56,7 @@ "migration:up": "npm run auditlog-migration:up && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:up", "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:down", "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:list", - "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", + "migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:status", "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.mjs migrate:rollback", "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts index 81401855d..88f6dea69 100644 --- a/backend/src/auto-start-migrations.ts +++ b/backend/src/auto-start-migrations.ts @@ -32,7 +32,7 @@ const migrationStatusCheckErrorHandler = (err: Error) => { export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { try { - // akhilmhdh(Feb 10 2025): 6 months from now remove this + // akhilmhdh(Feb 10 2025): 2 years from now remove this if (isProduction) { const migrationTable = migrationConfig.tableName; const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); diff --git a/backend/src/db/rename-migrations-to-mjs.ts b/backend/src/db/rename-migrations-to-mjs.ts new file mode 100644 index 000000000..d09b5097d --- /dev/null +++ b/backend/src/db/rename-migrations-to-mjs.ts @@ -0,0 +1,56 @@ +import path from "node:path"; + +import dotenv from "dotenv"; + +import { initAuditLogDbConnection, initDbConnection } from "./instance"; + +const isProduction = process.env.NODE_ENV === "production"; + +// Update with your config settings. . +dotenv.config({ + path: path.join(__dirname, "../../../.env.migration") +}); +dotenv.config({ + path: path.join(__dirname, "../../../.env") +}); + +const runRename = async () => { + if (!isProduction) return; + const migrationTable = "infisical_migrations"; + const applicationDb = initDbConnection({ + dbConnectionUri: process.env.DB_CONNECTION_URI as string, + dbRootCert: process.env.DB_ROOT_CERT + }); + + const auditLogDb = process.env.AUDIT_LOGS_DB_CONNECTION_URI + ? initAuditLogDbConnection({ + dbConnectionUri: process.env.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: process.env.AUDIT_LOGS_DB_ROOT_CERT + }) + : undefined; + + const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); + if (hasMigrationTable) { + const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await applicationDb(migrationTable).update({ + name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + if (auditLogDb) { + const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); + if (hasMigrationTableInAuditLog) { + const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await auditLogDb(migrationTable).update({ + name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + } + await applicationDb.destroy(); + await auditLogDb?.destroy(); +}; + +void runRename(); From b644829bb97369ce44dfbb1eb4d6629b416a7efc Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Mon, 10 Feb 2025 21:07:41 -0500 Subject: [PATCH 25/41] add missing transactions --- .../20250210101840_webhook-to-kms.ts | 5 +- ...250210101841_dynamic-secret-root-to-kms.ts | 2 +- .../20250210101841_secret-rotation-to-kms.ts | 2 +- .../20250210101842_identity-k8-auth-to-kms.ts | 137 +++++++++--------- ...0250210101842_identity-oidc-auth-to-kms.ts | 2 +- .../20250210101845_directory-config-to-kms.ts | 6 +- backend/src/services/kms/kms-service.ts | 3 +- 7 files changed, 78 insertions(+), 79 deletions(-) diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts index 0836e1b48..c9b8e7fec 100644 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -31,8 +31,7 @@ export async function up(knex: Knex): Promise { const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = createCircularCache>>(25); - - const webhooks = await knex(TableName.Webhook) + const webhooks = await knex(TableName.Webhook) .where({}) .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) .select( @@ -57,7 +56,7 @@ export async function up(knex: Knex): Promise { projectKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId: el.projectId - }); + }, knex); projectEncryptionRingBuffer.push(el.projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts index 1abf132d3..41dc6ba9f 100644 --- a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts @@ -49,7 +49,7 @@ export async function up(knex: Knex): Promise { projectKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId - }); + }, knex); projectEncryptionRingBuffer.push(projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts index 9d10471ad..567cace99 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -42,7 +42,7 @@ export async function up(knex: Knex): Promise { projectKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.SecretManager, projectId - }); + }, knex); projectEncryptionRingBuffer.push(projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts index 2aa1b61fd..3d62ab04f 100644 --- a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts @@ -59,7 +59,6 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const orgEncryptionRingBuffer = createCircularCache>>(25); - const identityKubernetesConfigs = await knex(TableName.IdentityKubernetesAuth) .join( TableName.IdentityOrgMembership, @@ -77,76 +76,76 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { ) .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - const updatedIdentityKubernetesConfigs = await Promise.all( - identityKubernetesConfigs.map( - async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { - let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }); - orgEncryptionRingBuffer.push(orgId, orgKmsService); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); + const updatedIdentityKubernetesConfigs = []; - const decryptedTokenReviewerJwt = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.tokenReviewerJwtIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.tokenReviewerJwtTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedTokenReviewerJwt - }) - : ""; - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.caCertIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.caCertTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCaCert - }) - : ""; - - const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedTokenReviewerJwt) - }).cipherTextBlob; - const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - - return { - ...el, - accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), - encryptedKubernetesCaCertificate, - encryptedKubernetesTokenReviewerJwt - }; + for (const { encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el } of identityKubernetesConfigs) { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId + }, knex); + orgEncryptionRingBuffer.push(orgId, orgKmsService); } - ) - ); + + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedTokenReviewerJwt = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.tokenReviewerJwtIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.tokenReviewerJwtTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedTokenReviewerJwt + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedTokenReviewerJwt) + }).cipherTextBlob; + const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + updatedIdentityKubernetesConfigs.push({ + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedKubernetesCaCertificate, + encryptedKubernetesTokenReviewerJwt + }); + } for (let i = 0; i < updatedIdentityKubernetesConfigs.length; i += BATCH_SIZE) { // eslint-disable-next-line no-await-in-loop diff --git a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts index 1b3a4d50c..dc87726a4 100644 --- a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts @@ -65,7 +65,7 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { orgKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId - }); + }, knex); orgEncryptionRingBuffer.push(orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ diff --git a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts index 2497bba05..05db40958 100644 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -52,7 +52,7 @@ const reencryptSamlConfig = async (knex: Knex) => { orgKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId: el.orgId - }); + }, knex); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ @@ -207,7 +207,7 @@ const reencryptLdapConfig = async (knex: Knex) => { orgKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId: el.orgId - }); + }, knex); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ @@ -356,7 +356,7 @@ const reencryptOidcConfig = async (knex: Knex) => { orgKmsService = await kmsService.createCipherPairWithDataKey({ type: KmsDataKey.Organization, orgId: el.orgId - }); + }, knex); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index f3f2ca5f0..babba4cb2 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -472,7 +472,8 @@ export const kmsServiceFactory = ({ } const kmsDecryptor = await decryptWithKmsKey({ - kmsId: kmsKeyId + kmsId: kmsKeyId, + tx: trx }); return kmsDecryptor({ From 74d01c37de6e5390a471e3b77f96963f5ff59e0b Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 10 Feb 2025 19:16:55 -0800 Subject: [PATCH 26/41] fix: remove capitalize from breadcrumbs container --- frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx index d7b78c67a..0afd1bb1f 100644 --- a/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx +++ b/frontend/src/components/v2/Breadcrumb/Breadcrumb.tsx @@ -126,7 +126,7 @@ export type TBreadcrumbFormat = }; const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] }) => ( -
+
{(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => { From 41c526371d0477e8551c719633de4703661f860d Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Feb 2025 20:28:58 +0530 Subject: [PATCH 27/41] feat: fixed wrong directory for audit log --- backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index 4f8647eab..71ba8a9dd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:latest": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", @@ -62,7 +62,7 @@ "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", - "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", + "seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run", "db:reset": "npm run migration:rollback -- --all && npm run migration:latest" }, "keywords": [], From ccf19fbcd42feaf2ea5e203c63b8142814a94874 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Feb 2025 20:30:11 +0530 Subject: [PATCH 28/41] fix: added conversion for audit log migration --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 71ba8a9dd..305e61a2d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", From 78f9ae7fabcf311cdd192773330ec075357abe29 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 11 Feb 2025 11:15:41 -0500 Subject: [PATCH 29/41] Revert "Feat/enc migration" --- backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index 305e61a2d..4f8647eab 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", @@ -62,7 +62,7 @@ "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", - "seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run", + "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", "db:reset": "npm run migration:rollback -- --all && npm run migration:latest" }, "keywords": [], From ff8f1d3bfbfd9b04f5e8ad28707d76046fff3d29 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 11 Feb 2025 11:16:09 -0500 Subject: [PATCH 30/41] Revert "Revert "Feat/enc migration"" --- backend/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/package.json b/backend/package.json index 4f8647eab..305e61a2d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:latest", "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", @@ -62,7 +62,7 @@ "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", - "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", + "seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run", "db:reset": "npm run migration:rollback -- --all && npm run migration:latest" }, "keywords": [], From 25f6947de57fe350e222d6f8c5010aa305201301 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 11 Feb 2025 11:16:30 -0500 Subject: [PATCH 31/41] Revert "Root encrypted data to kms encryption" --- backend/e2e-test/vitest-environment-knex.ts | 34 +- backend/package.json | 28 +- backend/src/@types/fastify.d.ts | 6 - backend/src/auto-start-migrations.ts | 105 ---- backend/src/db/instance.ts | 9 - backend/src/db/knexfile.ts | 6 +- .../20250210101840_webhook-to-kms.ts | 127 ----- ...250210101841_dynamic-secret-root-to-kms.ts | 108 ---- .../20250210101841_secret-rotation-to-kms.ts | 100 ---- .../20250210101842_identity-k8-auth-to-kms.ts | 190 ------- ...0250210101842_identity-oidc-auth-to-kms.ts | 138 ----- .../20250210101845_directory-config-to-kms.ts | 484 ------------------ backend/src/db/migrations/utils/env-config.ts | 53 -- backend/src/db/migrations/utils/kms.ts | 105 ++++ .../src/db/migrations/utils/ring-buffer.ts | 19 - backend/src/db/migrations/utils/services.ts | 52 -- backend/src/db/rename-migrations-to-mjs.ts | 56 -- backend/src/db/schemas/dynamic-secrets.ts | 11 +- .../db/schemas/identity-kubernetes-auths.ts | 18 +- backend/src/db/schemas/identity-oidc-auths.ts | 11 +- backend/src/db/schemas/ldap-configs.ts | 25 +- backend/src/db/schemas/oidc-configs.ts | 18 +- backend/src/db/schemas/saml-configs.ts | 7 +- backend/src/db/schemas/secret-rotations.ts | 5 +- backend/src/db/schemas/webhooks.ts | 8 +- backend/src/ee/routes/v1/ldap-router.ts | 7 +- backend/src/ee/routes/v1/oidc-router.ts | 36 +- .../ee/routes/v1/project-template-router.ts | 2 +- backend/src/ee/routes/v1/saml-router.ts | 6 +- .../v1/user-additional-privilege-router.ts | 2 +- ...ity-project-additional-privilege-router.ts | 2 +- .../dynamic-secret-lease-dal.ts | 12 +- .../dynamic-secret-lease-queue.ts | 48 +- .../dynamic-secret-lease-service.ts | 46 +- .../dynamic-secret/dynamic-secret-service.ts | 51 +- backend/src/ee/services/hsm/hsm-fns.ts | 10 +- backend/src/ee/services/hsm/hsm-service.ts | 33 +- ...project-additional-privilege-v2-service.ts | 2 +- ...ty-project-additional-privilege-service.ts | 2 +- .../ldap-config/ldap-config-service.ts | 169 +++++- .../ee/services/oidc/oidc-config-service.ts | 155 +++++- .../services/permission/project-permission.ts | 2 +- .../project-template-service.ts | 2 +- .../project-template-types.ts | 2 +- ...oject-user-additional-privilege-service.ts | 2 +- .../saml-config/saml-config-service.ts | 200 ++++++-- .../secret-rotation-queue.ts | 49 +- .../secret-rotation-service.ts | 21 +- .../secret-snapshot-service.ts | 2 - .../services/secret-snapshot/snapshot-dal.ts | 2 +- backend/src/keystore/keystore.ts | 6 - backend/src/keystore/memory.ts | 38 -- backend/src/lib/config/env.ts | 3 +- backend/src/lib/logger/logger.ts | 2 +- backend/src/main.ts | 66 +-- backend/src/server/app.ts | 7 +- backend/src/server/routes/index.ts | 44 +- .../sanitizedSchema/directory-config.ts | 42 -- backend/src/server/routes/sanitizedSchemas.ts | 7 +- .../identitiy-additional-privilege.ts | 0 .../permission.ts | 0 .../user-additional-privilege.ts | 0 .../v1/identity-kubernetes-auth-router.ts | 20 +- .../routes/v1/identity-oidc-auth-router.ts | 18 +- .../identity-kubernetes-auth-service.ts | 223 ++++++-- .../identity-oidc-auth-service.ts | 157 +++++- .../src/services/kms/kms-root-config-dal.ts | 16 +- backend/src/services/kms/kms-service.ts | 83 +-- .../project-role/project-role-service.ts | 2 +- backend/src/services/project/project-types.ts | 13 +- backend/src/services/secret/secret-queue.ts | 13 +- .../super-admin/super-admin-service.ts | 24 +- backend/src/services/webhook/webhook-fns.ts | 42 +- .../src/services/webhook/webhook-service.ts | 43 +- backend/tsconfig.json | 4 +- docker-compose.dev.yml | 20 +- docker-compose.prod.yml | 17 +- 77 files changed, 1294 insertions(+), 2204 deletions(-) delete mode 100644 backend/src/auto-start-migrations.ts delete mode 100644 backend/src/db/migrations/20250210101840_webhook-to-kms.ts delete mode 100644 backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts delete mode 100644 backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts delete mode 100644 backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts delete mode 100644 backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts delete mode 100644 backend/src/db/migrations/20250210101845_directory-config-to-kms.ts delete mode 100644 backend/src/db/migrations/utils/env-config.ts create mode 100644 backend/src/db/migrations/utils/kms.ts delete mode 100644 backend/src/db/migrations/utils/ring-buffer.ts delete mode 100644 backend/src/db/migrations/utils/services.ts delete mode 100644 backend/src/db/rename-migrations-to-mjs.ts delete mode 100644 backend/src/keystore/memory.ts delete mode 100644 backend/src/server/routes/sanitizedSchema/directory-config.ts rename backend/src/server/routes/{sanitizedSchema => santizedSchemas}/identitiy-additional-privilege.ts (100%) rename backend/src/server/routes/{sanitizedSchema => santizedSchemas}/permission.ts (100%) rename backend/src/server/routes/{sanitizedSchema => santizedSchemas}/user-additional-privilege.ts (100%) diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 9dfb38aeb..58f2bffeb 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -23,14 +23,14 @@ export default { name: "knex-env", transformMode: "ssr", async setup() { - const logger = initLogger(); - const envConfig = initEnvConfig(logger); + const logger = await initLogger(); + const cfg = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT + dbConnectionUri: cfg.DB_CONNECTION_URI, + dbRootCert: cfg.DB_ROOT_CERT }); - const redis = new Redis(envConfig.REDIS_URL); + const redis = new Redis(cfg.REDIS_URL); await redis.flushdb("SYNC"); try { @@ -42,7 +42,6 @@ export default { }, true ); - await db.migrate.latest({ directory: path.join(__dirname, "../src/db/migrations"), extension: "ts", @@ -53,24 +52,14 @@ export default { directory: path.join(__dirname, "../src/db/seeds"), extension: "ts" }); - const smtp = mockSmtpServer(); - const queue = queueServiceFactory(envConfig.REDIS_URL, { dbConnectionUrl: envConfig.DB_CONNECTION_URI }); - const keyStore = keyStoreFactory(envConfig.REDIS_URL); + const queue = queueServiceFactory(cfg.REDIS_URL, { dbConnectionUrl: cfg.DB_CONNECTION_URI }); + const keyStore = keyStoreFactory(cfg.REDIS_URL); - const hsmModule = initializeHsmModule(envConfig); + const hsmModule = initializeHsmModule(); hsmModule.initialize(); - const server = await main({ - db, - smtp, - logger, - queue, - keyStore, - hsmModule: hsmModule.getModule(), - redis, - envConfig - }); + const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule(), redis }); // @ts-expect-error type globalThis.testServer = server; @@ -84,8 +73,8 @@ export default { organizationId: seedData1.organization.id, accessVersion: 1 }, - envConfig.AUTH_SECRET, - { expiresIn: envConfig.JWT_AUTH_LIFETIME } + cfg.AUTH_SECRET, + { expiresIn: cfg.JWT_AUTH_LIFETIME } ); } catch (error) { // eslint-disable-next-line @@ -120,4 +109,3 @@ export default { }; } }; - diff --git a/backend/package.json b/backend/package.json index 4f8647eab..43409e619 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,21 +45,21 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", - "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", - "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", - "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", - "auditlog-migration:status": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:status", - "auditlog-migration:unlock": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:unlock", - "auditlog-migration:rollback": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:rollback", + "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest", + "auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up", + "auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down", + "auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list", + "auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status", + "auditlog-migration:unlock": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:unlock", + "auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback", "migration:new": "tsx ./scripts/create-migration.ts", - "migration:up": "npm run auditlog-migration:up && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:up", - "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:down", - "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:list", - "migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", - "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:status", - "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.mjs migrate:rollback", - "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", + "migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up", + "migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down", + "migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list", + "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest", + "migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status", + "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback", + "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./src/db/knexfile.ts migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index c02347038..f3298625e 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -93,12 +93,6 @@ import { TUserEngagementServiceFactory } from "@app/services/user-engagement/use import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; -declare module "@fastify/request-context" { - interface RequestContextData { - reqId: string; - } -} - declare module "fastify" { interface Session { callbackPort: string; diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts deleted file mode 100644 index 88f6dea69..000000000 --- a/backend/src/auto-start-migrations.ts +++ /dev/null @@ -1,105 +0,0 @@ -import path from "node:path"; - -import dotenv from "dotenv"; -import { Knex } from "knex"; -import { Logger } from "pino"; - -import { PgSqlLock } from "./keystore/keystore"; - -dotenv.config(); - -type TArgs = { - auditLogDb?: Knex; - applicationDb: Knex; - logger: Logger; -}; - -const isProduction = process.env.NODE_ENV === "production"; -const migrationConfig = { - directory: path.join(__dirname, "./db/migrations"), - loadExtensions: [".mjs", ".ts"], - tableName: "infisical_migrations" -}; - -const migrationStatusCheckErrorHandler = (err: Error) => { - // happens for first time in which the migration table itself is not created yet - // error: select * from "infisical_migrations" - relation "infisical_migrations" does not exist - if (err?.message?.includes("does not exist")) { - return true; - } - throw err; -}; - -export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { - try { - // akhilmhdh(Feb 10 2025): 2 years from now remove this - if (isProduction) { - const migrationTable = migrationConfig.tableName; - const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); - if (hasMigrationTable) { - const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; - if (firstFile?.name?.includes(".ts")) { - await applicationDb(migrationTable).update({ - name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") - }); - } - } - if (auditLogDb) { - const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); - if (hasMigrationTableInAuditLog) { - const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; - if (firstFile?.name?.includes(".ts")) { - await auditLogDb(migrationTable).update({ - name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") - }); - } - } - } - } - - const shouldRunMigration = Boolean( - await applicationDb.migrate.status(migrationConfig).catch(migrationStatusCheckErrorHandler) - ); // db.length - code.length - if (!shouldRunMigration) { - logger.info("No migrations pending: Skipping migration process."); - return; - } - - if (auditLogDb) { - await auditLogDb.transaction(async (tx) => { - await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); - logger.info("Running audit log migrations."); - - const didPreviousInstanceRunMigration = !(await auditLogDb.migrate - .status(migrationConfig) - .catch(migrationStatusCheckErrorHandler)); - if (didPreviousInstanceRunMigration) { - logger.info("No audit log migrations pending: Applied by previous instance. Skipping migration process."); - return; - } - - await auditLogDb.migrate.latest(migrationConfig); - logger.info("Finished audit log migrations."); - }); - } - - await applicationDb.transaction(async (tx) => { - await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); - logger.info("Running application migrations."); - - const didPreviousInstanceRunMigration = !(await applicationDb.migrate - .status(migrationConfig) - .catch(migrationStatusCheckErrorHandler)); - if (didPreviousInstanceRunMigration) { - logger.info("No application migrations pending: Applied by previous instance. Skipping migration process."); - return; - } - - await applicationDb.migrate.latest(migrationConfig); - logger.info("Finished application migrations."); - }); - } catch (err) { - logger.error(err, "Boot up migration failed"); - process.exit(1); - } -}; diff --git a/backend/src/db/instance.ts b/backend/src/db/instance.ts index 5a8dd3d05..d4a2a5b2c 100644 --- a/backend/src/db/instance.ts +++ b/backend/src/db/instance.ts @@ -49,9 +49,6 @@ export const initDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false - }, - migrations: { - tableName: "infisical_migrations" } }); @@ -67,9 +64,6 @@ export const initDbConnection = ({ ca: Buffer.from(replicaDbCertificate, "base64").toString("ascii") } : false - }, - migrations: { - tableName: "infisical_migrations" } }); }); @@ -104,9 +98,6 @@ export const initAuditLogDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false - }, - migrations: { - tableName: "infisical_migrations" } }); diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 8cf80b744..8af2b59ab 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -38,8 +38,7 @@ export default { directory: "./seeds" }, migrations: { - tableName: "infisical_migrations", - loadExtensions: [".mjs"] + tableName: "infisical_migrations" } }, production: { @@ -63,8 +62,7 @@ export default { max: 10 }, migrations: { - tableName: "infisical_migrations", - loadExtensions: [".mjs"] + tableName: "infisical_migrations" } } } as Knex.Config; diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts deleted file mode 100644 index c9b8e7fec..000000000 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -export async function up(knex: Knex): Promise { - const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); - const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); - const hasUrl = await knex.schema.hasColumn(TableName.Webhook, "url"); - - const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); - if (hasWebhookTable) { - await knex.schema.alterTable(TableName.Webhook, (t) => { - if (!hasEncryptedKey) t.binary("encryptedPassKey"); - if (!hasEncryptedUrl) t.binary("encryptedUrl"); - if (hasUrl) t.string("url").nullable().alter(); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const projectEncryptionRingBuffer = - createCircularCache>>(25); - const webhooks = await knex(TableName.Webhook) - .where({}) - .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) - .select( - "url", - "encryptedSecretKey", - "iv", - "tag", - "keyEncoding", - "urlCipherText", - "urlIV", - "urlTag", - knex.ref("id").withSchema(TableName.Webhook), - "envId" - ) - .select(knex.ref("projectId").withSchema(TableName.Environment)) - .orderBy(`${TableName.Environment}.projectId` as "projectId"); - - const updatedWebhooks = await Promise.all( - webhooks.map(async (el) => { - let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); - if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: el.projectId - }, knex); - projectEncryptionRingBuffer.push(el.projectId, projectKmsService); - } - - let encryptedSecretKey = null; - if (el.encryptedSecretKey && el.iv && el.tag && el.keyEncoding) { - const decyptedSecretKey = infisicalSymmetricDecrypt({ - keyEncoding: el.keyEncoding as SecretKeyEncoding, - iv: el.iv, - tag: el.tag, - ciphertext: el.encryptedSecretKey - }); - encryptedSecretKey = projectKmsService.encryptor({ - plainText: Buffer.from(decyptedSecretKey, "utf8") - }).cipherTextBlob; - } - - const decryptedUrl = - el.urlIV && el.urlTag && el.urlCipherText && el.keyEncoding - ? infisicalSymmetricDecrypt({ - keyEncoding: el.keyEncoding as SecretKeyEncoding, - iv: el.urlIV, - tag: el.urlTag, - ciphertext: el.urlCipherText - }) - : null; - - const encryptedUrl = projectKmsService.encryptor({ - plainText: Buffer.from(decryptedUrl || el.url || "") - }).cipherTextBlob; - return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId }; - }) - ); - - for (let i = 0; i < updatedWebhooks.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.Webhook) - .insert( - updatedWebhooks.slice(i, i + BATCH_SIZE).map((el) => ({ - id: el.id, - envId: el.envId, - url: "", - encryptedUrl: el.encryptedUrl, - encryptedPassKey: el.encryptedSecretKey - })) - ) - .onConflict("id") - .merge(); - } - - if (hasWebhookTable) { - await knex.schema.alterTable(TableName.Webhook, (t) => { - if (!hasEncryptedUrl) t.binary("encryptedUrl").notNullable().alter(); - }); - } -} - -export async function down(knex: Knex): Promise { - const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); - const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); - - const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); - if (hasWebhookTable) { - await knex.schema.alterTable(TableName.Webhook, (t) => { - if (hasEncryptedKey) t.dropColumn("encryptedPassKey"); - if (hasEncryptedUrl) t.dropColumn("encryptedUrl"); - }); - } -} diff --git a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts deleted file mode 100644 index 41dc6ba9f..000000000 --- a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -export async function up(knex: Knex): Promise { - const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); - const hasInputCiphertextColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext"); - const hasInputIVColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV"); - const hasInputTagColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag"); - - const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); - if (hasDynamicSecretTable) { - await knex.schema.alterTable(TableName.DynamicSecret, (t) => { - if (!hasEncryptedInputColumn) t.binary("encryptedInput"); - if (hasInputCiphertextColumn) t.text("inputCiphertext").nullable().alter(); - if (hasInputIVColumn) t.string("inputIV").nullable().alter(); - if (hasInputTagColumn) t.string("inputTag").nullable().alter(); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const projectEncryptionRingBuffer = - createCircularCache>>(25); - - const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret) - .join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) - .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) - .select(selectAllTableCols(TableName.DynamicSecret)) - .select(knex.ref("projectId").withSchema(TableName.Environment)) - .orderBy(`${TableName.Environment}.projectId` as "projectId"); - - const updatedDynamicSecrets = await Promise.all( - dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => { - let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); - if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }, knex); - projectEncryptionRingBuffer.push(projectId, projectKmsService); - } - - const decryptedInputData = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding - ? infisicalSymmetricDecrypt({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - keyEncoding: el.keyEncoding as SecretKeyEncoding, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.inputIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.inputTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.inputCiphertext - }) - : ""; - - const encryptedInput = projectKmsService.encryptor({ - plainText: Buffer.from(decryptedInputData) - }).cipherTextBlob; - - return { ...el, encryptedInput }; - }) - ); - - for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.DynamicSecret) - .insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - - if (hasDynamicSecretTable) { - await knex.schema.alterTable(TableName.DynamicSecret, (t) => { - if (!hasEncryptedInputColumn) t.binary("encryptedInput").notNullable().alter(); - }); - } -} - -export async function down(knex: Knex): Promise { - const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); - - const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); - if (hasDynamicSecretTable) { - await knex.schema.alterTable(TableName.DynamicSecret, (t) => { - if (hasEncryptedInputColumn) t.dropColumn("encryptedInput"); - }); - } -} diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts deleted file mode 100644 index 567cace99..000000000 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -export async function up(knex: Knex): Promise { - const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); - - const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); - if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { - if (!hasEncryptedRotationData) t.binary("encryptedRotationData"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const projectEncryptionRingBuffer = - createCircularCache>>(25); - - const secretRotations = await knex(TableName.SecretRotation) - .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) - .select(selectAllTableCols(TableName.SecretRotation)) - .select(knex.ref("projectId").withSchema(TableName.Environment)) - .orderBy(`${TableName.Environment}.projectId` as "projectId"); - - const updatedRotationData = await Promise.all( - secretRotations.map(async ({ projectId, ...el }) => { - let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); - if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }, knex); - projectEncryptionRingBuffer.push(projectId, projectKmsService); - } - - const decryptedRotationData = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding - ? infisicalSymmetricDecrypt({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - keyEncoding: el.keyEncoding as SecretKeyEncoding, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.encryptedDataIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.encryptedDataTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedData - }) - : ""; - - const encryptedRotationData = projectKmsService.encryptor({ - plainText: Buffer.from(decryptedRotationData) - }).cipherTextBlob; - return { ...el, encryptedRotationData }; - }) - ); - - for (let i = 0; i < updatedRotationData.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.SecretRotation) - .insert(updatedRotationData.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - - if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { - if (!hasEncryptedRotationData) t.binary("encryptedRotationData").notNullable().alter(); - }); - } -} - -export async function down(knex: Knex): Promise { - const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); - - const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); - if (hasRotationTable) { - await knex.schema.alterTable(TableName.SecretRotation, (t) => { - if (hasEncryptedRotationData) t.dropColumn("encryptedRotationData"); - }); - } -} diff --git a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts deleted file mode 100644 index 3d62ab04f..000000000 --- a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -const reencryptIdentityK8sAuth = async (knex: Knex) => { - const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "encryptedKubernetesTokenReviewerJwt" - ); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "encryptedKubernetesCaCertificate" - ); - const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); - - const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "encryptedCaCert"); - const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertIV"); - const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertTag"); - const hasEncryptedTokenReviewerJwtColumn = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "encryptedTokenReviewerJwt" - ); - const hasTokenReviewerJwtIVColumn = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "tokenReviewerJwtIV" - ); - const hasTokenReviewerJwtTagColumn = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "tokenReviewerJwtTag" - ); - - if (hasidentityKubernetesAuthTable) { - await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { - if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); - if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); - if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); - if (hasEncryptedTokenReviewerJwtColumn) t.text("encryptedTokenReviewerJwt").nullable().alter(); - if (hasTokenReviewerJwtIVColumn) t.string("tokenReviewerJwtIV").nullable().alter(); - if (hasTokenReviewerJwtTagColumn) t.string("tokenReviewerJwtTag").nullable().alter(); - - if (!hasEncryptedKubernetesTokenReviewerJwt) t.binary("encryptedKubernetesTokenReviewerJwt"); - if (!hasEncryptedCertificateColumn) t.binary("encryptedKubernetesCaCertificate"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const orgEncryptionRingBuffer = - createCircularCache>>(25); - const identityKubernetesConfigs = await knex(TableName.IdentityKubernetesAuth) - .join( - TableName.IdentityOrgMembership, - `${TableName.IdentityOrgMembership}.identityId`, - `${TableName.IdentityKubernetesAuth}.identityId` - ) - .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) - .select(selectAllTableCols(TableName.IdentityKubernetesAuth)) - .select( - knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), - knex.ref("orgId").withSchema(TableName.OrgBot) - ) - .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - - const updatedIdentityKubernetesConfigs = []; - - for (const { encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el } of identityKubernetesConfigs) { - let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); - - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }, knex); - orgEncryptionRingBuffer.push(orgId, orgKmsService); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedTokenReviewerJwt = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.tokenReviewerJwtIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.tokenReviewerJwtTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedTokenReviewerJwt - }) - : ""; - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.caCertIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.caCertTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCaCert - }) - : ""; - - const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedTokenReviewerJwt) - }).cipherTextBlob; - const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - - updatedIdentityKubernetesConfigs.push({ - ...el, - accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), - encryptedKubernetesCaCertificate, - encryptedKubernetesTokenReviewerJwt - }); - } - - for (let i = 0; i < updatedIdentityKubernetesConfigs.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.IdentityKubernetesAuth) - .insert(updatedIdentityKubernetesConfigs.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - if (hasidentityKubernetesAuthTable) { - await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { - if (!hasEncryptedKubernetesTokenReviewerJwt) - t.binary("encryptedKubernetesTokenReviewerJwt").notNullable().alter(); - }); - } -}; - -export async function up(knex: Knex): Promise { - await reencryptIdentityK8sAuth(knex); -} - -const dropIdentityK8sColumns = async (knex: Knex) => { - const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "encryptedKubernetesTokenReviewerJwt" - ); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn( - TableName.IdentityKubernetesAuth, - "encryptedKubernetesCaCertificate" - ); - const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); - - if (hasidentityKubernetesAuthTable) { - await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { - if (hasEncryptedKubernetesTokenReviewerJwt) t.dropColumn("encryptedKubernetesTokenReviewerJwt"); - if (hasEncryptedCertificateColumn) t.dropColumn("encryptedKubernetesCaCertificate"); - }); - } -}; - -export async function down(knex: Knex): Promise { - await dropIdentityK8sColumns(knex); -} diff --git a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts deleted file mode 100644 index dc87726a4..000000000 --- a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -const reencryptIdentityOidcAuth = async (knex: Knex) => { - const hasEncryptedCertificateColumn = await knex.schema.hasColumn( - TableName.IdentityOidcAuth, - "encryptedCaCertificate" - ); - const hasidentityOidcAuthTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); - - const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "encryptedCaCert"); - const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertIV"); - const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertTag"); - - if (hasidentityOidcAuthTable) { - await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { - if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); - if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); - if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); - - if (!hasEncryptedCertificateColumn) t.binary("encryptedCaCertificate"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const orgEncryptionRingBuffer = - createCircularCache>>(25); - - const identityOidcConfig = await knex(TableName.IdentityOidcAuth) - .join( - TableName.IdentityOrgMembership, - `${TableName.IdentityOrgMembership}.identityId`, - `${TableName.IdentityOidcAuth}.identityId` - ) - .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) - .select(selectAllTableCols(TableName.IdentityOidcAuth)) - .select( - knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), - knex.ref("orgId").withSchema(TableName.OrgBot) - ) - .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - - const updatedIdentityOidcConfigs = await Promise.all( - identityOidcConfig.map( - async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { - let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }, knex); - orgEncryptionRingBuffer.push(orgId, orgKmsService); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.caCertIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.caCertTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCaCert - }) - : ""; - - const encryptedCaCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - - return { - ...el, - accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), - encryptedCaCertificate - }; - } - ) - ); - - for (let i = 0; i < updatedIdentityOidcConfigs.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.IdentityOidcAuth) - .insert(updatedIdentityOidcConfigs.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } -}; - -export async function up(knex: Knex): Promise { - await reencryptIdentityOidcAuth(knex); -} - -const dropIdentityOidcColumns = async (knex: Knex) => { - const hasEncryptedCertificateColumn = await knex.schema.hasColumn( - TableName.IdentityOidcAuth, - "encryptedCaCertificate" - ); - const hasidentityOidcTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); - - if (hasidentityOidcTable) { - await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { - if (hasEncryptedCertificateColumn) t.dropColumn("encryptedCaCertificate"); - }); - } -}; - -export async function down(knex: Knex): Promise { - await dropIdentityOidcColumns(knex); -} diff --git a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts deleted file mode 100644 index 05db40958..000000000 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ /dev/null @@ -1,484 +0,0 @@ -import { Knex } from "knex"; - -import { inMemoryKeyStore } from "@app/keystore/memory"; -import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; -import { selectAllTableCols } from "@app/lib/knex"; -import { initLogger } from "@app/lib/logger"; -import { KmsDataKey } from "@app/services/kms/kms-types"; - -import { SecretKeyEncoding, TableName } from "../schemas"; -import { getMigrationEnvConfig } from "./utils/env-config"; -import { createCircularCache } from "./utils/ring-buffer"; -import { getMigrationEncryptionServices } from "./utils/services"; - -const BATCH_SIZE = 500; -const reencryptSamlConfig = async (knex: Knex) => { - const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); - const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); - const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); - - if (hasSamlConfigTable) { - await knex.schema.alterTable(TableName.SamlConfig, (t) => { - if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint"); - if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer"); - if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const orgEncryptionRingBuffer = - createCircularCache>>(25); - - const samlConfigs = await knex(TableName.SamlConfig) - .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.SamlConfig}.orgId`) - .select(selectAllTableCols(TableName.SamlConfig)) - .select( - knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ) - .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - - const updatedSamlConfigs = await Promise.all( - samlConfigs.map( - async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { - let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); - orgEncryptionRingBuffer.push(el.orgId, orgKmsService); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedEntryPoint = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedEntryPoint && el.entryPointIV && el.entryPointTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.entryPointIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.entryPointTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedEntryPoint - }) - : ""; - - const decryptedIssuer = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedIssuer && el.issuerIV && el.issuerTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.issuerIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.issuerTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedIssuer - }) - : ""; - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCert && el.certIV && el.certTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.certIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.certTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCert - }) - : ""; - - const encryptedSamlIssuer = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedIssuer) - }).cipherTextBlob; - const encryptedSamlCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - const encryptedSamlEntryPoint = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedEntryPoint) - }).cipherTextBlob; - return { ...el, encryptedSamlCertificate, encryptedSamlEntryPoint, encryptedSamlIssuer }; - } - ) - ); - - for (let i = 0; i < updatedSamlConfigs.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.SamlConfig) - .insert(updatedSamlConfigs.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - - if (hasSamlConfigTable) { - await knex.schema.alterTable(TableName.SamlConfig, (t) => { - if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint").notNullable().alter(); - if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer").notNullable().alter(); - if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate").notNullable().alter(); - }); - } -}; - -const reencryptLdapConfig = async (knex: Knex) => { - const hasEncryptedLdapBindDNColum = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); - const hasEncryptedLdapBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); - const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); - - const hasEncryptedCACertColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedCACert"); - const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertIV"); - const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertTag"); - const hasEncryptedBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindPass"); - const hasBindPassIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassIV"); - const hasBindPassTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassTag"); - const hasEncryptedBindDNColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindDN"); - const hasBindDNIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNIV"); - const hasBindDNTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNTag"); - - if (hasLdapConfigTable) { - await knex.schema.alterTable(TableName.LdapConfig, (t) => { - if (hasEncryptedCACertColumn) t.text("encryptedCACert").nullable().alter(); - if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); - if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); - if (hasEncryptedBindPassColumn) t.string("encryptedBindPass").nullable().alter(); - if (hasBindPassIVColumn) t.string("bindPassIV").nullable().alter(); - if (hasBindPassTagColumn) t.string("bindPassTag").nullable().alter(); - if (hasEncryptedBindDNColumn) t.string("encryptedBindDN").nullable().alter(); - if (hasBindDNIVColumn) t.string("bindDNIV").nullable().alter(); - if (hasBindDNTagColumn) t.string("bindDNTag").nullable().alter(); - - if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN"); - if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass"); - if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const orgEncryptionRingBuffer = - createCircularCache>>(25); - - const ldapConfigs = await knex(TableName.LdapConfig) - .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.LdapConfig}.orgId`) - .select(selectAllTableCols(TableName.LdapConfig)) - .select( - knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ) - .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - - const updatedLdapConfigs = await Promise.all( - ldapConfigs.map( - async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { - let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); - orgEncryptionRingBuffer.push(el.orgId, orgKmsService); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedBindDN = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedBindDN && el.bindDNIV && el.bindDNTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.bindDNIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.bindDNTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedBindDN - }) - : ""; - - const decryptedBindPass = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedBindPass && el.bindPassIV && el.bindPassTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.bindPassIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.bindPassTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedBindPass - }) - : ""; - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCACert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.caCertIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.caCertTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCACert - }) - : ""; - - const encryptedLdapBindDN = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedBindDN) - }).cipherTextBlob; - const encryptedLdapBindPass = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedBindPass) - }).cipherTextBlob; - const encryptedLdapCaCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - return { ...el, encryptedLdapBindPass, encryptedLdapBindDN, encryptedLdapCaCertificate }; - } - ) - ); - - for (let i = 0; i < updatedLdapConfigs.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.LdapConfig) - .insert(updatedLdapConfigs.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - if (hasLdapConfigTable) { - await knex.schema.alterTable(TableName.LdapConfig, (t) => { - if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass").notNullable().alter(); - if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN").notNullable().alter(); - }); - } -}; - -const reencryptOidcConfig = async (knex: Knex) => { - const hasEncryptedOidcClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); - const hasEncryptedOidcClientSecretColumn = await knex.schema.hasColumn( - TableName.OidcConfig, - "encryptedOidcClientSecret" - ); - - const hasEncryptedClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientId"); - const hasClientIdIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdIV"); - const hasClientIdTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdTag"); - const hasEncryptedClientSecretColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientSecret"); - const hasClientSecretIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretIV"); - const hasClientSecretTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretTag"); - - const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); - - if (hasOidcConfigTable) { - await knex.schema.alterTable(TableName.OidcConfig, (t) => { - if (hasEncryptedClientIdColumn) t.text("encryptedClientId").nullable().alter(); - if (hasClientIdIVColumn) t.string("clientIdIV").nullable().alter(); - if (hasClientIdTagColumn) t.string("clientIdTag").nullable().alter(); - if (hasEncryptedClientSecretColumn) t.text("encryptedClientSecret").nullable().alter(); - if (hasClientSecretIVColumn) t.string("clientSecretIV").nullable().alter(); - if (hasClientSecretTagColumn) t.string("clientSecretTag").nullable().alter(); - - if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId"); - if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret"); - }); - } - - initLogger(); - const envConfig = getMigrationEnvConfig(); - const keyStore = inMemoryKeyStore(); - const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); - const orgEncryptionRingBuffer = - createCircularCache>>(25); - - const oidcConfigs = await knex(TableName.OidcConfig) - .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.OidcConfig}.orgId`) - .select(selectAllTableCols(TableName.OidcConfig)) - .select( - knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), - knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) - ) - .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - - const updatedOidcConfigs = await Promise.all( - oidcConfigs.map( - async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { - let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); - orgEncryptionRingBuffer.push(el.orgId, orgKmsService); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedClientId = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedClientId && el.clientIdIV && el.clientIdTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.clientIdIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.clientIdTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedClientId - }) - : ""; - - const decryptedClientSecret = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedClientSecret && el.clientSecretIV && el.clientSecretTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.clientSecretIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.clientSecretTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedClientSecret - }) - : ""; - - const encryptedOidcClientId = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedClientId) - }).cipherTextBlob; - const encryptedOidcClientSecret = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedClientSecret) - }).cipherTextBlob; - return { ...el, encryptedOidcClientId, encryptedOidcClientSecret }; - } - ) - ); - - for (let i = 0; i < updatedOidcConfigs.length; i += BATCH_SIZE) { - // eslint-disable-next-line no-await-in-loop - await knex(TableName.OidcConfig) - .insert(updatedOidcConfigs.slice(i, i + BATCH_SIZE)) - .onConflict("id") - .merge(); - } - if (hasOidcConfigTable) { - await knex.schema.alterTable(TableName.OidcConfig, (t) => { - if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId").notNullable().alter(); - if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret").notNullable().alter(); - }); - } -}; - -export async function up(knex: Knex): Promise { - await reencryptSamlConfig(knex); - await reencryptLdapConfig(knex); - await reencryptOidcConfig(knex); -} - -const dropSamlConfigColumns = async (knex: Knex) => { - const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); - const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); - const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); - - if (hasSamlConfigTable) { - await knex.schema.alterTable(TableName.SamlConfig, (t) => { - if (hasEncryptedEntrypointColumn) t.dropColumn("encryptedSamlEntryPoint"); - if (hasEncryptedIssuerColumn) t.dropColumn("encryptedSamlIssuer"); - if (hasEncryptedCertificateColumn) t.dropColumn("encryptedSamlCertificate"); - }); - } -}; - -const dropLdapConfigColumns = async (knex: Knex) => { - const hasEncryptedBindDN = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); - const hasEncryptedBindPass = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); - const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); - const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); - - if (hasLdapConfigTable) { - await knex.schema.alterTable(TableName.LdapConfig, (t) => { - if (hasEncryptedBindDN) t.dropColumn("encryptedLdapBindDN"); - if (hasEncryptedBindPass) t.dropColumn("encryptedLdapBindPass"); - if (hasEncryptedCertificateColumn) t.dropColumn("encryptedLdapCaCertificate"); - }); - } -}; - -const dropOidcConfigColumns = async (knex: Knex) => { - const hasEncryptedClientId = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); - const hasEncryptedClientSecret = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientSecret"); - const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); - - if (hasOidcConfigTable) { - await knex.schema.alterTable(TableName.OidcConfig, (t) => { - if (hasEncryptedClientId) t.dropColumn("encryptedOidcClientId"); - if (hasEncryptedClientSecret) t.dropColumn("encryptedOidcClientSecret"); - }); - } -}; - -export async function down(knex: Knex): Promise { - await dropSamlConfigColumns(knex); - await dropLdapConfigColumns(knex); - await dropOidcConfigColumns(knex); -} diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts deleted file mode 100644 index 05ccae97b..000000000 --- a/backend/src/db/migrations/utils/env-config.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { z } from "zod"; - -import { zpStr } from "@app/lib/zod"; - -const envSchema = z - .object({ - DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default( - `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}` - ), - DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()), - DB_HOST: zpStr(z.string().describe("Postgres database host").optional()), - DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"), - DB_USER: zpStr(z.string().describe("Postgres database username").optional()), - DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), - DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), - // TODO(akhilmhdh): will be changed to one - ENCRYPTION_KEY: zpStr(z.string().optional()), - ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()), - // HSM - 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) - }) - // 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: - Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined - })); - -export type TMigrationEnvConfig = z.infer; - -export const getMigrationEnvConfig = () => { - const parsedEnv = envSchema.safeParse(process.env); - if (!parsedEnv.success) { - // eslint-disable-next-line no-console - console.error("Invalid environment variables. Check the error below"); - // eslint-disable-next-line no-console - console.error( - "Migration is now automatic at startup. Please remove this step from your workflow and start the application as normal." - ); - // eslint-disable-next-line no-console - console.error(parsedEnv.error.issues); - process.exit(-1); - } - - return Object.freeze(parsedEnv.data); -}; diff --git a/backend/src/db/migrations/utils/kms.ts b/backend/src/db/migrations/utils/kms.ts new file mode 100644 index 000000000..9ed090978 --- /dev/null +++ b/backend/src/db/migrations/utils/kms.ts @@ -0,0 +1,105 @@ +import slugify from "@sindresorhus/slugify"; +import { Knex } from "knex"; + +import { TableName } from "@app/db/schemas"; +import { randomSecureBytes } from "@app/lib/crypto"; +import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; +import { alphaNumericNanoId } from "@app/lib/nanoid"; + +const getInstanceRootKey = async (knex: Knex) => { + const encryptionKey = process.env.ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; + // if root key its base64 encoded + const isBase64 = !process.env.ENCRYPTION_KEY; + if (!encryptionKey) throw new Error("ENCRYPTION_KEY variable needed for migration"); + const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8"); + + const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000"; + const kmsRootConfig = await knex(TableName.KmsServerRootConfig).where({ id: KMS_ROOT_CONFIG_UUID }).first(); + const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); + if (kmsRootConfig) { + const decryptedRootKey = cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer); + // set the flag so that other instancen nodes can start + return decryptedRootKey; + } + + const newRootKey = randomSecureBytes(32); + const encryptedRootKey = cipher.encrypt(newRootKey, encryptionKeyBuffer); + await knex(TableName.KmsServerRootConfig).insert({ + encryptedRootKey, + // eslint-disable-next-line + // @ts-ignore id is kept as fixed for idempotence and to avoid race condition + id: KMS_ROOT_CONFIG_UUID + }); + return encryptedRootKey; +}; + +export const getSecretManagerDataKey = async (knex: Knex, projectId: string) => { + const KMS_VERSION = "v01"; + const KMS_VERSION_BLOB_LENGTH = 3; + const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); + const project = await knex(TableName.Project).where({ id: projectId }).first(); + if (!project) throw new Error("Missing project id"); + + const ROOT_ENCRYPTION_KEY = await getInstanceRootKey(knex); + + let secretManagerKmsKey; + const projectSecretManagerKmsId = project?.kmsSecretManagerKeyId; + if (projectSecretManagerKmsId) { + const kmsDoc = await knex(TableName.KmsKey) + .leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`) + .where({ [`${TableName.KmsKey}.id` as "id"]: projectSecretManagerKmsId }) + .first(); + if (!kmsDoc) throw new Error("missing kms"); + secretManagerKmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY); + } else { + const [kmsDoc] = await knex(TableName.KmsKey) + .insert({ + name: slugify(alphaNumericNanoId(8).toLowerCase()), + orgId: project.orgId, + isReserved: false + }) + .returning("*"); + + secretManagerKmsKey = randomSecureBytes(32); + const encryptedKeyMaterial = cipher.encrypt(secretManagerKmsKey, ROOT_ENCRYPTION_KEY); + await knex(TableName.InternalKms).insert({ + version: 1, + encryptedKey: encryptedKeyMaterial, + encryptionAlgorithm: SymmetricEncryption.AES_GCM_256, + kmsKeyId: kmsDoc.id + }); + } + + const encryptedSecretManagerDataKey = project?.kmsSecretManagerEncryptedDataKey; + let dataKey: Buffer; + if (!encryptedSecretManagerDataKey) { + dataKey = randomSecureBytes(); + // the below versioning we do it automatically in kms service + const unversionedDataKey = cipher.encrypt(dataKey, secretManagerKmsKey); + const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 + await knex(TableName.Project) + .where({ id: projectId }) + .update({ + kmsSecretManagerEncryptedDataKey: Buffer.concat([unversionedDataKey, versionBlob]) + }); + } else { + const cipherTextBlob = encryptedSecretManagerDataKey.subarray(0, -KMS_VERSION_BLOB_LENGTH); + dataKey = cipher.decrypt(cipherTextBlob, secretManagerKmsKey); + } + + return { + encryptor: ({ plainText }: { plainText: Buffer }) => { + const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey); + + // Buffer#1 encrypted text + Buffer#2 version number + const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 + const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]); + return { cipherTextBlob }; + }, + decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: { cipherTextBlob: Buffer }) => { + const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH); + const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey); + return decryptedBlob; + } + }; +}; diff --git a/backend/src/db/migrations/utils/ring-buffer.ts b/backend/src/db/migrations/utils/ring-buffer.ts deleted file mode 100644 index 8e5c58662..000000000 --- a/backend/src/db/migrations/utils/ring-buffer.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const createCircularCache = (bufferSize = 10) => { - const bufferItems: { id: string; item: T }[] = []; - let bufferIndex = 0; - - const push = (id: string, item: T) => { - if (bufferItems.length < bufferSize) { - bufferItems.push({ id, item }); - } else { - bufferItems[bufferIndex] = { id, item }; - } - bufferIndex = (bufferIndex + 1) % bufferSize; - }; - - const getItem = (id: string) => { - return bufferItems.find((i) => i.id === id)?.item; - }; - - return { push, getItem }; -}; diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts deleted file mode 100644 index 731f703e2..000000000 --- a/backend/src/db/migrations/utils/services.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Knex } from "knex"; - -import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; -import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; -import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; -import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; -import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; -import { kmsServiceFactory } from "@app/services/kms/kms-service"; -import { orgDALFactory } from "@app/services/org/org-dal"; -import { projectDALFactory } from "@app/services/project/project-dal"; - -import { TMigrationEnvConfig } from "./env-config"; - -type TDependencies = { - envConfig: TMigrationEnvConfig; - db: Knex; - keyStore: TKeyStoreFactory; -}; - -export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { - // eslint-disable-next-line no-param-reassign - const hsmModule = initializeHsmModule(envConfig); - hsmModule.initialize(); - - const hsmService = hsmServiceFactory({ - hsmModule: hsmModule.getModule(), - envConfig - }); - - const orgDAL = orgDALFactory(db); - const kmsRootConfigDAL = kmsRootConfigDALFactory(db); - const kmsDAL = kmskeyDALFactory(db); - const internalKmsDAL = internalKmsDALFactory(db); - const projectDAL = projectDALFactory(db); - - const kmsService = kmsServiceFactory({ - kmsRootConfigDAL, - keyStore, - kmsDAL, - internalKmsDAL, - orgDAL, - projectDAL, - hsmService, - envConfig - }); - - await hsmService.startService(); - await kmsService.startService(); - - return { kmsService }; -}; diff --git a/backend/src/db/rename-migrations-to-mjs.ts b/backend/src/db/rename-migrations-to-mjs.ts deleted file mode 100644 index d09b5097d..000000000 --- a/backend/src/db/rename-migrations-to-mjs.ts +++ /dev/null @@ -1,56 +0,0 @@ -import path from "node:path"; - -import dotenv from "dotenv"; - -import { initAuditLogDbConnection, initDbConnection } from "./instance"; - -const isProduction = process.env.NODE_ENV === "production"; - -// Update with your config settings. . -dotenv.config({ - path: path.join(__dirname, "../../../.env.migration") -}); -dotenv.config({ - path: path.join(__dirname, "../../../.env") -}); - -const runRename = async () => { - if (!isProduction) return; - const migrationTable = "infisical_migrations"; - const applicationDb = initDbConnection({ - dbConnectionUri: process.env.DB_CONNECTION_URI as string, - dbRootCert: process.env.DB_ROOT_CERT - }); - - const auditLogDb = process.env.AUDIT_LOGS_DB_CONNECTION_URI - ? initAuditLogDbConnection({ - dbConnectionUri: process.env.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: process.env.AUDIT_LOGS_DB_ROOT_CERT - }) - : undefined; - - const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); - if (hasMigrationTable) { - const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; - if (firstFile?.name?.includes(".ts")) { - await applicationDb(migrationTable).update({ - name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") - }); - } - } - if (auditLogDb) { - const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); - if (hasMigrationTableInAuditLog) { - const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; - if (firstFile?.name?.includes(".ts")) { - await auditLogDb(migrationTable).update({ - name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") - }); - } - } - } - await applicationDb.destroy(); - await auditLogDb?.destroy(); -}; - -void runRename(); diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index eaddea8fe..b27da396c 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const DynamicSecretsSchema = z.object({ @@ -16,17 +14,16 @@ export const DynamicSecretsSchema = z.object({ type: z.string(), defaultTTL: z.string(), maxTTL: z.string().nullable().optional(), - inputIV: z.string().nullable().optional(), - inputCiphertext: z.string().nullable().optional(), - inputTag: z.string().nullable().optional(), + inputIV: z.string(), + inputCiphertext: z.string(), + inputTag: z.string(), algorithm: z.string().default("aes-256-gcm"), keyEncoding: z.string().default("utf8"), folderId: z.string().uuid(), status: z.string().nullable().optional(), statusDetails: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date(), - encryptedInput: zodBuffer + updatedAt: z.date() }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index 85f210ff1..ed99dec86 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const IdentityKubernetesAuthsSchema = z.object({ @@ -19,17 +17,15 @@ export const IdentityKubernetesAuthsSchema = z.object({ updatedAt: z.date(), identityId: z.string().uuid(), kubernetesHost: z.string(), - encryptedCaCert: z.string().nullable().optional(), - caCertIV: z.string().nullable().optional(), - caCertTag: z.string().nullable().optional(), - encryptedTokenReviewerJwt: z.string().nullable().optional(), - tokenReviewerJwtIV: z.string().nullable().optional(), - tokenReviewerJwtTag: z.string().nullable().optional(), + encryptedCaCert: z.string(), + caCertIV: z.string(), + caCertTag: z.string(), + encryptedTokenReviewerJwt: z.string(), + tokenReviewerJwtIV: z.string(), + tokenReviewerJwtTag: z.string(), allowedNamespaces: z.string(), allowedNames: z.string(), - allowedAudience: z.string(), - encryptedKubernetesTokenReviewerJwt: zodBuffer, - encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() + allowedAudience: z.string() }); export type TIdentityKubernetesAuths = z.infer; diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts index ebde5e7dc..3d7d38c41 100644 --- a/backend/src/db/schemas/identity-oidc-auths.ts +++ b/backend/src/db/schemas/identity-oidc-auths.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const IdentityOidcAuthsSchema = z.object({ @@ -17,16 +15,15 @@ export const IdentityOidcAuthsSchema = z.object({ accessTokenTrustedIps: z.unknown(), identityId: z.string().uuid(), oidcDiscoveryUrl: z.string(), - encryptedCaCert: z.string().nullable().optional(), - caCertIV: z.string().nullable().optional(), - caCertTag: z.string().nullable().optional(), + encryptedCaCert: z.string(), + caCertIV: z.string(), + caCertTag: z.string(), boundIssuer: z.string(), boundAudiences: z.string(), boundClaims: z.unknown(), boundSubject: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date(), - encryptedCaCertificate: zodBuffer.nullable().optional() + updatedAt: z.date() }); export type TIdentityOidcAuths = z.infer; diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index 778e7be6e..460c2cff6 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const LdapConfigsSchema = z.object({ @@ -14,25 +12,22 @@ export const LdapConfigsSchema = z.object({ orgId: z.string().uuid(), isActive: z.boolean(), url: z.string(), - encryptedBindDN: z.string().nullable().optional(), - bindDNIV: z.string().nullable().optional(), - bindDNTag: z.string().nullable().optional(), - encryptedBindPass: z.string().nullable().optional(), - bindPassIV: z.string().nullable().optional(), - bindPassTag: z.string().nullable().optional(), + encryptedBindDN: z.string(), + bindDNIV: z.string(), + bindDNTag: z.string(), + encryptedBindPass: z.string(), + bindPassIV: z.string(), + bindPassTag: z.string(), searchBase: z.string(), - encryptedCACert: z.string().nullable().optional(), - caCertIV: z.string().nullable().optional(), - caCertTag: z.string().nullable().optional(), + encryptedCACert: z.string(), + caCertIV: z.string(), + caCertTag: z.string(), createdAt: z.date(), updatedAt: z.date(), groupSearchBase: z.string().default(""), groupSearchFilter: z.string().default(""), searchFilter: z.string().default(""), - uniqueUserAttribute: z.string().default(""), - encryptedLdapBindDN: zodBuffer, - encryptedLdapBindPass: zodBuffer, - encryptedLdapCaCertificate: zodBuffer.nullable().optional() + uniqueUserAttribute: z.string().default("") }); export type TLdapConfigs = z.infer; diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts index 76923aee8..d7bf2f00f 100644 --- a/backend/src/db/schemas/oidc-configs.ts +++ b/backend/src/db/schemas/oidc-configs.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const OidcConfigsSchema = z.object({ @@ -17,22 +15,20 @@ export const OidcConfigsSchema = z.object({ jwksUri: z.string().nullable().optional(), tokenEndpoint: z.string().nullable().optional(), userinfoEndpoint: z.string().nullable().optional(), - encryptedClientId: z.string().nullable().optional(), + encryptedClientId: z.string(), configurationType: z.string(), - clientIdIV: z.string().nullable().optional(), - clientIdTag: z.string().nullable().optional(), - encryptedClientSecret: z.string().nullable().optional(), - clientSecretIV: z.string().nullable().optional(), - clientSecretTag: z.string().nullable().optional(), + clientIdIV: z.string(), + clientIdTag: z.string(), + encryptedClientSecret: z.string(), + clientSecretIV: z.string(), + clientSecretTag: z.string(), allowedEmailDomains: z.string().nullable().optional(), isActive: z.boolean(), createdAt: z.date(), updatedAt: z.date(), orgId: z.string().uuid(), lastUsed: z.date().nullable().optional(), - manageGroupMemberships: z.boolean().default(false), - encryptedOidcClientId: zodBuffer, - encryptedOidcClientSecret: zodBuffer + manageGroupMemberships: z.boolean().default(false) }); export type TOidcConfigs = z.infer; diff --git a/backend/src/db/schemas/saml-configs.ts b/backend/src/db/schemas/saml-configs.ts index 350e84492..67171469a 100644 --- a/backend/src/db/schemas/saml-configs.ts +++ b/backend/src/db/schemas/saml-configs.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const SamlConfigsSchema = z.object({ @@ -25,10 +23,7 @@ export const SamlConfigsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), orgId: z.string().uuid(), - lastUsed: z.date().nullable().optional(), - encryptedSamlEntryPoint: zodBuffer, - encryptedSamlIssuer: zodBuffer, - encryptedSamlCertificate: zodBuffer + lastUsed: z.date().nullable().optional() }); export type TSamlConfigs = z.infer; diff --git a/backend/src/db/schemas/secret-rotations.ts b/backend/src/db/schemas/secret-rotations.ts index a3cd04ebb..b491edc46 100644 --- a/backend/src/db/schemas/secret-rotations.ts +++ b/backend/src/db/schemas/secret-rotations.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const SecretRotationsSchema = z.object({ @@ -24,8 +22,7 @@ export const SecretRotationsSchema = z.object({ keyEncoding: z.string().nullable().optional(), envId: z.string().uuid(), createdAt: z.date(), - updatedAt: z.date(), - encryptedRotationData: zodBuffer + updatedAt: z.date() }); export type TSecretRotations = z.infer; diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index 60f031fff..a7aac2933 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -5,14 +5,12 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const WebhooksSchema = z.object({ id: z.string().uuid(), secretPath: z.string().default("/"), - url: z.string().nullable().optional(), + url: z.string(), lastStatus: z.string().nullable().optional(), lastRunErrorMessage: z.string().nullable().optional(), isDisabled: z.boolean().default(false), @@ -27,9 +25,7 @@ export const WebhooksSchema = z.object({ urlCipherText: z.string().nullable().optional(), urlIV: z.string().nullable().optional(), urlTag: z.string().nullable().optional(), - type: z.string().default("general").nullable().optional(), - encryptedPassKey: zodBuffer.nullable().optional(), - encryptedUrl: zodBuffer + type: z.string().default("general").nullable().optional() }); export type TWebhooks = z.infer; diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 2057677cf..735ba632c 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -14,7 +14,7 @@ import { FastifyRequest } from "fastify"; import LdapStrategy from "passport-ldapauth"; import { z } from "zod"; -import { LdapGroupMapsSchema } from "@app/db/schemas"; +import { LdapConfigsSchema, LdapGroupMapsSchema } from "@app/db/schemas"; import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types"; import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; import { getConfig } from "@app/lib/config/env"; @@ -22,7 +22,6 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedLdapConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerLdapRouter = async (server: FastifyZodProvider) => { @@ -188,7 +187,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { caCert: z.string().trim().default("") }), response: { - 200: SanitizedLdapConfigSchema + 200: LdapConfigsSchema } }, handler: async (req) => { @@ -229,7 +228,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: SanitizedLdapConfigSchema + 200: LdapConfigsSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index df5c61fe4..71daa3446 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -11,28 +11,13 @@ import fastifySession from "@fastify/session"; import RedisStore from "connect-redis"; import { z } from "zod"; -import { OidcConfigsSchema } from "@app/db/schemas"; +import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs"; import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types"; import { getConfig } from "@app/lib/config/env"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ - id: true, - issuer: true, - authorizationEndpoint: true, - configurationType: true, - discoveryURL: true, - jwksUri: true, - tokenEndpoint: true, - userinfoEndpoint: true, - orgId: true, - isActive: true, - allowedEmailDomains: true, - manageGroupMemberships: true -}); - export const registerOidcRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" }); @@ -157,7 +142,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { orgSlug: z.string().trim() }), response: { - 200: SanitizedOidcConfigSchema.pick({ + 200: OidcConfigsSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -229,7 +214,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ orgSlug: z.string() })), response: { - 200: SanitizedOidcConfigSchema.pick({ + 200: OidcConfigsSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -342,7 +327,20 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { } }), response: { - 200: SanitizedOidcConfigSchema + 200: OidcConfigsSchema.pick({ + id: true, + issuer: true, + authorizationEndpoint: true, + configurationType: true, + discoveryURL: true, + jwksUri: true, + tokenEndpoint: true, + userinfoEndpoint: true, + orgId: true, + isActive: true, + allowedEmailDomains: true, + manageGroupMemberships: true + }) } }, diff --git a/backend/src/ee/routes/v1/project-template-router.ts b/backend/src/ee/routes/v1/project-template-router.ts index cabf65337..60f93d65d 100644 --- a/backend/src/ee/routes/v1/project-template-router.ts +++ b/backend/src/ee/routes/v1/project-template-router.ts @@ -9,7 +9,7 @@ import { ProjectTemplates } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; import { AuthMode } from "@app/services/auth/auth-type"; const MAX_JSON_SIZE_LIMIT_IN_BYTES = 32_768; diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 71facb22a..933015a66 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -12,13 +12,13 @@ import { MultiSamlStrategy } from "@node-saml/passport-saml"; import { FastifyRequest } from "fastify"; import { z } from "zod"; +import { SamlConfigsSchema } from "@app/db/schemas"; import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedSamlConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; type TSAMLConfig = { @@ -298,7 +298,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { cert: z.string() }), response: { - 200: SanitizedSamlConfigSchema + 200: SamlConfigsSchema } }, handler: async (req) => { @@ -333,7 +333,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: SanitizedSamlConfigSchema + 200: SamlConfigsSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index de37a4cde..bb3e179dd 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/sanitizedSchema/user-additional-privilege"; +import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts index d9c3a05b5..7934c3f90 100644 --- a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchema/identitiy-additional-privilege"; +import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts index e9f00f401..810628030 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts @@ -37,7 +37,11 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"), db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"), db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"), - db.ref("encryptedInput").withSchema(TableName.DynamicSecret).as("dynEncryptedInput"), + db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"), + db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"), + db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"), + db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"), + db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"), db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"), db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"), db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"), @@ -55,7 +59,11 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { type: doc.dynType, defaultTTL: doc.dynDefaultTTL, maxTTL: doc.dynMaxTTL, - encryptedInput: doc.dynEncryptedInput, + inputIV: doc.dynInputIV, + inputTag: doc.dynInputTag, + inputCiphertext: doc.dynInputCiphertext, + algorithm: doc.dynAlgorithm, + keyEncoding: doc.dynKeyEncoding, folderId: doc.dynFolderId, status: doc.dynStatus, statusDetails: doc.dynStatusDetails, diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts index fa1a80ac3..9bdb1c24e 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts @@ -1,10 +1,8 @@ +import { SecretKeyEncoding } from "@app/db/schemas"; import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { NotFoundError } from "@app/lib/errors"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; -import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal"; import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types"; @@ -16,8 +14,6 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = { dynamicSecretLeaseDAL: Pick; dynamicSecretDAL: Pick; dynamicSecretProviders: Record; - kmsService: Pick; - folderDAL: Pick; }; export type TDynamicSecretLeaseQueueServiceFactory = ReturnType; @@ -26,9 +22,7 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ queueService, dynamicSecretDAL, dynamicSecretProviders, - dynamicSecretLeaseDAL, - kmsService, - folderDAL + dynamicSecretLeaseDAL }: TDynamicSecretLeaseQueueServiceFactoryDep) => { const pruneDynamicSecret = async (dynamicSecretCfgId: string) => { await queueService.queue( @@ -82,21 +76,15 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" }); - const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId); - if (!folder) - throw new NotFoundError({ - message: `Failed to find folder with ${dynamicSecretLease.dynamicSecret.folderId}` - }); - - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: folder.projectId - }); - const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId); @@ -112,22 +100,16 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting) throw new DisableRotationErrors({ message: "Document not deleted" }); - const folder = await folderDAL.findById(dynamicSecretCfg.folderId); - if (!folder) - throw new NotFoundError({ - message: `Failed to find folder with ${dynamicSecretCfg.folderId}` - }); - - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: folder.projectId - }); - const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId }); if (dynamicSecretLeases.length) { const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id))); diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 39e8ae7e2..830c1aa57 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import ms from "ms"; -import { ActionProjectType } from "@app/db/schemas"; +import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { @@ -9,10 +9,9 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -38,7 +37,6 @@ type TDynamicSecretLeaseServiceFactoryDep = { folderDAL: Pick; permissionService: Pick; projectDAL: Pick; - kmsService: Pick; }; export type TDynamicSecretLeaseServiceFactory = ReturnType; @@ -51,8 +49,7 @@ export const dynamicSecretLeaseServiceFactory = ({ permissionService, dynamicSecretQueueService, projectDAL, - licenseService, - kmsService + licenseService }: TDynamicSecretLeaseServiceFactoryDep) => { const create = async ({ environmentSlug, @@ -107,14 +104,13 @@ export const dynamicSecretLeaseServiceFactory = ({ throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; - - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -164,11 +160,6 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - const plan = await licenseService.getPlan(actorOrgId); if (!plan?.dynamicSecret) { throw new BadRequestError({ @@ -190,7 +181,12 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -244,11 +240,6 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); if (!folder) throw new NotFoundError({ @@ -262,7 +253,12 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; const revokeResponse = await selectedProvider diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index eac5e2ecf..631d5b6ba 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -1,16 +1,15 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType } from "@app/db/schemas"; +import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -43,7 +42,6 @@ type TDynamicSecretServiceFactoryDep = { folderDAL: Pick; projectDAL: Pick; permissionService: Pick; - kmsService: Pick; }; export type TDynamicSecretServiceFactory = ReturnType; @@ -56,8 +54,7 @@ export const dynamicSecretServiceFactory = ({ dynamicSecretProviders, permissionService, dynamicSecretQueueService, - projectDAL, - kmsService + projectDAL }: TDynamicSecretServiceFactoryDep) => { const create = async ({ path, @@ -111,15 +108,16 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(provider.inputs); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); + const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs)); const dynamicSecretCfg = await dynamicSecretDAL.create({ type: provider.type, version: 1, - encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob, + inputIV: encryptedInput.iv, + inputTag: encryptedInput.tag, + inputCiphertext: encryptedInput.ciphertext, + algorithm: encryptedInput.algorithm, + keyEncoding: encryptedInput.encoding, maxTTL, defaultTTL, folderId: folder.id, @@ -182,15 +180,15 @@ export const dynamicSecretServiceFactory = ({ if (existingDynamicSecret) throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" }); } - const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = - await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; const newInput = { ...decryptedStoredInput, ...(inputs || {}) }; const updatedInput = await selectedProvider.validateProviderInputs(newInput); @@ -198,8 +196,13 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(newInput); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); + const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput)); const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, { - encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) }).cipherTextBlob, + inputIV: encryptedInput.iv, + inputTag: encryptedInput.tag, + inputCiphertext: encryptedInput.ciphertext, + algorithm: encryptedInput.algorithm, + keyEncoding: encryptedInput.encoding, maxTTL, defaultTTL, name: newName ?? name, @@ -312,13 +315,13 @@ export const dynamicSecretServiceFactory = ({ if (!dynamicSecretCfg) { throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` }); } - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - const decryptedStoredInput = JSON.parse( - secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() + infisicalSymmetricDecrypt({ + keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, + ciphertext: dynamicSecretCfg.inputCiphertext, + tag: dynamicSecretCfg.inputTag, + iv: dynamicSecretCfg.inputIV + }) ) as object; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object; diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index ef975a371..3124e1012 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -1,23 +1,25 @@ import * as pkcs11js from "pkcs11js"; -import { TEnvConfig } from "@app/lib/config/env"; +import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmModule } from "./hsm-types"; -export const initializeHsmModule = (envConfig: Pick) => { +export const initializeHsmModule = () => { + const appCfg = getConfig(); + // Create a new instance of PKCS11 module const pkcs11 = new pkcs11js.PKCS11(); let isInitialized = false; const initialize = () => { - if (!envConfig.isHsmConfigured) { + if (!appCfg.isHsmConfigured) { return; } try { // Load the PKCS#11 module - pkcs11.load(envConfig.HSM_LIB_PATH!); + pkcs11.load(appCfg.HSM_LIB_PATH!); // Initialize the module pkcs11.C_Initialize(); diff --git a/backend/src/ee/services/hsm/hsm-service.ts b/backend/src/ee/services/hsm/hsm-service.ts index d35d17a24..a1a0773fc 100644 --- a/backend/src/ee/services/hsm/hsm-service.ts +++ b/backend/src/ee/services/hsm/hsm-service.ts @@ -1,13 +1,12 @@ import pkcs11js from "pkcs11js"; -import { TEnvConfig } from "@app/lib/config/env"; +import { getConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmKeyType, HsmModule } from "./hsm-types"; type THsmServiceFactoryDep = { hsmModule: HsmModule; - envConfig: Pick; }; export type THsmServiceFactory = ReturnType; @@ -16,7 +15,9 @@ type SyncOrAsync = T | Promise; type SessionCallback = (session: pkcs11js.Handle) => SyncOrAsync; // eslint-disable-next-line no-empty-pattern -export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envConfig }: THsmServiceFactoryDep) => { +export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsmServiceFactoryDep) => { + const appCfg = getConfig(); + // Constants for buffer structures const IV_LENGTH = 16; // Luna HSM typically expects 16-byte IV for cbc const BLOCK_SIZE = 16; @@ -62,11 +63,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon throw new Error("No slots available"); } - if (envConfig.HSM_SLOT >= slots.length) { - throw new Error(`HSM slot ${envConfig.HSM_SLOT} not found or not initialized`); + if (appCfg.HSM_SLOT >= slots.length) { + throw new Error(`HSM slot ${appCfg.HSM_SLOT} not found or not initialized`); } - const slotId = slots[envConfig.HSM_SLOT]; + const slotId = slots[appCfg.HSM_SLOT]; const startTime = Date.now(); while (Date.now() - startTime < MAX_TIMEOUT) { @@ -77,7 +78,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon // Login try { - pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, envConfig.HSM_PIN); + pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, appCfg.HSM_PIN); logger.info("HSM: Successfully authenticated"); break; } catch (error) { @@ -85,7 +86,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon if (error instanceof pkcs11js.Pkcs11Error) { if (error.code === pkcs11js.CKR_PIN_INCORRECT) { // We throw instantly here to prevent further attempts, because if too many attempts are made, the HSM will potentially wipe all key material - logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${envConfig.HSM_SLOT}`); + logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`); throw new Error("HSM: Incorrect HSM Pin detected. Please check the HSM configuration."); } if (error.code === pkcs11js.CKR_USER_ALREADY_LOGGED_IN) { @@ -132,7 +133,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon }; const $findKey = (sessionHandle: pkcs11js.Handle, type: HsmKeyType) => { - const label = type === HsmKeyType.HMAC ? `${envConfig.HSM_KEY_LABEL}_HMAC` : envConfig.HSM_KEY_LABEL; + const label = type === HsmKeyType.HMAC ? `${appCfg.HSM_KEY_LABEL}_HMAC` : appCfg.HSM_KEY_LABEL; const keyType = type === HsmKeyType.HMAC ? pkcs11js.CKK_GENERIC_SECRET : pkcs11js.CKK_AES; const template = [ @@ -359,7 +360,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon }; const isActive = async () => { - if (!isInitialized || !envConfig.isHsmConfigured) { + if (!isInitialized || !appCfg.isHsmConfigured) { return false; } @@ -371,11 +372,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon logger.error(err, "HSM: Error testing PKCS#11 module"); } - return envConfig.isHsmConfigured && isInitialized && pkcs11TestPassed; + return appCfg.isHsmConfigured && isInitialized && pkcs11TestPassed; }; const startService = async () => { - if (!envConfig.isHsmConfigured || !pkcs11 || !isInitialized) return; + if (!appCfg.isHsmConfigured || !pkcs11 || !isInitialized) return; try { await $withSession(async (sessionHandle) => { @@ -394,7 +395,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_AES }, { type: pkcs11js.CKA_VALUE_LEN, value: AES_KEY_SIZE / 8 }, - { type: pkcs11js.CKA_LABEL, value: envConfig.HSM_KEY_LABEL! }, + { type: pkcs11js.CKA_LABEL, value: appCfg.HSM_KEY_LABEL! }, { type: pkcs11js.CKA_ENCRYPT, value: true }, // Allow encryption { type: pkcs11js.CKA_DECRYPT, value: true }, // Allow decryption ...genericAttributes @@ -409,7 +410,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon keyTemplate ); - logger.info(`HSM: Master key created successfully with label: ${envConfig.HSM_KEY_LABEL}`); + logger.info(`HSM: Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`); } // Check if HMAC key exists, create if not @@ -418,7 +419,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_GENERIC_SECRET }, { type: pkcs11js.CKA_VALUE_LEN, value: HMAC_KEY_SIZE / 8 }, // 256-bit key - { type: pkcs11js.CKA_LABEL, value: `${envConfig.HSM_KEY_LABEL!}_HMAC` }, + { type: pkcs11js.CKA_LABEL, value: `${appCfg.HSM_KEY_LABEL!}_HMAC` }, { type: pkcs11js.CKA_SIGN, value: true }, // Allow signing { type: pkcs11js.CKA_VERIFY, value: true }, // Allow verification ...genericAttributes @@ -433,7 +434,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon hmacKeyTemplate ); - logger.info(`HSM: HMAC key created successfully with label: ${envConfig.HSM_KEY_LABEL}_HMAC`); + logger.info(`HSM: HMAC key created successfully with label: ${appCfg.HSM_KEY_LABEL}_HMAC`); } // Get slot info to check supported mechanisms diff --git a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts index eb9c66c1c..3a38c0d65 100644 --- a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; +import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts index d74f9c504..16c0cc212 100644 --- a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index e22b18e1b..cafc7abf0 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,18 +1,25 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, SecretKeyEncoding, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { getConfig } from "@app/lib/config/env"; +import { + decryptSymmetric, + encryptSymmetric, + generateAsymmetricKeyPair, + generateSymmetricKey, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; +import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -52,6 +59,7 @@ type TLdapConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; + orgBotDAL: Pick; groupDAL: Pick; groupProjectDAL: Pick; projectKeyDAL: Pick; @@ -76,7 +84,6 @@ type TLdapConfigServiceFactoryDep = { licenseService: Pick; tokenService: Pick; smtpService: Pick; - kmsService: Pick; }; export type TLdapConfigServiceFactory = ReturnType; @@ -86,6 +93,7 @@ export const ldapConfigServiceFactory = ({ ldapGroupMapDAL, orgDAL, orgMembershipDAL, + orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -97,8 +105,7 @@ export const ldapConfigServiceFactory = ({ permissionService, licenseService, tokenService, - smtpService, - kmsService + smtpService }: TLdapConfigServiceFactoryDep) => { const createLdapCfg = async ({ actor, @@ -126,23 +133,77 @@ export const ldapConfigServiceFactory = ({ message: "Failed to create LDAP configuration due to plan restriction. Upgrade plan to create LDAP configuration." }); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId + + const orgBot = await orgBotDAL.transaction(async (tx) => { + const doc = await orgBotDAL.findOne({ orgId }, tx); + if (doc) return doc; + + const { privateKey, publicKey } = generateAsymmetricKeyPair(); + const key = generateSymmetricKey(); + const { + ciphertext: encryptedPrivateKey, + iv: privateKeyIV, + tag: privateKeyTag, + encoding: privateKeyKeyEncoding, + algorithm: privateKeyAlgorithm + } = infisicalSymmetricEncypt(privateKey); + const { + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + encoding: symmetricKeyKeyEncoding, + algorithm: symmetricKeyAlgorithm + } = infisicalSymmetricEncypt(key); + + return orgBotDAL.create( + { + name: "Infisical org bot", + publicKey, + privateKeyIV, + encryptedPrivateKey, + symmetricKeyIV, + symmetricKeyTag, + encryptedSymmetricKey, + symmetricKeyAlgorithm, + orgId, + privateKeyTag, + privateKeyAlgorithm, + privateKeyKeyEncoding, + symmetricKeyKeyEncoding + }, + tx + ); }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); + const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); + const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + const ldapConfig = await ldapConfigDAL.create({ orgId, isActive, url, + encryptedBindDN, + bindDNIV, + bindDNTag, + encryptedBindPass, + bindPassIV, + bindPassTag, uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, groupSearchFilter, - encryptedLdapCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, - encryptedLdapBindDN: encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob, - encryptedLdapBindPass: encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob + encryptedCACert, + caCertIV, + caCertTag }); return ldapConfig; @@ -185,21 +246,38 @@ export const ldapConfigServiceFactory = ({ uniqueUserAttribute }; - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId + const orgBot = await orgBotDAL.findOne({ orgId }); + if (!orgBot) + throw new NotFoundError({ + message: `Organization bot in organization with ID '${orgId}' not found`, + name: "OrgBotNotFound" + }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); if (bindDN !== undefined) { - updateQuery.encryptedLdapBindDN = encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob; + const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); + updateQuery.encryptedBindDN = encryptedBindDN; + updateQuery.bindDNIV = bindDNIV; + updateQuery.bindDNTag = bindDNTag; } if (bindPass !== undefined) { - updateQuery.encryptedLdapBindPass = encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob; + const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); + updateQuery.encryptedBindPass = encryptedBindPass; + updateQuery.bindPassIV = bindPassIV; + updateQuery.bindPassTag = bindPassTag; } if (caCert !== undefined) { - updateQuery.encryptedLdapCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; + const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + updateQuery.encryptedCACert = encryptedCACert; + updateQuery.caCertIV = caCertIV; + updateQuery.caCertTag = caCertTag; } const [ldapConfig] = await ldapConfigDAL.update({ orgId }, updateQuery); @@ -215,24 +293,61 @@ export const ldapConfigServiceFactory = ({ }); } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: ldapConfig.orgId + const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found in organization with ID ${ldapConfig.orgId}`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); + const { + encryptedBindDN, + bindDNIV, + bindDNTag, + encryptedBindPass, + bindPassIV, + bindPassTag, + encryptedCACert, + caCertIV, + caCertTag + } = ldapConfig; + let bindDN = ""; - if (ldapConfig.encryptedLdapBindDN) { - bindDN = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindDN }).toString(); + if (encryptedBindDN && bindDNIV && bindDNTag) { + bindDN = decryptSymmetric({ + ciphertext: encryptedBindDN, + key, + tag: bindDNTag, + iv: bindDNIV + }); } let bindPass = ""; - if (ldapConfig.encryptedLdapBindPass) { - bindPass = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindPass }).toString(); + if (encryptedBindPass && bindPassIV && bindPassTag) { + bindPass = decryptSymmetric({ + ciphertext: encryptedBindPass, + key, + tag: bindPassTag, + iv: bindPassIV + }); } let caCert = ""; - if (ldapConfig.encryptedLdapCaCertificate) { - caCert = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapCaCertificate }).toString(); + if (encryptedCACert && caCertIV && caCertTag) { + caCert = decryptSymmetric({ + ciphertext: encryptedCACert, + key, + tag: caCertTag, + iv: caCertIV + }); } return { diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 52c8dd597..0c037a2d3 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; -import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas"; import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; @@ -14,14 +14,21 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { getConfig } from "@app/lib/config/env"; +import { + decryptSymmetric, + encryptSymmetric, + generateAsymmetricKeyPair, + generateSymmetricKey, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; +import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -63,6 +70,7 @@ type TOidcConfigServiceFactoryDep = { "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; orgMembershipDAL: Pick; + orgBotDAL: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; @@ -83,7 +91,6 @@ type TOidcConfigServiceFactoryDep = { projectDAL: Pick; projectBotDAL: Pick; auditLogService: Pick; - kmsService: Pick; }; export type TOidcConfigServiceFactory = ReturnType; @@ -96,6 +103,7 @@ export const oidcConfigServiceFactory = ({ licenseService, permissionService, tokenService, + orgBotDAL, smtpService, oidcConfigDAL, userGroupMembershipDAL, @@ -104,8 +112,7 @@ export const oidcConfigServiceFactory = ({ projectKeyDAL, projectDAL, projectBotDAL, - auditLogService, - kmsService + auditLogService }: TOidcConfigServiceFactoryDep) => { const getOidc = async (dto: TGetOidcCfgDTO) => { const org = await orgDAL.findOne({ slug: dto.orgSlug }); @@ -136,19 +143,43 @@ export const oidcConfigServiceFactory = ({ }); } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: oidcCfg.orgId + // decrypt and return cfg + const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot for organization with ID '${oidcCfg.orgId}' not found`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); + const { encryptedClientId, clientIdIV, clientIdTag, encryptedClientSecret, clientSecretIV, clientSecretTag } = + oidcCfg; + let clientId = ""; - if (oidcCfg.encryptedOidcClientId) { - clientId = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientId }).toString(); + if (encryptedClientId && clientIdIV && clientIdTag) { + clientId = decryptSymmetric({ + ciphertext: encryptedClientId, + key, + tag: clientIdTag, + iv: clientIdIV + }); } let clientSecret = ""; - if (oidcCfg.encryptedOidcClientSecret) { - clientSecret = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientSecret }).toString(); + if (encryptedClientSecret && clientSecretIV && clientSecretTag) { + clientSecret = decryptSymmetric({ + key, + tag: clientSecretTag, + iv: clientSecretIV, + ciphertext: encryptedClientSecret + }); } return { @@ -509,10 +540,12 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: org.id - }); + const orgBot = await orgBotDAL.findOne({ orgId: org.id }); + if (!orgBot) + throw new NotFoundError({ + message: `Organization bot for organization with ID '${org.id}' not found`, + name: "OrgBotNotFound" + }); const serverCfg = await getServerCfg(); if (isActive && !serverCfg.trustOidcEmails) { @@ -525,6 +558,13 @@ export const oidcConfigServiceFactory = ({ } } + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + const updateQuery: TOidcConfigsUpdate = { allowedEmailDomains, configurationType, @@ -540,11 +580,22 @@ export const oidcConfigServiceFactory = ({ }; if (clientId !== undefined) { - updateQuery.encryptedOidcClientId = encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob; + const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); + updateQuery.encryptedClientId = encryptedClientId; + updateQuery.clientIdIV = clientIdIV; + updateQuery.clientIdTag = clientIdTag; } if (clientSecret !== undefined) { - updateQuery.encryptedOidcClientSecret = encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob; + const { + ciphertext: encryptedClientSecret, + iv: clientSecretIV, + tag: clientSecretTag + } = encryptSymmetric(clientSecret, key); + + updateQuery.encryptedClientSecret = encryptedClientSecret; + updateQuery.clientSecretIV = clientSecretIV; + updateQuery.clientSecretTag = clientSecretTag; } const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery); @@ -596,11 +647,61 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: org.id + const orgBot = await orgBotDAL.transaction(async (tx) => { + const doc = await orgBotDAL.findOne({ orgId: org.id }, tx); + if (doc) return doc; + + const { privateKey, publicKey } = generateAsymmetricKeyPair(); + const key = generateSymmetricKey(); + const { + ciphertext: encryptedPrivateKey, + iv: privateKeyIV, + tag: privateKeyTag, + encoding: privateKeyKeyEncoding, + algorithm: privateKeyAlgorithm + } = infisicalSymmetricEncypt(privateKey); + const { + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + encoding: symmetricKeyKeyEncoding, + algorithm: symmetricKeyAlgorithm + } = infisicalSymmetricEncypt(key); + + return orgBotDAL.create( + { + name: "Infisical org bot", + publicKey, + privateKeyIV, + encryptedPrivateKey, + symmetricKeyIV, + symmetricKeyTag, + encryptedSymmetricKey, + symmetricKeyAlgorithm, + orgId: org.id, + privateKeyTag, + privateKeyAlgorithm, + privateKeyKeyEncoding, + symmetricKeyKeyEncoding + }, + tx + ); }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); + const { + ciphertext: encryptedClientSecret, + iv: clientSecretIV, + tag: clientSecretTag + } = encryptSymmetric(clientSecret, key); + const oidcCfg = await oidcConfigDAL.create({ issuer, isActive, @@ -612,9 +713,13 @@ export const oidcConfigServiceFactory = ({ tokenEndpoint, userinfoEndpoint, orgId: org.id, - manageGroupMemberships, - encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob, - encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob + encryptedClientId, + clientIdIV, + clientIdTag, + encryptedClientSecret, + clientSecretIV, + clientSecretTag, + manageGroupMemberships }); return oidcCfg; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index 657e9ce3a..e9ba49127 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -6,7 +6,7 @@ import { CASL_ACTION_SCHEMA_NATIVE_ENUM } from "@app/ee/services/permission/permission-schemas"; import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; import { PermissionConditionSchema } from "./permission-types"; diff --git a/backend/src/ee/services/project-template/project-template-service.ts b/backend/src/ee/services/project-template/project-template-service.ts index b2430ac14..5afa58caf 100644 --- a/backend/src/ee/services/project-template/project-template-service.ts +++ b/backend/src/ee/services/project-template/project-template-service.ts @@ -15,7 +15,7 @@ import { } from "@app/ee/services/project-template/project-template-types"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; -import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; +import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"; import { TProjectTemplateDALFactory } from "./project-template-dal"; diff --git a/backend/src/ee/services/project-template/project-template-types.ts b/backend/src/ee/services/project-template/project-template-types.ts index c2764dc53..6b600f386 100644 --- a/backend/src/ee/services/project-template/project-template-types.ts +++ b/backend/src/ee/services/project-template/project-template-types.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { TProjectEnvironments } from "@app/db/schemas"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; export type TProjectTemplateEnvironment = Pick; diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index 6f87663b2..14586d5e2 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index f22e2ad58..068a45520 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -1,15 +1,29 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { OrgMembershipStatus, TableName, TSamlConfigs, TSamlConfigsUpdate, TUsers } from "@app/db/schemas"; +import { + OrgMembershipStatus, + SecretKeyEncoding, + TableName, + TSamlConfigs, + TSamlConfigsUpdate, + TUsers +} from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; +import { + decryptSymmetric, + encryptSymmetric, + generateAsymmetricKeyPair, + generateSymmetricKey, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; +import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -38,19 +52,21 @@ type TSamlConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; + identityMetadataDAL: Pick; orgMembershipDAL: Pick; + orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; - kmsService: Pick; }; export type TSamlConfigServiceFactory = ReturnType; export const samlConfigServiceFactory = ({ samlConfigDAL, + orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -59,8 +75,7 @@ export const samlConfigServiceFactory = ({ licenseService, tokenService, smtpService, - identityMetadataDAL, - kmsService + identityMetadataDAL }: TSamlConfigServiceFactoryDep) => { const createSamlCfg = async ({ cert, @@ -84,18 +99,70 @@ export const samlConfigServiceFactory = ({ "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to create SSO configuration." }); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId + const orgBot = await orgBotDAL.transaction(async (tx) => { + const doc = await orgBotDAL.findOne({ orgId }, tx); + if (doc) return doc; + + const { privateKey, publicKey } = generateAsymmetricKeyPair(); + const key = generateSymmetricKey(); + const { + ciphertext: encryptedPrivateKey, + iv: privateKeyIV, + tag: privateKeyTag, + encoding: privateKeyKeyEncoding, + algorithm: privateKeyAlgorithm + } = infisicalSymmetricEncypt(privateKey); + const { + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + encoding: symmetricKeyKeyEncoding, + algorithm: symmetricKeyAlgorithm + } = infisicalSymmetricEncypt(key); + + return orgBotDAL.create( + { + name: "Infisical org bot", + publicKey, + privateKeyIV, + encryptedPrivateKey, + symmetricKeyIV, + symmetricKeyTag, + encryptedSymmetricKey, + symmetricKeyAlgorithm, + orgId, + privateKeyTag, + privateKeyAlgorithm, + privateKeyKeyEncoding, + symmetricKeyKeyEncoding + }, + tx + ); }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key); + const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); + const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); const samlConfig = await samlConfigDAL.create({ orgId, authProvider, isActive, - encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob, - encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob, - encryptedSamlCertificate: encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob + encryptedEntryPoint, + entryPointIV, + entryPointTag, + encryptedIssuer, + issuerIV, + issuerTag, + encryptedCert, + certIV, + certTag }); return samlConfig; @@ -123,21 +190,40 @@ export const samlConfigServiceFactory = ({ }); const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId + const orgBot = await orgBotDAL.findOne({ orgId }); + if (!orgBot) + throw new NotFoundError({ + message: `Organization bot not found for organization with ID '${orgId}'`, + name: "OrgBotNotFound" + }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); if (entryPoint !== undefined) { - updateQuery.encryptedSamlEntryPoint = encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob; + const { + ciphertext: encryptedEntryPoint, + iv: entryPointIV, + tag: entryPointTag + } = encryptSymmetric(entryPoint, key); + updateQuery.encryptedEntryPoint = encryptedEntryPoint; + updateQuery.entryPointIV = entryPointIV; + updateQuery.entryPointTag = entryPointTag; } - if (issuer !== undefined) { - updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob; + const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); + updateQuery.encryptedIssuer = encryptedIssuer; + updateQuery.issuerIV = issuerIV; + updateQuery.issuerTag = issuerTag; } - if (cert !== undefined) { - updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob; + const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); + updateQuery.encryptedCert = encryptedCert; + updateQuery.certIV = certIV; + updateQuery.certTag = certTag; } const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); @@ -147,14 +233,14 @@ export const samlConfigServiceFactory = ({ }; const getSaml = async (dto: TGetSamlCfgDTO) => { - let samlConfig: TSamlConfigs | undefined; + let ssoConfig: TSamlConfigs | undefined; if (dto.type === "org") { - samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); - if (!samlConfig) return; + ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); + if (!ssoConfig) return; } else if (dto.type === "orgSlug") { const org = await orgDAL.findOne({ slug: dto.orgSlug }); if (!org) return; - samlConfig = await samlConfigDAL.findOne({ orgId: org.id }); + ssoConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: // We made this change because saml config ids were not moved over during the migration @@ -173,51 +259,81 @@ export const samlConfigServiceFactory = ({ const id = UUIDToMongoId[dto.id] ?? dto.id; - samlConfig = await samlConfigDAL.findById(id); + ssoConfig = await samlConfigDAL.findById(id); } - if (!samlConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); + if (!ssoConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); // when dto is type id means it's internally used if (dto.type === "org") { const { permission } = await permissionService.getOrgPermission( dto.actor, dto.actorId, - samlConfig.orgId, + ssoConfig.orgId, dto.actorAuthMethod, dto.actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: samlConfig.orgId + const { + entryPointTag, + entryPointIV, + encryptedEntryPoint, + certTag, + certIV, + encryptedCert, + issuerTag, + issuerIV, + encryptedIssuer + } = ssoConfig; + + const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId }); + if (!orgBot) + throw new NotFoundError({ + message: `Organization bot not found in organization with ID '${ssoConfig.orgId}'`, + name: "OrgBotNotFound" + }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); let entryPoint = ""; - if (samlConfig.encryptedSamlEntryPoint) { - entryPoint = decryptor({ cipherTextBlob: samlConfig.encryptedSamlEntryPoint }).toString(); + if (encryptedEntryPoint && entryPointIV && entryPointTag) { + entryPoint = decryptSymmetric({ + ciphertext: encryptedEntryPoint, + key, + tag: entryPointTag, + iv: entryPointIV + }); } let issuer = ""; - if (samlConfig.encryptedSamlIssuer) { - issuer = decryptor({ cipherTextBlob: samlConfig.encryptedSamlIssuer }).toString(); + if (encryptedIssuer && issuerTag && issuerIV) { + issuer = decryptSymmetric({ + key, + tag: issuerTag, + iv: issuerIV, + ciphertext: encryptedIssuer + }); } let cert = ""; - if (samlConfig.encryptedSamlCertificate) { - cert = decryptor({ cipherTextBlob: samlConfig.encryptedSamlCertificate }).toString(); + if (encryptedCert && certTag && certIV) { + cert = decryptSymmetric({ key, tag: certTag, iv: certIV, ciphertext: encryptedCert }); } return { - id: samlConfig.id, - organization: samlConfig.orgId, - orgId: samlConfig.orgId, - authProvider: samlConfig.authProvider, - isActive: samlConfig.isActive, + id: ssoConfig.id, + organization: ssoConfig.orgId, + orgId: ssoConfig.orgId, + authProvider: ssoConfig.authProvider, + isActive: ssoConfig.isActive, entryPoint, issuer, cert, - lastUsed: samlConfig.lastUsed + lastUsed: ssoConfig.lastUsed }; }; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index fdc493b9f..355507ecf 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -5,9 +5,13 @@ import { IAMClient } from "@aws-sdk/client-iam"; -import { SecretType } from "@app/db/schemas"; +import { SecretKeyEncoding, SecretType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; +import { + encryptSymmetric128BitHexKeyUTF8, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -131,15 +135,20 @@ export const secretRotationQueueFactory = ({ // deep copy const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate; - const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = - await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: secretRotation.projectId - }); - const decryptedData = secretManagerDecryptor({ - cipherTextBlob: secretRotation.encryptedRotationData - }).toString(); + // now get the encrypted variable values + // in includes the inputs, the previous outputs + // internal mapping variables etc + const { encryptedDataTag, encryptedDataIV, encryptedData, keyEncoding } = secretRotation; + if (!encryptedDataTag || !encryptedDataIV || !encryptedData || !keyEncoding) { + throw new DisableRotationErrors({ message: "No inputs found" }); + } + const decryptedData = infisicalSymmetricDecrypt({ + keyEncoding: keyEncoding as SecretKeyEncoding, + ciphertext: encryptedData, + iv: encryptedDataIV, + tag: encryptedDataTag + }); const variables = JSON.parse(decryptedData) as TSecretRotationEncData; // rotation set cycle @@ -294,9 +303,11 @@ export const secretRotationQueueFactory = ({ outputs: newCredential.outputs, internal: newCredential.internal }); - const encryptedRotationData = secretManagerEncryptor({ - plainText: Buffer.from(JSON.stringify(variables)) - }).cipherTextBlob; + const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables)); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: secretRotation.projectId + }); const numberOfSecretsRotated = rotationOutputs.length; if (shouldUseSecretV2Bridge) { @@ -312,7 +323,11 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedRotationData, + encryptedData: encVarData.ciphertext, + encryptedDataIV: encVarData.iv, + encryptedDataTag: encVarData.tag, + keyEncoding: encVarData.encoding, + algorithm: encVarData.algorithm, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" @@ -356,7 +371,11 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedRotationData, + encryptedData: encVarData.ciphertext, + encryptedDataIV: encVarData.iv, + encryptedDataTag: encVarData.tag, + keyEncoding: encVarData.encoding, + algorithm: encVarData.algorithm, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index 02da4b7ea..c456e1581 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -2,11 +2,9 @@ import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; -import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; +import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; -import { TKmsServiceFactory } from "@app/services/kms/kms-service"; -import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretDALFactory } from "@app/services/secret/secret-dal"; @@ -32,7 +30,6 @@ type TSecretRotationServiceFactoryDep = { permissionService: Pick; secretRotationQueue: TSecretRotationQueueFactory; projectBotService: Pick; - kmsService: Pick; }; export type TSecretRotationServiceFactory = ReturnType; @@ -47,8 +44,7 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL, projectBotService, - secretV2BridgeDAL, - kmsService + secretV2BridgeDAL }: TSecretRotationServiceFactoryDep) => { const getProviderTemplates = async ({ actor, @@ -160,11 +156,7 @@ export const secretRotationServiceFactory = ({ inputs: formattedInputs, creds: [] }; - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - + const encData = infisicalSymmetricEncypt(JSON.stringify(unencryptedData)); const secretRotation = await secretRotationDAL.transaction(async (tx) => { const doc = await secretRotationDAL.create( { @@ -172,8 +164,11 @@ export const secretRotationServiceFactory = ({ secretPath, interval, envId: folder.envId, - encryptedRotationData: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(unencryptedData)) }) - .cipherTextBlob + encryptedDataTag: encData.tag, + encryptedDataIV: encData.iv, + encryptedData: encData.ciphertext, + algorithm: encData.algorithm, + keyEncoding: encData.encoding }, tx ); diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 1c34f6b3d..2e4ed0f93 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ -// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error import { ForbiddenError, subject } from "@casl/ability"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; diff --git a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts index d8240f27e..8a9eeab8c 100644 --- a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts +++ b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-await-in-loop,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ +/* eslint-disable no-await-in-loop */ import { Knex } from "knex"; import { z } from "zod"; diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index a5f3c24c0..dbfdfd063 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -2,12 +2,6 @@ import { Redis } from "ioredis"; import { Redlock, Settings } from "@app/lib/red-lock"; -export enum PgSqlLock { - BootUpMigration = 2023, - SuperAdminInit = 2024, - KmsRootKeyInit = 2025 -} - export type TKeyStoreFactory = ReturnType; // all the key prefixes used must be set here to avoid conflict diff --git a/backend/src/keystore/memory.ts b/backend/src/keystore/memory.ts deleted file mode 100644 index 1fe78cf7e..000000000 --- a/backend/src/keystore/memory.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Lock } from "@app/lib/red-lock"; - -import { TKeyStoreFactory } from "./keystore"; - -export const inMemoryKeyStore = (): TKeyStoreFactory => { - const store: Record = {}; - - return { - setItem: async (key, value) => { - store[key] = value; - return "OK"; - }, - setItemWithExpiry: async (key, value) => { - store[key] = value; - return "OK"; - }, - deleteItem: async (key) => { - delete store[key]; - return 1; - }, - getItem: async (key) => { - const value = store[key]; - if (typeof value === "string") { - return value; - } - return null; - }, - incrementBy: async () => { - return 1; - }, - acquireLock: () => { - return Promise.resolve({ - release: () => {} - }) as Promise; - }, - waitTillReady: async () => {} - }; -}; diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 801e937a2..7f0f31728 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -258,8 +258,7 @@ const envSchema = z SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",") })); -export type TEnvConfig = Readonly>; -let envCfg: TEnvConfig; +let envCfg: Readonly>; export const getConfig = () => envCfg; // cannot import singleton logger directly as it needs config to load various transport diff --git a/backend/src/lib/logger/logger.ts b/backend/src/lib/logger/logger.ts index 170a0285f..9676496f7 100644 --- a/backend/src/lib/logger/logger.ts +++ b/backend/src/lib/logger/logger.ts @@ -98,7 +98,7 @@ const extractReqId = () => { } }; -export const initLogger = () => { +export const initLogger = async () => { const cfg = loggerConfig.parse(process.env); const targets: pino.TransportMultiOptions["targets"][number][] = [ { diff --git a/backend/src/main.ts b/backend/src/main.ts index 461601fc0..850298f89 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -2,13 +2,14 @@ import "./lib/telemetry/instrumentation"; import dotenv from "dotenv"; import { Redis } from "ioredis"; +import path from "path"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; -import { runMigrations } from "./auto-start-migrations"; import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; -import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; +import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env"; +import { isMigrationMode } from "./lib/fn"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; @@ -18,53 +19,58 @@ import { smtpServiceFactory } from "./services/smtp/smtp-service"; dotenv.config(); const run = async () => { - const logger = initLogger(); - const envConfig = initEnvConfig(logger); + const logger = await initLogger(); + const appCfg = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT, - readReplicas: envConfig.DB_READ_REPLICAS?.map((el) => ({ + dbConnectionUri: appCfg.DB_CONNECTION_URI, + dbRootCert: appCfg.DB_ROOT_CERT, + readReplicas: appCfg.DB_READ_REPLICAS?.map((el) => ({ dbRootCert: el.DB_ROOT_CERT, dbConnectionUri: el.DB_CONNECTION_URI })) }); - const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI + const auditLogDb = appCfg.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ - dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT + dbConnectionUri: appCfg.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: appCfg.AUDIT_LOGS_DB_ROOT_CERT }) : undefined; - await runMigrations({ applicationDb: db, auditLogDb, logger }); + // Case: App is running in packaged mode (binary), and migration mode is enabled. + // Run the migrations and exit the process after completion. + if (IS_PACKAGED && isMigrationMode()) { + try { + logger.info("Running Postgres migrations.."); + await db.migrate.latest({ + directory: path.join(__dirname, "./db/migrations") + }); + logger.info("Postgres migrations completed"); + } catch (err) { + logger.error(err, "Failed to run migrations"); + process.exit(1); + } + + process.exit(0); + } const smtp = smtpServiceFactory(formatSmtpConfig()); - const queue = queueServiceFactory(envConfig.REDIS_URL, { - dbConnectionUrl: envConfig.DB_CONNECTION_URI, - dbRootCert: envConfig.DB_ROOT_CERT + const queue = queueServiceFactory(appCfg.REDIS_URL, { + dbConnectionUrl: appCfg.DB_CONNECTION_URI, + dbRootCert: appCfg.DB_ROOT_CERT }); await queue.initialize(); - const keyStore = keyStoreFactory(envConfig.REDIS_URL); - const redis = new Redis(envConfig.REDIS_URL); + const keyStore = keyStoreFactory(appCfg.REDIS_URL); + const redis = new Redis(appCfg.REDIS_URL); - const hsmModule = initializeHsmModule(envConfig); + const hsmModule = initializeHsmModule(); hsmModule.initialize(); - const server = await main({ - db, - auditLogDb, - hsmModule: hsmModule.getModule(), - smtp, - logger, - queue, - keyStore, - redis, - envConfig - }); + const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore, redis }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line @@ -84,8 +90,8 @@ const run = async () => { }); await server.listen({ - port: envConfig.PORT, - host: envConfig.HOST, + port: appCfg.PORT, + host: appCfg.HOST, listenTextResolver: (address) => { void bootstrap(); return address; diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index 26f556508..ce1be4a04 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -17,7 +17,7 @@ import { Knex } from "knex"; import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env"; +import { getConfig, IS_PACKAGED } from "@app/lib/config/env"; import { CustomLogger } from "@app/lib/logger/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TQueueServiceFactory } from "@app/queue"; @@ -43,11 +43,10 @@ type TMain = { keyStore: TKeyStoreFactory; hsmModule: HsmModule; redis: Redis; - envConfig: TEnvConfig; }; // Run the server! -export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis, envConfig }: TMain) => { +export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis }: TMain) => { const appCfg = getConfig(); const server = fastify({ @@ -128,7 +127,7 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key }) }); - await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule, envConfig }); + await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule }); await server.register(registerServeUI, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 10da81bf5..8cebbdebd 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -85,7 +85,7 @@ import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certi import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig, TEnvConfig } from "@app/lib/config/env"; +import { getConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { readLimit } from "@app/server/config/rateLimiter"; import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue"; @@ -244,8 +244,7 @@ export const registerRoutes = async ( hsmModule, smtp: smtpService, queue: queueService, - keyStore, - envConfig + keyStore }: { auditLogDb?: Knex; db: Knex; @@ -253,7 +252,6 @@ export const registerRoutes = async ( smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory; - envConfig: TEnvConfig; } ) => { const appCfg = getConfig(); @@ -393,8 +391,7 @@ export const registerRoutes = async ( const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore }); const hsmService = hsmServiceFactory({ - hsmModule, - envConfig + hsmModule }); const kmsService = kmsServiceFactory({ @@ -404,8 +401,7 @@ export const registerRoutes = async ( internalKmsDAL, orgDAL, projectDAL, - hsmService, - envConfig + hsmService }); const externalKmsService = externalKmsServiceFactory({ @@ -451,6 +447,7 @@ export const registerRoutes = async ( const samlService = samlConfigServiceFactory({ identityMetadataDAL, permissionService, + orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -458,8 +455,7 @@ export const registerRoutes = async ( samlConfigDAL, licenseService, tokenService, - smtpService, - kmsService + smtpService }); const groupService = groupServiceFactory({ userDAL, @@ -510,6 +506,7 @@ export const registerRoutes = async ( ldapGroupMapDAL, orgDAL, orgMembershipDAL, + orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -521,8 +518,7 @@ export const registerRoutes = async ( permissionService, licenseService, tokenService, - smtpService, - kmsService + smtpService }); const telemetryService = telemetryServiceFactory({ @@ -973,8 +969,7 @@ export const registerRoutes = async ( permissionService, webhookDAL, projectEnvDAL, - projectDAL, - kmsService + projectDAL }); const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService }); @@ -1154,8 +1149,7 @@ export const registerRoutes = async ( secretDAL, folderDAL, projectBotService, - secretV2BridgeDAL, - kmsService + secretV2BridgeDAL }); const integrationService = integrationServiceFactory({ @@ -1244,9 +1238,9 @@ export const registerRoutes = async ( identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, + orgBotDAL, permissionService, - licenseService, - kmsService + licenseService }); const identityGcpAuthService = identityGcpAuthServiceFactory({ identityGcpAuthDAL, @@ -1278,7 +1272,7 @@ export const registerRoutes = async ( identityAccessTokenDAL, permissionService, licenseService, - kmsService + orgBotDAL }); const identityJwtAuthService = identityJwtAuthServiceFactory({ @@ -1295,9 +1289,7 @@ export const registerRoutes = async ( queueService, dynamicSecretLeaseDAL, dynamicSecretProviders, - dynamicSecretDAL, - folderDAL, - kmsService + dynamicSecretDAL }); const dynamicSecretService = dynamicSecretServiceFactory({ projectDAL, @@ -1307,8 +1299,7 @@ export const registerRoutes = async ( dynamicSecretProviders, folderDAL, permissionService, - licenseService, - kmsService + licenseService }); const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({ projectDAL, @@ -1318,8 +1309,7 @@ export const registerRoutes = async ( dynamicSecretLeaseDAL, dynamicSecretProviders, folderDAL, - licenseService, - kmsService + licenseService }); const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ auditLogDAL, @@ -1347,7 +1337,7 @@ export const registerRoutes = async ( licenseService, tokenService, smtpService, - kmsService, + orgBotDAL, permissionService, oidcConfigDAL, projectBotDAL, diff --git a/backend/src/server/routes/sanitizedSchema/directory-config.ts b/backend/src/server/routes/sanitizedSchema/directory-config.ts deleted file mode 100644 index 61be4d9cf..000000000 --- a/backend/src/server/routes/sanitizedSchema/directory-config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { LdapConfigsSchema, OidcConfigsSchema, SamlConfigsSchema } from "@app/db/schemas"; - -export const SanitizedSamlConfigSchema = SamlConfigsSchema.pick({ - id: true, - orgId: true, - isActive: true, - lastUsed: true, - createdAt: true, - updatedAt: true, - authProvider: true -}); - -export const SanitizedLdapConfigSchema = LdapConfigsSchema.pick({ - updatedAt: true, - createdAt: true, - isActive: true, - orgId: true, - id: true, - url: true, - searchBase: true, - searchFilter: true, - groupSearchBase: true, - uniqueUserAttribute: true, - groupSearchFilter: true -}); - -export const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ - id: true, - orgId: true, - isActive: true, - createdAt: true, - updatedAt: true, - lastUsed: true, - issuer: true, - jwksUri: true, - discoveryURL: true, - tokenEndpoint: true, - userinfoEndpoint: true, - configurationType: true, - allowedEmailDomains: true, - authorizationEndpoint: true -}); diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 4d645ac4b..67f01c9ba 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -11,7 +11,7 @@ import { } from "@app/db/schemas"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "./sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "./santizedSchemas/permission"; // sometimes the return data must be santizied to avoid leaking important values // always prefer pick over omit in zod @@ -201,11 +201,10 @@ export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({ }); export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ - encryptedInput: true, - keyEncoding: true, - inputCiphertext: true, inputIV: true, inputTag: true, + inputCiphertext: true, + keyEncoding: true, algorithm: true }); diff --git a/backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts b/backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts rename to backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts diff --git a/backend/src/server/routes/sanitizedSchema/permission.ts b/backend/src/server/routes/santizedSchemas/permission.ts similarity index 100% rename from backend/src/server/routes/sanitizedSchema/permission.ts rename to backend/src/server/routes/santizedSchemas/permission.ts diff --git a/backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts b/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts rename to backend/src/server/routes/santizedSchemas/user-additional-privilege.ts diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 263fa478e..3b3025179 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -8,19 +8,13 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; -const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({ - id: true, - accessTokenTTL: true, - accessTokenMaxTTL: true, - accessTokenNumUsesLimit: true, - accessTokenTrustedIps: true, - createdAt: true, - updatedAt: true, - identityId: true, - kubernetesHost: true, - allowedNamespaces: true, - allowedNames: true, - allowedAudience: true +const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.omit({ + encryptedCaCert: true, + caCertIV: true, + caCertTag: true, + encryptedTokenReviewerJwt: true, + tokenReviewerJwtIV: true, + tokenReviewerJwtTag: true }).extend({ caCert: z.string(), tokenReviewerJwt: z.string() diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 7ce0b05b7..431ed3f4f 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -12,20 +12,10 @@ import { validateOidcBoundClaimsField } from "@app/services/identity-oidc-auth/identity-oidc-auth-validators"; -const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({ - id: true, - accessTokenTTL: true, - accessTokenMaxTTL: true, - accessTokenNumUsesLimit: true, - accessTokenTrustedIps: true, - identityId: true, - oidcDiscoveryUrl: true, - boundIssuer: true, - boundAudiences: true, - boundClaims: true, - boundSubject: true, - createdAt: true, - updatedAt: true +const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.omit({ + encryptedCaCert: true, + caCertIV: true, + caCertTag: true }).extend({ caCert: z.string() }); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index a5677894d..4508a255d 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -3,21 +3,28 @@ import axios, { AxiosError } from "axios"; import https from "https"; import jwt from "jsonwebtoken"; -import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; +import { + decryptSymmetric, + encryptSymmetric, + generateAsymmetricKeyPair, + generateSymmetricKey, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; +import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; -import { TKmsServiceFactory } from "../kms/kms-service"; -import { KmsDataKey } from "../kms/kms-types"; import { TIdentityKubernetesAuthDALFactory } from "./identity-kubernetes-auth-dal"; import { extractK8sUsername } from "./identity-kubernetes-auth-fns"; import { @@ -36,9 +43,9 @@ type TIdentityKubernetesAuthServiceFactoryDep = { >; identityAccessTokenDAL: Pick; identityOrgMembershipDAL: Pick; + orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; - kmsService: Pick; }; export type TIdentityKubernetesAuthServiceFactory = ReturnType; @@ -47,9 +54,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, + orgBotDAL, permissionService, - licenseService, - kmsService + licenseService }: TIdentityKubernetesAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => { const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); @@ -68,21 +75,42 @@ export const identityKubernetesAuthServiceFactory = ({ }); } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); + const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = + identityKubernetesAuth; + let caCert = ""; - if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { - caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); + if (encryptedCaCert && caCertIV && caCertTag) { + caCert = decryptSymmetric({ + ciphertext: encryptedCaCert, + iv: caCertIV, + tag: caCertTag, + key + }); } let tokenReviewerJwt = ""; - if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { - tokenReviewerJwt = decryptor({ - cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt - }).toString(); + if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { + tokenReviewerJwt = decryptSymmetric({ + ciphertext: encryptedTokenReviewerJwt, + iv: tokenReviewerJwtIV, + tag: tokenReviewerJwtTag, + key + }); } const { data } = await axios @@ -269,25 +297,79 @@ export const identityKubernetesAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.transaction(async (tx) => { + const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); + if (doc) return doc; + + const { privateKey, publicKey } = generateAsymmetricKeyPair(); + const key = generateSymmetricKey(); + const { + ciphertext: encryptedPrivateKey, + iv: privateKeyIV, + tag: privateKeyTag, + encoding: privateKeyKeyEncoding, + algorithm: privateKeyAlgorithm + } = infisicalSymmetricEncypt(privateKey); + const { + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + encoding: symmetricKeyKeyEncoding, + algorithm: symmetricKeyAlgorithm + } = infisicalSymmetricEncypt(key); + + return orgBotDAL.create( + { + name: "Infisical org bot", + publicKey, + privateKeyIV, + encryptedPrivateKey, + symmetricKeyIV, + symmetricKeyTag, + encryptedSymmetricKey, + symmetricKeyAlgorithm, + orgId: identityMembershipOrg.orgId, + privateKeyTag, + privateKeyAlgorithm, + privateKeyKeyEncoding, + symmetricKeyKeyEncoding + }, + tx + ); }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + const { + ciphertext: encryptedTokenReviewerJwt, + iv: tokenReviewerJwtIV, + tag: tokenReviewerJwtTag + } = encryptSymmetric(tokenReviewerJwt, key); + const identityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => { const doc = await identityKubernetesAuthDAL.create( { identityId: identityMembershipOrg.identityId, kubernetesHost, + encryptedCaCert, + caCertIV, + caCertTag, + encryptedTokenReviewerJwt, + tokenReviewerJwtIV, + tokenReviewerJwtTag, allowedNamespaces, allowedNames, allowedAudience, accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), - encryptedKubernetesTokenReviewerJwt: encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob, - encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob + accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) }, tx ); @@ -373,34 +455,61 @@ export const identityKubernetesAuthServiceFactory = ({ : undefined }; - const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, + name: "OrgBotNotFound" + }); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); if (caCert !== undefined) { - updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; + const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + updateQuery.encryptedCaCert = encryptedCACert; + updateQuery.caCertIV = caCertIV; + updateQuery.caCertTag = caCertTag; } if (tokenReviewerJwt !== undefined) { - updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({ - plainText: Buffer.from(tokenReviewerJwt) - }).cipherTextBlob; + const { + ciphertext: encryptedTokenReviewerJwt, + iv: tokenReviewerJwtIV, + tag: tokenReviewerJwtTag + } = encryptSymmetric(tokenReviewerJwt, key); + updateQuery.encryptedTokenReviewerJwt = encryptedTokenReviewerJwt; + updateQuery.tokenReviewerJwtIV = tokenReviewerJwtIV; + updateQuery.tokenReviewerJwtTag = tokenReviewerJwtTag; } const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery); - const updatedCACert = updatedKubernetesAuth.encryptedKubernetesCaCertificate - ? decryptor({ - cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesCaCertificate - }).toString() - : ""; + const updatedCACert = + updatedKubernetesAuth.encryptedCaCert && updatedKubernetesAuth.caCertIV && updatedKubernetesAuth.caCertTag + ? decryptSymmetric({ + ciphertext: updatedKubernetesAuth.encryptedCaCert, + iv: updatedKubernetesAuth.caCertIV, + tag: updatedKubernetesAuth.caCertTag, + key + }) + : ""; - const updatedTokenReviewerJwt = updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt - ? decryptor({ - cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt - }).toString() - : ""; + const updatedTokenReviewerJwt = + updatedKubernetesAuth.encryptedTokenReviewerJwt && + updatedKubernetesAuth.tokenReviewerJwtIV && + updatedKubernetesAuth.tokenReviewerJwtTag + ? decryptSymmetric({ + ciphertext: updatedKubernetesAuth.encryptedTokenReviewerJwt, + iv: updatedKubernetesAuth.tokenReviewerJwtIV, + tag: updatedKubernetesAuth.tokenReviewerJwtTag, + key + }) + : ""; return { ...updatedKubernetesAuth, @@ -436,21 +545,41 @@ export const identityKubernetesAuthServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) + throw new NotFoundError({ + message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, + name: "OrgBotNotFound" + }); + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); + const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = + identityKubernetesAuth; + let caCert = ""; - if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { - caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); + if (encryptedCaCert && caCertIV && caCertTag) { + caCert = decryptSymmetric({ + ciphertext: encryptedCaCert, + iv: caCertIV, + tag: caCertTag, + key + }); } let tokenReviewerJwt = ""; - if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { - tokenReviewerJwt = decryptor({ - cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt - }).toString(); + if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { + tokenReviewerJwt = decryptSymmetric({ + ciphertext: encryptedTokenReviewerJwt, + iv: tokenReviewerJwtIV, + tag: tokenReviewerJwtTag, + key + }); } return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId }; diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index ff7256a9c..a1dbed46b 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -4,12 +4,20 @@ import https from "https"; import jwt from "jsonwebtoken"; import { JwksClient } from "jwks-rsa"; -import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, SecretKeyEncoding, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; +import { generateAsymmetricKeyPair } from "@app/lib/crypto"; +import { + decryptSymmetric, + encryptSymmetric, + generateSymmetricKey, + infisicalSymmetricDecrypt, + infisicalSymmetricEncypt +} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -17,8 +25,7 @@ import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; -import { TKmsServiceFactory } from "../kms/kms-service"; -import { KmsDataKey } from "../kms/kms-types"; +import { TOrgBotDALFactory } from "../org/org-bot-dal"; import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal"; import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns"; import { @@ -35,7 +42,7 @@ type TIdentityOidcAuthServiceFactoryDep = { identityAccessTokenDAL: Pick; permissionService: Pick; licenseService: Pick; - kmsService: Pick; + orgBotDAL: Pick; }; export type TIdentityOidcAuthServiceFactory = ReturnType; @@ -46,7 +53,7 @@ export const identityOidcAuthServiceFactory = ({ permissionService, licenseService, identityAccessTokenDAL, - kmsService + orgBotDAL }: TIdentityOidcAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: oidcJwt }: TLoginOidcAuthDTO) => { const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); @@ -63,14 +70,31 @@ export const identityOidcAuthServiceFactory = ({ }); } - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); + const { encryptedCaCert, caCertIV, caCertTag } = identityOidcAuth; + let caCert = ""; - if (identityOidcAuth.encryptedCaCertificate) { - caCert = decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString(); + if (encryptedCaCert && caCertIV && caCertTag) { + caCert = decryptSymmetric({ + ciphertext: encryptedCaCert, + iv: caCertIV, + tag: caCertTag, + key + }); } const requestAgent = new https.Agent({ ca: caCert, rejectUnauthorized: !!caCert }); @@ -240,17 +264,64 @@ export const identityOidcAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const { encryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.transaction(async (tx) => { + const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); + if (doc) return doc; + + const { privateKey, publicKey } = generateAsymmetricKeyPair(); + const key = generateSymmetricKey(); + const { + ciphertext: encryptedPrivateKey, + iv: privateKeyIV, + tag: privateKeyTag, + encoding: privateKeyKeyEncoding, + algorithm: privateKeyAlgorithm + } = infisicalSymmetricEncypt(privateKey); + const { + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + encoding: symmetricKeyKeyEncoding, + algorithm: symmetricKeyAlgorithm + } = infisicalSymmetricEncypt(key); + + return orgBotDAL.create( + { + name: "Infisical org bot", + publicKey, + privateKeyIV, + encryptedPrivateKey, + symmetricKeyIV, + symmetricKeyTag, + encryptedSymmetricKey, + symmetricKeyAlgorithm, + orgId: identityMembershipOrg.orgId, + privateKeyTag, + privateKeyAlgorithm, + privateKeyKeyEncoding, + symmetricKeyKeyEncoding + }, + tx + ); }); + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + const identityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => { const doc = await identityOidcAuthDAL.create( { identityId: identityMembershipOrg.identityId, oidcDiscoveryUrl, - encryptedCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, + encryptedCaCert, + caCertIV, + caCertTag, boundIssuer, boundAudiences, boundClaims, @@ -344,19 +415,38 @@ export const identityOidcAuthServiceFactory = ({ : undefined }; - const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); if (caCert !== undefined) { - updateQuery.encryptedCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; + const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); + updateQuery.encryptedCaCert = encryptedCACert; + updateQuery.caCertIV = caCertIV; + updateQuery.caCertTag = caCertTag; } const updatedOidcAuth = await identityOidcAuthDAL.updateById(identityOidcAuth.id, updateQuery); - const updatedCACert = updatedOidcAuth.encryptedCaCertificate - ? decryptor({ cipherTextBlob: updatedOidcAuth.encryptedCaCertificate }).toString() - : ""; + const updatedCACert = + updatedOidcAuth.encryptedCaCert && updatedOidcAuth.caCertIV && updatedOidcAuth.caCertTag + ? decryptSymmetric({ + ciphertext: updatedOidcAuth.encryptedCaCert, + iv: updatedOidcAuth.caCertIV, + tag: updatedOidcAuth.caCertTag, + key + }) + : ""; return { ...updatedOidcAuth, @@ -386,14 +476,27 @@ export const identityOidcAuthServiceFactory = ({ const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); - const { decryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: identityMembershipOrg.orgId + const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); + if (!orgBot) { + throw new NotFoundError({ + message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, + name: "OrgBotNotFound" + }); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: orgBot.encryptedSymmetricKey, + iv: orgBot.symmetricKeyIV, + tag: orgBot.symmetricKeyTag, + keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding }); - const caCert = identityOidcAuth.encryptedCaCertificate - ? decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString() - : ""; + const caCert = decryptSymmetric({ + ciphertext: identityOidcAuth.encryptedCaCert, + iv: identityOidcAuth.caCertIV, + tag: identityOidcAuth.caCertTag, + key + }); return { ...identityOidcAuth, orgId: identityMembershipOrg.orgId, caCert }; }; diff --git a/backend/src/services/kms/kms-root-config-dal.ts b/backend/src/services/kms/kms-root-config-dal.ts index 8745d286e..f448e2df8 100644 --- a/backend/src/services/kms/kms-root-config-dal.ts +++ b/backend/src/services/kms/kms-root-config-dal.ts @@ -1,24 +1,10 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; -import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; -import { Knex } from "knex"; export type TKmsRootConfigDALFactory = ReturnType; export const kmsRootConfigDALFactory = (db: TDbClient) => { const kmsOrm = ormify(db, TableName.KmsServerRootConfig); - - const findById = async (id: string, tx?: Knex) => { - try { - const result = await (tx || db)(TableName.KmsServerRootConfig) - .where({ id } as never) - .first("*"); - return result; - } catch (error) { - throw new DatabaseError({ error, name: "Find by id" }); - } - }; - - return { ...kmsOrm, findById }; + return kmsOrm; }; diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index babba4cb2..c41783860 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -12,8 +12,8 @@ import { TExternalKmsProviderFns } from "@app/ee/services/external-kms/providers/model"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; -import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; -import { TEnvConfig } from "@app/lib/config/env"; +import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; +import { getConfig } from "@app/lib/config/env"; import { randomSecureBytes } from "@app/lib/crypto"; import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; import { generateHash } from "@app/lib/crypto/encryption"; @@ -44,22 +44,23 @@ type TKmsServiceFactoryDep = { kmsDAL: TKmsKeyDALFactory; projectDAL: Pick; orgDAL: Pick; - kmsRootConfigDAL: Pick; + kmsRootConfigDAL: Pick; keyStore: Pick; internalKmsDAL: Pick; hsmService: THsmServiceFactory; - envConfig: Pick; }; export type TKmsServiceFactory = ReturnType; +const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key"; +const KMS_ROOT_CREATION_WAIT_TIME = 10; + // akhilmhdh: Don't edit this value. This is measured for blob concatination in kms const KMS_VERSION = "v01"; const KMS_VERSION_BLOB_LENGTH = 3; const KmsSanitizedSchema = KmsKeysSchema.extend({ isExternal: z.boolean() }); export const kmsServiceFactory = ({ - envConfig, kmsDAL, kmsRootConfigDAL, keyStore, @@ -472,8 +473,7 @@ export const kmsServiceFactory = ({ } const kmsDecryptor = await decryptWithKmsKey({ - kmsId: kmsKeyId, - tx: trx + kmsId: kmsKeyId }); return kmsDecryptor({ @@ -635,8 +635,10 @@ export const kmsServiceFactory = ({ }; const $getBasicEncryptionKey = () => { - const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY; - const isBase64 = !envConfig.ENCRYPTION_KEY; + const appCfg = getConfig(); + + const encryptionKey = appCfg.ENCRYPTION_KEY || appCfg.ROOT_ENCRYPTION_KEY; + const isBase64 = !appCfg.ENCRYPTION_KEY; if (!encryptionKey) throw new Error( "Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?" @@ -872,33 +874,54 @@ export const kmsServiceFactory = ({ return { id, name, orgId, isExternal }; }; + // akhilmhdh: a copy of this is made in migrations/utils/kms const startService = async () => { - const kmsRootConfig = await kmsRootConfigDAL.transaction(async (tx) => { - await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.KmsRootKeyInit]); - // check if KMS root key was already generated and saved in DB - const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); - if (existingRootConfig) return existingRootConfig; - - logger.info("KMS: Generating new ROOT Key"); - const newRootKey = randomSecureBytes(32); - const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { - logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); - throw err; + const lock = await keyStore.acquireLock([`KMS_ROOT_CFG_LOCK`], 3000, { retryCount: 3 }).catch(() => null); + if (!lock) { + await keyStore.waitTillReady({ + key: KMS_ROOT_CREATION_WAIT_KEY, + keyCheckCb: (val) => val === "true", + waitingCb: () => logger.info("KMS. Waiting for leader to finish creation of KMS Root Key") }); + } - const newRootConfig = await kmsRootConfigDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID, - encryptedRootKey, - encryptionStrategy: RootKeyEncryptionStrategy.Software - }); - return newRootConfig; + // check if KMS root key was already generated and saved in DB + const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); + + // case 1: a root key already exists in the DB + if (kmsRootConfig) { + if (lock) await lock.release(); + logger.info(`KMS: Encrypted ROOT Key found from DB. Decrypting. [strategy=${kmsRootConfig.encryptionStrategy}]`); + + const decryptedRootKey = await $decryptRootKey(kmsRootConfig); + + // set the flag so that other instance nodes can start + await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); + logger.info("KMS: Loading ROOT Key into Memory."); + ROOT_ENCRYPTION_KEY = decryptedRootKey; + return; + } + + // case 2: no config is found, so we create a new root key with basic encryption + logger.info("KMS: Generating new ROOT Key"); + const newRootKey = randomSecureBytes(32); + const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { + logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); + throw err; }); - const decryptedRootKey = await $decryptRootKey(kmsRootConfig); + await kmsRootConfigDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: KMS_ROOT_CONFIG_UUID, + encryptedRootKey, + encryptionStrategy: RootKeyEncryptionStrategy.Software + }); - logger.info("KMS: Loading ROOT Key into Memory."); - ROOT_ENCRYPTION_KEY = decryptedRootKey; + // set the flag so that other instance nodes can start + await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); + logger.info("KMS: Saved and loaded ROOT Key into memory"); + if (lock) await lock.release(); + ROOT_ENCRYPTION_KEY = newRootKey; }; const updateEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => { diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 1d695a5ff..09bc6460d 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -9,7 +9,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; import { ActorAuthMethod } from "../auth/auth-type"; import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal"; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 83a59b6af..2c6b8e2da 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -4,17 +4,8 @@ import { ProjectType, TProjectKeys } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; - -enum KmsType { - External = "external", - Internal = "internal" -} - -enum CaStatus { - ACTIVE = "active", - DISABLED = "disabled", - PENDING_CERTIFICATE = "pending-certificate" -} +import { CaStatus } from "../certificate-authority/certificate-authority-types"; +import { KmsType } from "../kms/kms-types"; export enum ProjectFilterType { ID = "id", diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index 00b0e7da8..dc973c0b1 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -1488,18 +1488,7 @@ export const secretQueueFactory = ({ }); queueService.start(QueueName.SecretWebhook, async (job) => { - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: job.data.projectId - }); - - await fnTriggerWebhook({ - ...job.data, - projectEnvDAL, - webhookDAL, - projectDAL, - secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString() - }); + await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL }); }); return { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 9a6075423..b0fdd9c5c 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; @@ -87,21 +87,17 @@ export const superAdminServiceFactory = ({ // reset on initialized await keyStore.deleteItem(ADMIN_CONFIG_KEY); - const serverCfg = await serverCfgDAL.transaction(async (tx) => { - await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.SuperAdminInit]); - const serverCfgInDB = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); - if (serverCfgInDB) return serverCfgInDB; + const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); + if (serverCfg) return; - const newCfg = await serverCfgDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: ADMIN_CONFIG_DB_UUID, - initialized: false, - allowSignUp: true, - defaultAuthOrgId: null - }); - return newCfg; + const newCfg = await serverCfgDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: ADMIN_CONFIG_DB_UUID, + initialized: false, + allowSignUp: true, + defaultAuthOrgId: null }); - return serverCfg; + return newCfg; }; const updateServerCfg = async ( diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index e46f9db2a..58f51f880 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -3,8 +3,9 @@ import crypto from "node:crypto"; import { AxiosError } from "axios"; import picomatch from "picomatch"; -import { TWebhooks } from "@app/db/schemas"; +import { SecretKeyEncoding, TWebhooks } from "@app/db/schemas"; import { request } from "@app/lib/config/request"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -15,14 +16,28 @@ import { WebhookType } from "./webhook-types"; const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000; -export const decryptWebhookDetails = (webhook: TWebhooks, decryptor: (value: Buffer) => string) => { - const { encryptedPassKey, encryptedUrl } = webhook; - - const decryptedUrl = decryptor(encryptedUrl); +export const decryptWebhookDetails = (webhook: TWebhooks) => { + const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook; let decryptedSecretKey = ""; - if (encryptedPassKey) { - decryptedSecretKey = decryptor(encryptedPassKey); + let decryptedUrl = url; + + if (encryptedSecretKey) { + decryptedSecretKey = infisicalSymmetricDecrypt({ + keyEncoding: keyEncoding as SecretKeyEncoding, + ciphertext: encryptedSecretKey, + iv: iv as string, + tag: tag as string + }); + } + + if (urlCipherText) { + decryptedUrl = infisicalSymmetricDecrypt({ + keyEncoding: keyEncoding as SecretKeyEncoding, + ciphertext: urlCipherText, + iv: urlIV as string, + tag: urlTag as string + }); } return { @@ -31,14 +46,10 @@ export const decryptWebhookDetails = (webhook: TWebhooks, decryptor: (value: Buf }; }; -export const triggerWebhookRequest = async ( - webhook: TWebhooks, - decryptor: (value: Buffer) => string, - data: Record -) => { +export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record) => { const headers: Record = {}; const payload = { ...data, timestamp: Date.now() }; - const { secretKey, url } = decryptWebhookDetails(webhook, decryptor); + const { secretKey, url } = decryptWebhookDetails(webhook); if (secretKey) { const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); @@ -113,7 +124,6 @@ export type TFnTriggerWebhookDTO = { webhookDAL: Pick; projectEnvDAL: Pick; projectDAL: Pick; - secretManagerDecryptor: (value: Buffer) => string; }; // this is reusable function @@ -124,8 +134,7 @@ export const fnTriggerWebhook = async ({ projectId, webhookDAL, projectEnvDAL, - projectDAL, - secretManagerDecryptor + projectDAL }: TFnTriggerWebhookDTO) => { const webhooks = await webhookDAL.findAllWebhooks(projectId, environment); const toBeTriggeredHooks = webhooks.filter( @@ -139,7 +148,6 @@ export const fnTriggerWebhook = async ({ toBeTriggeredHooks.map((hook) => triggerWebhookRequest( hook, - secretManagerDecryptor, getWebhookPayload("secrets.modified", { workspaceName: project.name, workspaceId: projectId, diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index bb078e0f1..26136aaf6 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -3,10 +3,9 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; +import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; -import { TKmsServiceFactory } from "../kms/kms-service"; -import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TWebhookDALFactory } from "./webhook-dal"; @@ -24,7 +23,6 @@ type TWebhookServiceFactoryDep = { projectEnvDAL: TProjectEnvDALFactory; projectDAL: Pick; permissionService: Pick; - kmsService: Pick; }; export type TWebhookServiceFactory = ReturnType; @@ -33,8 +31,7 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService, - projectDAL, - kmsService + projectDAL }: TWebhookServiceFactoryDep) => { const createWebhook = async ({ actor, @@ -63,20 +60,30 @@ export const webhookServiceFactory = ({ message: `Environment with slug '${environment}' in project with ID '${projectId}' not found` }); - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); const insertDoc: TWebhooksInsert = { + url: "", // deprecated - we are moving away from plaintext URLs envId: env.id, isDisabled: false, secretPath: secretPath || "/", - type, - encryptedUrl: secretManagerEncryptor({ plainText: Buffer.from(webhookUrl) }).cipherTextBlob + type }; if (webhookSecretKey) { - insertDoc.encryptedPassKey = secretManagerEncryptor({ plainText: Buffer.from(webhookSecretKey) }).cipherTextBlob; + const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey); + insertDoc.encryptedSecretKey = ciphertext; + insertDoc.iv = iv; + insertDoc.tag = tag; + insertDoc.algorithm = algorithm; + insertDoc.keyEncoding = encoding; + } + + if (webhookUrl) { + const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookUrl); + insertDoc.urlCipherText = ciphertext; + insertDoc.urlIV = iv; + insertDoc.urlTag = tag; + insertDoc.algorithm = algorithm; + insertDoc.keyEncoding = encoding; } const webhook = await webhookDAL.create(insertDoc); @@ -133,17 +140,12 @@ export const webhookServiceFactory = ({ }); const project = await projectDAL.findById(webhook.projectId); - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: project.id - }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; try { await triggerWebhookRequest( webhook, - (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString(), getWebhookPayload("test", { workspaceName: project.name, workspaceId: webhook.projectId, @@ -183,13 +185,8 @@ export const webhookServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); const webhooks = await webhookDAL.findAllWebhooks(projectId, environment, secretPath); - const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }); - return webhooks.map((w) => { - const { url } = decryptWebhookDetails(w, (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()); + const { url } = decryptWebhookDetails(w); return { ...w, url diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 90165acbe..fcf508922 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,8 +1,7 @@ { "ts-node": { // Do not forget to `npm i -D tsconfig-paths` - "require": ["tsconfig-paths/register"], - "files": true + "require": ["tsconfig-paths/register"] }, "compilerOptions": { "target": "esnext", @@ -20,7 +19,6 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "Node", - "allowSyntheticDefaultImports": true, "skipLibCheck": true, "baseUrl": ".", "paths": { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 680a655d8..40d17c1b0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -56,6 +56,20 @@ services: POSTGRES_USER: infisical POSTGRES_DB: infisical-test + db-migration: + container_name: infisical-db-migration + depends_on: + - db + build: + context: ./backend + dockerfile: Dockerfile.dev + env_file: .env + environment: + - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable + command: npm run migration:latest + volumes: + - ./backend/src:/app/src + backend: container_name: infisical-dev-api build: @@ -66,6 +80,8 @@ services: condition: service_started redis: condition: service_started + db-migration: + condition: service_completed_successfully env_file: - .env ports: @@ -176,7 +192,7 @@ services: depends_on: - openldap profiles: [ldap] - + keycloak: image: quay.io/keycloak/keycloak:26.1.0 restart: always @@ -186,7 +202,7 @@ services: command: start-dev ports: - 8088:8080 - profiles: [sso] + profiles: [ sso ] volumes: postgres-data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d3526d841..77a1e04ab 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,6 +1,18 @@ version: "3" services: + db-migration: + container_name: infisical-db-migration + depends_on: + db: + condition: service_healthy + image: infisical/infisical:latest-postgres + env_file: .env + command: npm run migration:latest + pull_policy: always + networks: + - infisical + backend: container_name: infisical-backend restart: unless-stopped @@ -9,6 +21,8 @@ services: condition: service_healthy redis: condition: service_started + db-migration: + condition: service_completed_successfully image: infisical/infisical:latest-postgres pull_policy: always env_file: .env @@ -55,5 +69,4 @@ volumes: driver: local networks: - infisical: - + infisical: \ No newline at end of file From f44888afa2d4e17aebe5c2b7267b6002dbbc57a6 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 11 Feb 2025 21:08:20 +0400 Subject: [PATCH 32/41] Update secret-router.ts --- backend/src/server/routes/v3/secret-router.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 6f1b420df..af1747ed6 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -36,11 +36,12 @@ const SecretReferenceNodeTree: z.ZodType = SecretReference children: z.lazy(() => SecretReferenceNodeTree.array()) }); -const SecretNameSchema = z - .string() - .trim() - .min(1) - .refine((el) => !el.includes(" "), "Secret name cannot contain spaces."); +const BaseSecretNameSchema = z.string().trim().min(1); + +const SecretNameSchema = BaseSecretNameSchema.refine( + (el) => !el.includes(" "), + "Secret name cannot contain spaces." +).refine((el) => !el.includes(":"), "Secret name cannot contain colon."); export const registerSecretRouter = async (server: FastifyZodProvider) => { server.route({ @@ -618,7 +619,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } ], params: z.object({ - secretName: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName) + secretName: BaseSecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName) }), body: z.object({ workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId), From 30284e3458f68a7005a27095b6b421042616fa87 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 11 Feb 2025 23:58:20 +0400 Subject: [PATCH 33/41] Update identity-aws-auth-validators.ts --- .../services/identity-aws-auth/identity-aws-auth-validators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d0c255e9b..2cc736f1e 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 @@ -1,7 +1,7 @@ import { z } from "zod"; const twelveDigitRegex = /^\d{12}$/; -const arnRegex = /^arn:aws:iam::\d{12}:(user\/[\w+=,.@/-]+|role\/[\w+=,.@/-]+|\*)$/; +const arnRegex = /^arn:aws:iam::\d{12}:(user\/[a-zA-Z0-9_.@+*/-]+|role\/[a-zA-Z0-9_.@+*/-]+|\*)$/; export const validateAccountIds = z .string() From 4fdfdc1a397c7fada842da9afc45df065e3b86cf Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 11 Feb 2025 16:14:13 -0800 Subject: [PATCH 34/41] improvements: ui improvements & add users to org from project member invite modal --- frontend/public/locales/en/translations.json | 4 +- frontend/public/lotties/settings-cog.json | 1 + .../secret-syncs/DeleteSecretSyncModal.tsx | 5 +- .../SecretSyncImportSecretsModal.tsx | 5 +- .../SecretSyncRemoveSecretsModal.tsx | 5 +- .../secret-syncs/forms/EditSecretSyncForm.tsx | 3 +- .../forms/SecretSyncConnectionField.tsx | 8 +- .../components/v2/PageHeader/PageHeader.tsx | 8 +- .../src/hooks/api/secretSyncs/mutations.tsx | 23 +- .../src/hooks/api/secretSyncs/queries.tsx | 20 +- .../src/hooks/api/secretSyncs/types/index.ts | 5 + frontend/src/hooks/api/workspace/queries.tsx | 2 +- .../OrganizationLayout/OrganizationLayout.tsx | 7 + .../MinimizedOrgSidebar.tsx | 11 +- .../AppConnectionsPage/AppConnectionsPage.tsx | 90 ++++++++ .../components/AddAppConnectionModal.tsx | 0 .../AppConnectionForm/AppConnectionForm.tsx | 0 .../AppConnectionForm/AwsConnectionForm.tsx | 0 .../AzureAppConfigurationConnectionForm.tsx | 0 .../AzureKeyVaultConnectionForm.tsx | 0 .../AppConnectionForm/GcpConnectionForm.tsx | 0 .../GenericAppConnectionFields.tsx | 0 .../GitHubConnectionForm.tsx | 0 .../components/AppConnectionForm/index.ts | 0 .../components/AppConnectionHeader.tsx | 0 .../components/AppConnectionList.tsx | 0 .../components/AppConnectionRow.tsx | 0 .../components/AppConnectionsTable.tsx | 2 +- .../components/DeleteAppConnectionModal.tsx | 0 .../EditAppConnectionCredentialsModal.tsx | 0 .../EditAppConnectionDetailsModal.tsx | 0 .../AppConnectionsPage}/components/index.tsx | 0 .../AppConnectionsPage/route.tsx | 23 ++ .../OauthCallbackPage/OauthCallbackPage.tsx | 8 +- .../AppConnectionsTab/AppConnectionsTab.tsx | 77 ------- .../components/AppConnectionsTab/index.tsx | 1 - .../components/OrgTabGroup/OrgTabGroup.tsx | 2 - .../MembersTab/components/AddMemberModal.tsx | 210 +++++++++++------- .../IntegrationsDetailsByIDPage/route.tsx | 7 +- .../IntegrationsListPage.tsx | 2 +- .../SecretSyncTable/SecretSyncsTable.tsx | 6 +- .../IntegrationsListPage/route.tsx | 51 ++++- .../components/SecretSyncActionTriggers.tsx | 6 +- .../SecretSyncDetailsByIDPage/route.tsx | 7 +- .../AwsParameterStoreAuthorizePage/route.tsx | 7 +- .../AwsParamterStoreConfigurePage.tsx | 4 + .../AwsParameterStoreConfigurePage/route.tsx | 7 +- .../AwsSecretManagerAuthorizePage/route.tsx | 7 +- .../AwsSecretManagerConfigurePage.tsx | 4 + .../AwsSecretManagerConfigurePage/route.tsx | 7 +- .../AzureAppConfigurationConfigurePage.tsx | 4 + .../route.tsx | 7 +- .../route.tsx | 7 +- .../AzureDevopsAuthorizePage/route.tsx | 7 +- .../AzureDevopsConfigurePage.tsx | 4 + .../AzureDevopsConfigurePage/route.tsx | 7 +- .../AzureKeyVaultAuthorizePage/route.tsx | 7 +- .../AzureKeyVaultConfigurePage.tsx | 4 + .../AzureKeyVaultConfigurePage/route.tsx | 7 +- .../AzureKeyVaultOauthCallbackPage/route.tsx | 7 +- .../BitbucketConfigurePage.tsx | 4 + .../BitbucketConfigurePage/route.tsx | 7 +- .../BitbucketOauthCallbackPage/route.tsx | 7 +- .../ChecklyAuthorizePage/route.tsx | 7 +- .../ChecklyConfigurePage.tsx | 4 + .../ChecklyConfigurePage/route.tsx | 7 +- .../CircleCIAuthorizePage/route.tsx | 7 +- .../CircleCIConfigurePage.tsx | 4 + .../CircleCIConfigurePage/route.tsx | 7 +- .../Cloud66AuthorizePage/route.tsx | 7 +- .../Cloud66ConfigurePage.tsx | 4 + .../Cloud66ConfigurePage/route.tsx | 7 +- .../CloudflarePagesAuthorizePage/route.tsx | 7 +- .../CloudflarePagesConfigurePage.tsx | 4 + .../CloudflarePagesConfigurePage/route.tsx | 7 +- .../CloudflareWorkersAuthorizePage/route.tsx | 7 +- .../CloudflareWorkersConfigurePage.tsx | 4 + .../CloudflareWorkersConfigurePage/route.tsx | 7 +- .../CodefreshAuthorizePage/route.tsx | 7 +- .../CodefreshConfigurePage.tsx | 4 + .../CodefreshConfigurePage/route.tsx | 7 +- .../DatabricksAuthorizePage/route.tsx | 7 +- .../DatabricksConfigurePage.tsx | 4 + .../DatabricksConfigurePage/route.tsx | 7 +- .../route.tsx | 7 +- .../DigitalOceanAppPlatformConfigurePage.tsx | 4 + .../route.tsx | 7 +- .../integrations/FlyioAuthorizePage/route.tsx | 7 +- .../FlyioConfigurePage/FlyioConfigurePage.tsx | 4 + .../integrations/FlyioConfigurePage/route.tsx | 7 +- .../GcpSecretManagerAuthorizePage/route.tsx | 7 +- .../GcpSecretManagerConfigurePage.tsx | 4 + .../GcpSecretManagerConfigurePage/route.tsx | 7 +- .../route.tsx | 7 +- .../GithubAuthorizePage/route.tsx | 7 +- .../GithubConfigurePage.tsx | 4 + .../GithubConfigurePage/route.tsx | 7 +- .../GithubOauthCallbackPage/route.tsx | 7 +- .../GitlabAuthorizePage/route.tsx | 7 +- .../GitlabConfigurePage.tsx | 4 + .../GitlabConfigurePage/route.tsx | 7 +- .../GitlabOauthCallbackPage/route.tsx | 7 +- .../HashicorpVaultAuthorizePage/route.tsx | 7 +- .../HashicorpVaultConfigurePage.tsx | 4 + .../HashicorpVaultConfigurePage/route.tsx | 7 +- .../HasuraCloudAuthorizePage/route.tsx | 7 +- .../HasuraCloudConfigurePage.tsx | 4 + .../HasuraCloudConfigurePage/route.tsx | 7 +- .../HerokuConfigurePage.tsx | 4 + .../HerokuConfigurePage/route.tsx | 7 +- .../HerokuOauthCallbackPage/route.tsx | 7 +- .../LaravelForgeAuthorizePage/route.tsx | 7 +- .../LaravelForgeConfigurePage.tsx | 4 + .../LaravelForgeConfigurePage/route.tsx | 7 +- .../NetlifyConfigurePage.tsx | 4 + .../NetlifyConfigurePage/route.tsx | 7 +- .../NetlifyOauthCallbackPage/route.tsx | 7 +- .../NorthflankAuthorizePage/route.tsx | 7 +- .../NorthflankConfigurePage.tsx | 4 + .../NorthflankConfigurePage/route.tsx | 7 +- .../OctopusDeployAuthorizePage/route.tsx | 7 +- .../OctopusDeployConfigurePage.tsx | 4 + .../OctopusDeployConfigurePage/route.tsx | 7 +- .../QoveryAuthorizePage/route.tsx | 7 +- .../QoveryConfigurePage.tsx | 4 + .../QoveryConfigurePage/route.tsx | 7 +- .../RailwayAuthorizePage/route.tsx | 7 +- .../RailwayConfigurePage.tsx | 4 + .../RailwayConfigurePage/route.tsx | 7 +- .../RenderAuthorizePage/route.tsx | 7 +- .../RenderConfigurePage.tsx | 4 + .../RenderConfigurePage/route.tsx | 7 +- .../RundeckAuthorizePage/route.tsx | 7 +- .../RundeckConfigurePage.tsx | 4 + .../RundeckConfigurePage/route.tsx | 7 +- .../SelectIntegrationAuthPage/route.tsx | 7 +- .../SupabaseAuthorizePage/route.tsx | 7 +- .../SupabaseConfigurePage.tsx | 4 + .../SupabaseConfigurePage/route.tsx | 7 +- .../TeamcityAuthorizePage/route.tsx | 7 +- .../TeamcityConfigurePage.tsx | 4 + .../TeamcityConfigurePage/route.tsx | 7 +- .../TerraformCloudAuthorizePage/route.tsx | 7 +- .../TerraformCloudConfigurePage.tsx | 4 + .../TerraformCloudConfigurePage/route.tsx | 7 +- .../TravisCIAuthorizePage/route.tsx | 7 +- .../TravisCIConfigurePage.tsx | 4 + .../TravisCIConfigurePage/route.tsx | 7 +- .../VercelConfigurePage.tsx | 4 + .../VercelConfigurePage/route.tsx | 7 +- .../VercelOauthCallbackPage/route.tsx | 7 +- .../WindmillAuthorizePage/route.tsx | 7 +- .../WindmillConfigurePage.tsx | 4 + .../WindmillConfigurePage/route.tsx | 7 +- frontend/src/routeTree.gen.ts | 97 +++++++- frontend/src/routes.ts | 12 +- 156 files changed, 1059 insertions(+), 314 deletions(-) create mode 100644 frontend/public/lotties/settings-cog.json create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AddAppConnectionModal.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/AppConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/AwsConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/GcpConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/GenericAppConnectionFields.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/GitHubConnectionForm.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionForm/index.ts (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionHeader.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionList.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionRow.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/AppConnectionsTable.tsx (99%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/DeleteAppConnectionModal.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/EditAppConnectionCredentialsModal.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/EditAppConnectionDetailsModal.tsx (100%) rename frontend/src/pages/organization/{SettingsPage/components/AppConnectionsTab => AppConnections/AppConnectionsPage}/components/index.tsx (100%) create mode 100644 frontend/src/pages/organization/AppConnections/AppConnectionsPage/route.tsx delete mode 100644 frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/AppConnectionsTab.tsx delete mode 100644 frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/index.tsx diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index 5c68e440d..8b6af1169 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -222,10 +222,10 @@ "org-members-description": "Manage members of your organization. These users could afterwards be formed into projects.", "search-members": "Search members...", "add-dialog": { - "add-member-to-project": "Add a member to your project", + "add-member-to-project": "Add users to your project", "already-all-invited": "All the users in your organization are already invited.", "add-user-org-first": "Add more users to the organization first.", - "user-will-email": "The user will receive an email with the instructions.", + "user-will-email": "Users will receive an email with instructions to gain access.", "looking-add": "<0>If you are looking to add users to your org,<1>click here", "add-user-to-org": "Add Users to Organization" } diff --git a/frontend/public/lotties/settings-cog.json b/frontend/public/lotties/settings-cog.json new file mode 100644 index 000000000..fdf1333c7 --- /dev/null +++ b/frontend/public/lotties/settings-cog.json @@ -0,0 +1 @@ +{"v":"5.12.1","fr":60,"ip":0,"op":60,"w":500,"h":500,"nm":"system-regular-63-settings-cog","ddd":0,"assets":[{"id":"comp_1","nm":"hover-cog-1","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":60,"s":[180]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[65.415,17.27],[65.425,17.27]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-5.279,-1.58],[-4.338,0],[-4.502,1.145],[-2.995,1.872],[-0.218,0.122],[-4.41,7.876],[-0.134,0.213],[-1.581,5.285],[0,4.342],[1.146,4.497],[1.868,2.989],[0.123,0.22],[7.877,4.411],[0.214,0.133],[5.283,1.581],[4.342,0],[4.498,-1.145],[2.992,-1.871],[0.22,-0.123],[4.411,-7.878],[0.134,-0.213],[1.581,-5.283],[0,-4.341],[-1.144,-4.497],[-1.871,-2.993],[-0.123,-0.219],[-7.879,-4.413],[-0.213,-0.132]],"o":[[4.502,1.145],[4.338,0],[5.28,-1.581],[0.212,-0.132],[7.878,-4.413],[0.123,-0.22],[1.868,-2.99],[1.146,-4.495],[0,-4.342],[-1.581,-5.285],[-0.134,-0.214],[-4.411,-7.877],[-0.219,-0.123],[-2.991,-1.87],[-4.497,-1.145],[-4.341,0],[-5.282,1.581],[-0.214,0.133],[-7.876,4.41],[-0.123,0.219],[-1.87,2.992],[-1.145,4.498],[0,4.341],[1.581,5.284],[0.133,0.213],[4.41,7.876],[0.218,0.122],[2.996,1.872]],"v":[[-13.321,50.103],[-0.001,51.829],[13.318,50.104],[25.456,45.04],[26.102,44.658],[44.885,25.874],[45.271,25.224],[50.331,13.088],[52.057,-0.229],[50.331,-13.547],[45.271,-25.682],[44.885,-26.332],[26.103,-45.116],[25.453,-45.5],[13.316,-50.562],[-0.001,-52.287],[-13.319,-50.562],[-25.455,-45.5],[-26.105,-45.115],[-44.888,-26.332],[-45.272,-25.684],[-50.334,-13.548],[-52.059,-0.229],[-50.335,13.087],[-45.271,25.226],[-44.888,25.874],[-26.104,44.658],[-25.458,45.04]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[7.066,0],[7.15,1.852],[0.174,0.051],[7.142,4.396],[7.169,12.665],[1.507,5.085],[0.045,0.174],[0,7.071],[-1.853,7.147],[-0.051,0.173],[-4.395,7.138],[-12.665,7.167],[-5.085,1.507],[-0.174,0.045],[-7.071,0],[-7.146,-1.853],[-0.172,-0.051],[-7.138,-4.395],[-7.169,-12.666],[-1.508,-5.086],[-0.045,-0.174],[0,-7.073],[1.854,-7.145],[0.051,-0.171],[4.395,-7.136],[12.666,-7.17],[5.083,-1.507],[0.176,-0.045]],"o":[[-7.065,0],[-0.175,-0.046],[-5.082,-1.506],[-12.667,-7.17],[-4.395,-7.139],[-0.051,-0.172],[-1.853,-7.146],[0,-7.071],[0.045,-0.175],[1.508,-5.085],[7.17,-12.666],[7.138,-4.396],[0.172,-0.051],[7.147,-1.853],[7.072,0],[0.174,0.045],[5.086,1.508],[12.664,7.168],[4.394,7.134],[0.051,0.172],[1.854,7.146],[0,7.074],[-0.045,0.173],[-1.508,5.086],[-7.168,12.664],[-7.142,4.396],[-0.174,0.051],[-7.149,1.852]],"v":[[-0.001,83.129],[-21.426,80.338],[-21.949,80.193],[-41.716,71.788],[-72.018,41.488],[-80.423,21.717],[-80.567,21.197],[-83.359,-0.229],[-80.567,-21.656],[-80.423,-22.178],[-72.018,-41.946],[-41.719,-72.244],[-21.949,-80.651],[-21.43,-80.795],[-0.001,-83.587],[21.427,-80.795],[21.946,-80.651],[41.717,-72.244],[72.015,-41.945],[80.42,-22.178],[80.563,-21.659],[83.357,-0.229],[80.563,21.2],[80.42,21.717],[72.015,41.487],[41.716,71.787],[21.946,80.193],[21.423,80.338]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-0.007,0.004],[0,0]],"o":[[0,0],[0.006,-0.003]],"v":[[142.26,-118.378],[142.24,-118.367]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,0],[-0.007,-0.004]],"o":[[0.007,0.004],[0,0]],"v":[[-142.23,-118.359],[-142.211,-118.348]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[173.794,64.156],[173.804,64.156]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[0.007,0.003],[0,0]],"o":[[0,0],[-0.006,-0.003]],"v":[[142.209,117.89],[142.228,117.901]],"c":true},"ix":2},"nm":"Path 7","mn":"ADBE Vector Shape - Group","hd":false},{"ind":7,"ty":"sh","ix":8,"ks":{"a":0,"k":{"i":[[0,0],[0.006,-0.003]],"o":[[-0.007,0.003],[0,0]],"v":[[-142.24,117.907],[-142.259,117.918]],"c":true},"ix":2},"nm":"Path 8","mn":"ADBE Vector Shape - Group","hd":false},{"ind":8,"ty":"sh","ix":9,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-6.556,2.028],[-14.196,13.044],[-5.925,-3.438],[0,0],[0,0],[0,0],[-1.534,6.696],[0,9.907],[1.949,8.508],[-5.948,3.436],[0,0],[0,0],[0,0],[5.044,4.635],[17.51,5.416],[0,6.862],[0,0],[0,0],[0,0],[6.556,-2.027],[14.196,-13.046],[5.926,3.438],[0,0],[0,0],[0,0],[1.535,-6.697],[0,-9.912],[-1.949,-8.505],[5.949,-3.437],[0,0],[0,0],[0,0],[-5.046,-4.636],[-17.514,-5.417],[0,-6.862]],"o":[[0,0],[0,0],[0,-6.862],[17.513,-5.417],[5.045,-4.635],[0,0],[0,0],[0,0],[-5.948,-3.437],[1.949,-8.51],[0,-9.908],[-1.534,-6.696],[0,0],[0,0],[0,0],[-5.927,3.438],[-14.198,-13.046],[-6.556,-2.027],[0,0],[0,0],[0,0],[0,6.862],[-17.511,5.416],[-5.046,4.636],[0,0],[0,0],[0,0],[5.949,3.437],[-1.948,8.503],[0,9.911],[1.534,6.697],[0,0],[0,0],[0,0],[5.927,-3.439],[14.194,13.044],[6.556,2.028],[0,0]],"v":[[-31.226,176.828],[31.224,176.828],[31.224,143.104],[42.249,128.152],[90.035,100.33],[108.479,98.318],[137.809,115.337],[168.973,61.378],[139.879,44.572],[132.452,27.526],[135.39,-0.229],[132.452,-27.984],[139.879,-45.03],[168.973,-61.837],[137.809,-115.795],[108.479,-98.776],[90.035,-100.788],[42.25,-128.611],[31.224,-143.562],[31.224,-177.286],[-31.226,-177.286],[-31.226,-143.562],[-42.252,-128.611],[-90.036,-100.788],[-108.48,-98.776],[-137.812,-115.795],[-168.976,-61.836],[-139.881,-45.031],[-132.455,-27.982],[-135.392,-0.229],[-132.454,27.525],[-139.881,44.572],[-168.976,61.378],[-137.812,115.337],[-108.48,98.318],[-90.036,100.33],[-42.251,128.152],[-31.226,143.104]],"c":true},"ix":2},"nm":"Path 9","mn":"ADBE Vector Shape - Group","hd":false},{"ind":9,"ty":"sh","ix":10,"ks":{"a":0,"k":{"i":[[14.373,0],[0,0],[0,14.373],[0,0],[12.287,9.814],[0,0],[7.26,12.444],[0,0],[0.067,0.125],[-12.372,6.865],[0,0],[0,8.014],[-1.062,7.435],[0,0],[-6.843,12.634],[-0.072,0.124],[0,0],[-6.784,1.764],[-6.019,-3.511],[0,0],[-14.219,5.629],[0,0],[-14.373,0],[0,0],[0,-14.373],[0,0],[-12.288,-9.814],[0,0],[-7.261,-12.448],[0,0],[-0.068,-0.125],[12.369,-6.866],[0,0],[0,-8.012],[1.062,-7.437],[0,0],[6.844,-12.634],[0.071,-0.124],[0,0],[12.468,7.269],[0,0],[14.221,-5.63],[0,0]],"o":[[0,0],[-14.373,0],[0,0],[-14.221,-5.63],[0,0],[-12.438,7.253],[0,0],[-0.071,-0.124],[-6.844,-12.634],[0,0],[-1.062,-7.436],[0,-8.014],[0,0],[-12.371,-6.865],[0.068,-0.125],[0,0],[3.559,-6.101],[6.745,-1.756],[0,0],[12.287,-9.814],[0,0],[0,-14.373],[0,0],[14.373,0],[0,0],[14.218,5.629],[0,0],[12.436,-7.256],[0,0],[0.071,0.124],[6.842,12.633],[0,0],[1.062,7.437],[0,8.011],[0,0],[12.372,6.865],[-0.067,0.125],[0,0],[-7.293,12.502],[0,0],[-12.288,9.814],[0,0],[0,14.373]],"v":[[36.457,208.128],[-36.459,208.128],[-62.526,182.062],[-62.526,154.174],[-102.371,130.96],[-126.521,144.973],[-162.269,135.573],[-198.761,72.389],[-198.97,72.015],[-188.935,36.76],[-165.094,22.989],[-166.692,-0.229],[-165.094,-23.448],[-188.936,-37.219],[-198.971,-72.473],[-198.761,-72.848],[-162.303,-135.973],[-146.283,-148.136],[-126.489,-145.413],[-102.371,-131.418],[-62.526,-154.633],[-62.526,-182.52],[-36.459,-208.586],[36.457,-208.586],[62.524,-182.52],[62.524,-154.633],[102.369,-131.418],[126.519,-145.432],[162.266,-136.032],[198.759,-72.848],[198.969,-72.473],[188.935,-37.221],[165.092,-23.448],[166.69,-0.229],[165.092,22.989],[188.934,36.761],[198.968,72.015],[198.759,72.389],[162.3,135.514],[126.49,144.957],[102.369,130.96],[62.524,154.174],[62.524,182.062]],"c":true},"ix":2},"nm":"Path 10","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.914,0.91,0.91,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-63-settings-cog').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":11,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":425,"st":60,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":1,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[0.914,0.91,0.91],"ix":1}}]}],"ip":0,"op":271,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"hover-cog-1","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":0,"op":70,"st":0,"bm":0}],"markers":[{"tm":0,"cm":"default:hover-cog-1","dr":60}],"props":{}} \ No newline at end of file diff --git a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx index 1daed67a3..71c20832b 100644 --- a/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx +++ b/frontend/src/components/secret-syncs/DeleteSecretSyncModal.tsx @@ -18,7 +18,7 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp if (!secretSync) return null; - const { id: syncId, name, destination } = secretSync; + const { id: syncId, name, destination, projectId } = secretSync; const handleDeleteSecretSync = async () => { const destinationName = SECRET_SYNC_MAP[destination].name; @@ -27,7 +27,8 @@ export const DeleteSecretSyncModal = ({ isOpen, onOpenChange, secretSync, onComp await deleteSync.mutateAsync({ syncId, destination, - removeSecrets + removeSecrets, + projectId }); createNotification({ diff --git a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx index 8f0fdc40e..8b587c62f 100644 --- a/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncImportSecretsModal.tsx @@ -37,7 +37,7 @@ const FormSchema = z.object({ type TFormData = z.infer; const Content = ({ secretSync, onComplete }: ContentProps) => { - const { id: syncId, destination } = secretSync; + const { id: syncId, destination, projectId } = secretSync; const destinationName = SECRET_SYNC_MAP[destination].name; const { @@ -53,7 +53,8 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { await triggerImportSecrets.mutateAsync({ syncId, destination, - importBehavior + importBehavior, + projectId }); createNotification({ diff --git a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx index c8bdeee6d..718392b92 100644 --- a/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncRemoveSecretsModal.tsx @@ -15,7 +15,7 @@ type ContentProps = { }; const Content = ({ secretSync, onComplete }: ContentProps) => { - const { id: syncId, destination } = secretSync; + const { id: syncId, destination, projectId } = secretSync; const destinationName = SECRET_SYNC_MAP[destination].name; const triggerSyncImport = useTriggerSecretSyncRemoveSecrets(); @@ -24,7 +24,8 @@ const Content = ({ secretSync, onComplete }: ContentProps) => { try { await triggerSyncImport.mutateAsync({ syncId, - destination + destination, + projectId }); createNotification({ diff --git a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx index d9306c671..6d15e8007 100644 --- a/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx +++ b/frontend/src/components/secret-syncs/forms/EditSecretSyncForm.tsx @@ -41,7 +41,8 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) => syncId: secretSync.id, ...formData, environment: environment?.slug, - connectionId: connection.id + connectionId: connection.id, + projectId: secretSync.projectId }); createNotification({ diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx index d1a10d7e8..60a81605b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncConnectionField.tsx @@ -71,12 +71,8 @@ export const SecretSyncConnectionField = ({ onChange: callback }: Props) => { {canCreateConnection ? ( <> You do not have access to any {appName} Connections. Create one from the{" "} - - Organization Settings + + App Connections {" "} page. diff --git a/frontend/src/components/v2/PageHeader/PageHeader.tsx b/frontend/src/components/v2/PageHeader/PageHeader.tsx index f07aa2f2e..337011b38 100644 --- a/frontend/src/components/v2/PageHeader/PageHeader.tsx +++ b/frontend/src/components/v2/PageHeader/PageHeader.tsx @@ -1,15 +1,17 @@ import { ReactNode } from "@tanstack/react-router"; +import { twMerge } from "tailwind-merge"; type Props = { title: ReactNode; description?: ReactNode; children?: ReactNode; + className?: string; }; -export const PageHeader = ({ title, description, children }: Props) => ( -
+export const PageHeader = ({ title, description, children, className }: Props) => ( +
-
+

{title}

{children}
diff --git a/frontend/src/hooks/api/secretSyncs/mutations.tsx b/frontend/src/hooks/api/secretSyncs/mutations.tsx index 1efab6688..bde4ce026 100644 --- a/frontend/src/hooks/api/secretSyncs/mutations.tsx +++ b/frontend/src/hooks/api/secretSyncs/mutations.tsx @@ -23,7 +23,8 @@ export const useCreateSecretSync = () => { return data.secretSync; }, - onSuccess: () => queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }) + onSuccess: (_, { projectId }) => + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }) }); }; @@ -38,8 +39,8 @@ export const useUpdateSecretSync = () => { return data.secretSync; }, - onSuccess: (_, { syncId, destination }) => { - queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }); + onSuccess: (_, { syncId, destination, projectId }) => { + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }); queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) }); } }); @@ -55,8 +56,8 @@ export const useDeleteSecretSync = () => { return data; }, - onSuccess: (_, { syncId, destination }) => { - queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }); + onSuccess: (_, { syncId, destination, projectId }) => { + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }); queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) }); } }); @@ -72,8 +73,8 @@ export const useTriggerSecretSyncSyncSecrets = () => { return data; }, - onSuccess: (_, { syncId, destination }) => { - queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }); + onSuccess: (_, { syncId, destination, projectId }) => { + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }); queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) }); } }); @@ -93,8 +94,8 @@ export const useTriggerSecretSyncImportSecrets = () => { return data; }, - onSuccess: (_, { syncId, destination }) => { - queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }); + onSuccess: (_, { syncId, destination, projectId }) => { + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }); queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) }); } }); @@ -110,8 +111,8 @@ export const useTriggerSecretSyncRemoveSecrets = () => { return data; }, - onSuccess: (_, { syncId, destination }) => { - queryClient.invalidateQueries({ queryKey: secretSyncKeys.list() }); + onSuccess: (_, { syncId, destination, projectId }) => { + queryClient.invalidateQueries({ queryKey: secretSyncKeys.list(projectId) }); queryClient.invalidateQueries({ queryKey: secretSyncKeys.byId(destination, syncId) }); } }); diff --git a/frontend/src/hooks/api/secretSyncs/queries.tsx b/frontend/src/hooks/api/secretSyncs/queries.tsx index dbf444e20..bb0787a7b 100644 --- a/frontend/src/hooks/api/secretSyncs/queries.tsx +++ b/frontend/src/hooks/api/secretSyncs/queries.tsx @@ -12,7 +12,7 @@ import { export const secretSyncKeys = { all: ["secret-sync"] as const, options: () => [...secretSyncKeys.all, "options"] as const, - list: () => [...secretSyncKeys.all, "list"] as const, + list: (projectId: string) => [...secretSyncKeys.all, "list", projectId] as const, byId: (destination: SecretSync, syncId: string) => [...secretSyncKeys.all, destination, "by-id", syncId] as const }; @@ -46,6 +46,14 @@ export const useSecretSyncOption = (destination: SecretSync) => { return { syncOption, isPending }; }; +export const fetchSecretSyncsByProjectId = async (projectId: string) => { + const { data } = await apiRequest.get("/api/v1/secret-syncs", { + params: { projectId } + }); + + return data.secretSyncs; +}; + export const useListSecretSyncs = ( projectId: string, options?: Omit< @@ -54,14 +62,8 @@ export const useListSecretSyncs = ( > ) => { return useQuery({ - queryKey: secretSyncKeys.list(), - queryFn: async () => { - const { data } = await apiRequest.get("/api/v1/secret-syncs", { - params: { projectId } - }); - - return data.secretSyncs; - }, + queryKey: secretSyncKeys.list(projectId), + queryFn: () => fetchSecretSyncsByProjectId(projectId), ...options }); }; diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index 51f60810c..a83a3151f 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -43,26 +43,31 @@ export type TUpdateSecretSyncDTO = Partial< > & { destination: SecretSync; syncId: string; + projectId: string; }; export type TDeleteSecretSyncDTO = { destination: SecretSync; syncId: string; removeSecrets: boolean; + projectId: string; }; export type TTriggerSecretSyncSyncSecretsDTO = { destination: SecretSync; syncId: string; + projectId: string; }; export type TTriggerSecretSyncImportSecretsDTO = { destination: SecretSync; syncId: string; importBehavior: SecretSyncImportBehavior; + projectId: string; }; export type TTriggerSecretSyncRemoveSecretsDTO = { destination: SecretSync; syncId: string; + projectId: string; }; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 7ceeec450..60867fcf4 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -206,7 +206,7 @@ export const useGetWorkspaceAuthorizations = ( select }); -const fetchWorkspaceIntegrations = async (workspaceId: string) => { +export const fetchWorkspaceIntegrations = async (workspaceId: string) => { const { data } = await apiRequest.get<{ integrations: TIntegration[] }>( `/api/v1/workspace/${workspaceId}/integrations` ); diff --git a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx index bb69e6a62..589e82895 100644 --- a/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx +++ b/frontend/src/layouts/OrganizationLayout/OrganizationLayout.tsx @@ -94,6 +94,13 @@ export const OrganizationLayout = () => { )} + + {({ isActive }) => ( + + App Connections + + )} + {({ isActive }) => ( diff --git a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx index 8a11b6ca0..01dcf4f0c 100644 --- a/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx +++ b/frontend/src/layouts/OrganizationLayout/components/MinimizedOrgSidebar/MinimizedOrgSidebar.tsx @@ -10,6 +10,7 @@ import { faInfo, faInfoCircle, faMoneyBill, + faPlug, faSignOut, faUser, faUsers @@ -100,6 +101,7 @@ export const MinimizedOrgSidebar = () => { const isMoreSelected = ( [ linkOptions({ to: "/organization/access-management" }).to, + linkOptions({ to: "/organization/app-connections" }).to, linkOptions({ to: "/organization/settings" }).to, linkOptions({ to: "/organization/audit-logs" }).to ] as string[] @@ -311,10 +313,10 @@ export const MinimizedOrgSidebar = () => {
- More + Org Controls
@@ -325,6 +327,11 @@ export const MinimizedOrgSidebar = () => { Access Control + + }> + App Connections + + {(window.location.origin.includes("https://app.infisical.com") || window.location.origin.includes("https://eu.infisical.com") || window.location.origin.includes("https://gamma.infisical.com")) && ( diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx new file mode 100644 index 000000000..ff3f31c9e --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/AppConnectionsPage.tsx @@ -0,0 +1,90 @@ +import { Helmet } from "react-helmet"; +import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { OrgPermissionCan } from "@app/components/permissions"; +import { Button, PageHeader } from "@app/components/v2"; +import { + OrgPermissionAppConnectionActions, + OrgPermissionSubjects +} from "@app/context/OrgPermissionContext/types"; +import { withPermission } from "@app/hoc"; +import { usePopUp } from "@app/hooks"; +import { + AddAppConnectionModal, + AppConnectionsTable +} from "@app/pages/organization/AppConnections/AppConnectionsPage/components"; + +export const AppConnectionsPage = withPermission( + () => { + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const); + + return ( +
+ + Infisical | App Connections + + + +
+
+ + App Connections + +
+ + Docs + +
+
+ + {(isAllowed) => ( + + )} + +
+ } + description="Create and configure connections with third-party apps for re-use across Infisical projects" + /> +
+ + handlePopUpToggle("addConnection", isOpen)} + /> +
+
+
+
+ ); + }, + { + action: OrgPermissionAppConnectionActions.Read, + subject: OrgPermissionSubjects.AppConnections + } +); diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AddAppConnectionModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AddAppConnectionModal.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AddAppConnectionModal.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AppConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AppConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AppConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AwsConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AwsConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AwsConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AwsConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureAppConfigurationConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/AzureKeyVaultConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GcpConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GcpConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GcpConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GcpConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GenericAppConnectionFields.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GenericAppConnectionFields.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GenericAppConnectionFields.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GenericAppConnectionFields.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GitHubConnectionForm.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/GitHubConnectionForm.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/GitHubConnectionForm.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/index.ts b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/index.ts similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionForm/index.ts rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/index.ts diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionHeader.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionHeader.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionHeader.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionHeader.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionList.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionList.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionList.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionRow.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionRow.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionRow.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionsTable.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx similarity index 99% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionsTable.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx index db5e218ec..ee71669ba 100644 --- a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/AppConnectionsTable.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionsTable.tsx @@ -159,7 +159,7 @@ export const AppConnectionsTable = () => { value={search} onChange={(e) => setSearch(e.target.value)} leftIcon={} - placeholder="Search integrations..." + placeholder="Search connections..." className="flex-1" /> diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/DeleteAppConnectionModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/DeleteAppConnectionModal.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/DeleteAppConnectionModal.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/EditAppConnectionCredentialsModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionCredentialsModal.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/EditAppConnectionCredentialsModal.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionCredentialsModal.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/EditAppConnectionDetailsModal.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/EditAppConnectionDetailsModal.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/EditAppConnectionDetailsModal.tsx diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/index.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/index.tsx similarity index 100% rename from frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/components/index.tsx rename to frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/index.tsx diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/route.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/route.tsx new file mode 100644 index 000000000..3b4205656 --- /dev/null +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/route.tsx @@ -0,0 +1,23 @@ +import { faHome } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createFileRoute, linkOptions } from "@tanstack/react-router"; + +import { AppConnectionsPage } from "./AppConnectionsPage"; + +export const Route = createFileRoute( + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/" +)({ + component: AppConnectionsPage, + context: () => ({ + breadcrumbs: [ + { + label: "Home", + icon: () => , + link: linkOptions({ to: "/organization/secret-manager/overview" }) + }, + { + label: "App Connections" + } + ] + }) +}); diff --git a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx index e327a78c7..8832d611e 100644 --- a/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx +++ b/frontend/src/pages/organization/AppConnections/OauthCallbackPage/OauthCallbackPage.tsx @@ -135,7 +135,7 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/settings?selectedTab=app-connections" + to: returnUrl ?? "/organization/app-connections" }); } @@ -183,7 +183,7 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/settings?selectedTab=app-connections" + to: returnUrl ?? "/organization/app-connections" }); } @@ -249,7 +249,7 @@ export const OAuthCallbackPage = () => { type: "error" }); navigate({ - to: returnUrl ?? "/organization/settings?selectedTab=app-connections" + to: returnUrl ?? "/organization/app-connections" }); } @@ -295,7 +295,7 @@ export const OAuthCallbackPage = () => { } await navigate({ - to: data?.returnUrl ?? "/organization/settings?selectedTab=app-connections" + to: data?.returnUrl ?? "/organization/app-connections" }); })(); }, [isReady]); diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/AppConnectionsTab.tsx b/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/AppConnectionsTab.tsx deleted file mode 100644 index 393bb4381..000000000 --- a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/AppConnectionsTab.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { faArrowUpRightFromSquare, faBookOpen, faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import { OrgPermissionCan } from "@app/components/permissions"; -import { Button } from "@app/components/v2"; -import { OrgPermissionSubjects } from "@app/context"; -import { OrgPermissionAppConnectionActions } from "@app/context/OrgPermissionContext/types"; -import { withPermission } from "@app/hoc"; -import { usePopUp } from "@app/hooks"; - -import { AddAppConnectionModal, AppConnectionsTable } from "./components"; - -export const AppConnectionsTab = withPermission( - () => { - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["addConnection"] as const); - - return ( -
-
-
-
-
-

App Connections

- -
- - Docs - -
-
-
-

- Create and configure connections with third-party apps for re-use across Infisical - projects -

-
- - {(isAllowed) => ( - - )} - -
- - handlePopUpToggle("addConnection", isOpen)} - /> -
-
- ); - }, - { - action: OrgPermissionAppConnectionActions.Read, - subject: OrgPermissionSubjects.AppConnections - } -); diff --git a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/index.tsx b/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/index.tsx deleted file mode 100644 index 79649edaf..000000000 --- a/frontend/src/pages/organization/SettingsPage/components/AppConnectionsTab/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { AppConnectionsTab } from "./AppConnectionsTab"; diff --git a/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx b/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx index 080139540..bfc2ee745 100644 --- a/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx +++ b/frontend/src/pages/organization/SettingsPage/components/OrgTabGroup/OrgTabGroup.tsx @@ -4,7 +4,6 @@ import { useSearch } from "@tanstack/react-router"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; import { ROUTE_PATHS } from "@app/const/routes"; -import { AppConnectionsTab } from "../AppConnectionsTab"; import { AuditLogStreamsTab } from "../AuditLogStreamTab"; import { ImportTab } from "../ImportTab"; import { OrgAuthTab } from "../OrgAuthTab"; @@ -26,7 +25,6 @@ export const OrgTabGroup = () => { key: "workflow-integrations", component: OrgWorkflowIntegrationTab }, - { name: "App Connections", key: "app-connections", component: AppConnectionsTab }, { name: "Audit Log Streams", key: "tag-audit-log-streams", component: AuditLogStreamsTab }, { name: "Import", key: "tab-import", component: ImportTab }, { name: "Project Templates", key: "project-templates", component: ProjectTemplatesTab } diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index 492df2aaa..0bf666b4b 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -1,12 +1,12 @@ import { useMemo } from "react"; -import { Controller, useForm } from "react-hook-form"; +import { Controller, useFieldArray, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Link } from "@tanstack/react-router"; import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Modal, ModalContent } from "@app/components/v2"; +import { CreatableSelect } from "@app/components/v2/CreatableSelect"; import { useOrganization, useWorkspace } from "@app/context"; import { useAddUsersToOrg, @@ -19,7 +19,15 @@ import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; const addMemberFormSchema = z.object({ - orgMemberships: z.array(z.object({ label: z.string().trim(), value: z.string().trim() })).min(1), + orgMemberships: z + .array( + z.object({ + label: z.string().trim(), + value: z.string().trim(), + isNewInvitee: z.boolean().optional() + }) + ) + .min(1), projectRoleSlugs: z.array(z.object({ slug: z.string().trim(), name: z.string().trim() })).min(1) }); @@ -60,7 +68,12 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { if (!currentWorkspace) return; if (!currentOrg?.id) return; - const selectedMembers = orgMemberships.map((orgMembership) => + const existingMembers = orgMemberships.filter((membership) => !membership.isNewInvitee); + const newInvitees = orgMemberships + .filter((membership) => membership.isNewInvitee) + .map((membership) => membership.value); + + const selectedMembers = existingMembers.map((orgMembership) => orgUsers?.find((orgUser) => orgUser.id === orgMembership.value) ); @@ -76,11 +89,11 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { const inviteeEmails = selectedMembers .map((member) => member?.user.username as string) .filter(Boolean); - if (inviteeEmails.length) { + if (inviteeEmails.length || newInvitees.length) { await addMembersToProject({ - inviteeEmails, + inviteeEmails: [...inviteeEmails, ...newInvitees], organizationId: orgId, - organizationRoleSlug: ProjectMembershipRole.Member, // ? This doesn't apply in this case, because we know the users being added are already part of the organization + organizationRoleSlug: ProjectMembershipRole.Member, // only applies to new invites projects: [ { slug: currentWorkspace.slug, @@ -125,6 +138,8 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { const selectedOrgMemberships = watch("orgMemberships"); const selectedRoleSlugs = watch("projectRoleSlugs"); + const { append } = useFieldArray({ control, name: "orgMemberships" }); + return ( { title={t("section.members.add-dialog.add-member-to-project") as string} subTitle={t("section.members.add-dialog.user-will-email")} > - {filteredOrgUsers.length ? ( -
-
- ( - - - - )} - /> + +
+ ( + + ( + <> +

+ {!filteredOrgUsers.length && ( +

All organization members are already assigned to this project.

+ )} +

+

Add new users to your organization by typing out their email address.

+ + )} + onCreateOption={(inputValue) => + append({ label: inputValue, value: inputValue, isNewInvitee: true }) + } + formatCreateLabel={(inputValue) => `Invite "${inputValue}"`} + isValidNewOption={(input) => + Boolean(input) && + z.string().email().safeParse(input).success && + !orgUsers + ?.flatMap(({ user }) => { + const emails: string[] = []; - ( - - option.slug} - getOptionLabel={(option) => option.name} - /> - - )} - /> -
-
- - -
- - ) : ( -
-
All the users in your organization are already invited.
- - - + placeholder="Add one or more users..." + isMulti + name="members" + options={filteredOrgUsers} + value={field.value} + onChange={field.onChange} + /> + + )} + /> + + ( + + option.slug} + getOptionLabel={(option) => option.name} + /> + + )} + />
- )} +
+ + +
+ ); diff --git a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/route.tsx b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/route.tsx index 94290372e..916812e66 100644 --- a/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/route.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsDetailsByIDPage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { IntegrationDetailsByIDPage } from "./IntegrationsDetailsByIDPage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx index 947850e22..a96a4d595 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/IntegrationsListPage.tsx @@ -29,7 +29,7 @@ export const IntegrationsListPage = () => { const updateSelectedTab = (tab: string) => { navigate({ to: ROUTE_PATHS.SecretManager.IntegrationsListPage.path, - search: (prev) => ({ ...prev, selectedTab: tab }), + search: (prev) => ({ ...prev, selectedTab: tab as IntegrationsListPageTabs }), params: { projectId: currentWorkspace.id } diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx index fefb1ad41..802e88bc1 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncsTable.tsx @@ -227,7 +227,8 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { await updateSync.mutateAsync({ syncId: secretSync.id, destination: secretSync.destination, - isAutoSyncEnabled + isAutoSyncEnabled, + projectId: secretSync.projectId }); createNotification({ @@ -248,7 +249,8 @@ export const SecretSyncsTable = ({ secretSyncs }: Props) => { try { await triggerSync.mutateAsync({ syncId: secretSync.id, - destination: secretSync.destination + destination: secretSync.destination, + projectId: secretSync.projectId }); createNotification({ diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx index 5a176b2f9..54142e2d9 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/route.tsx @@ -1,15 +1,16 @@ -import { createFileRoute } from "@tanstack/react-router"; +import { createFileRoute, redirect } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { workspaceKeys } from "@app/hooks/api"; +import { fetchSecretSyncsByProjectId, secretSyncKeys } from "@app/hooks/api/secretSyncs"; +import { fetchWorkspaceIntegrations } from "@app/hooks/api/workspace/queries"; import { IntegrationsListPageTabs } from "@app/types/integrations"; import { IntegrationsListPage } from "./IntegrationsListPage"; const IntegrationsListPageQuerySchema = z.object({ - selectedTab: z - .nativeEnum(IntegrationsListPageTabs) - .catch(IntegrationsListPageTabs.NativeIntegrations) + selectedTab: z.nativeEnum(IntegrationsListPageTabs).optional() }); export const Route = createFileRoute( @@ -17,7 +18,47 @@ export const Route = createFileRoute( )({ component: IntegrationsListPage, validateSearch: zodValidator(IntegrationsListPageQuerySchema), - beforeLoad: ({ context }) => { + beforeLoad: async ({ context, search, params: { projectId } }) => { + if (!search.selectedTab) { + const secretSyncs = await context.queryClient.ensureQueryData({ + queryKey: secretSyncKeys.list(projectId), + queryFn: () => fetchSecretSyncsByProjectId(projectId) + }); + + if (secretSyncs.length) { + throw redirect({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId + }, + search: { selectedTab: IntegrationsListPageTabs.SecretSyncs } + }); + } + + const integrations = await context.queryClient.ensureQueryData({ + queryKey: workspaceKeys.getWorkspaceIntegrations(projectId), + queryFn: () => fetchWorkspaceIntegrations(projectId) + }); + + if (integrations.length) { + throw redirect({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId + }, + search: { selectedTab: IntegrationsListPageTabs.NativeIntegrations } + }); + } + + throw redirect({ + to: "/secret-manager/$projectId/integrations", + params: { + projectId + }, + search: { selectedTab: IntegrationsListPageTabs.SecretSyncs } + }); + } + return { breadcrumbs: [ ...context.breadcrumbs, diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx index bc4f6d2b8..bda6e55e3 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncActionTriggers.tsx @@ -92,7 +92,8 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => { await updateSync.mutateAsync({ syncId: secretSync.id, destination: secretSync.destination, - isAutoSyncEnabled + isAutoSyncEnabled, + projectId: secretSync.projectId }); createNotification({ @@ -111,7 +112,8 @@ export const SecretSyncActionTriggers = ({ secretSync }: Props) => { try { await triggerSyncSecrets.mutateAsync({ syncId: secretSync.id, - destination: secretSync.destination + destination: secretSync.destination, + projectId: secretSync.projectId }); createNotification({ diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/route.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/route.tsx index 7fcade485..8e76138ea 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/route.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { SecretSyncDetailsByIDPage } from "./SecretSyncDetailsByIDPage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.SecretSyncs + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx index c885d2e10..f066497ff 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AWSParameterStoreAuthorizeIntegrationPage } from "./AwsParameterStoreAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx index 3bf787763..efb8573b3 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/AwsParamterStoreConfigurePage.tsx @@ -30,6 +30,7 @@ import { useWorkspace } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { Connection = "connection", @@ -158,6 +159,9 @@ export const AWSParameterStoreConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx index ce686c860..9195a08b8 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsParameterStoreConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AWSParameterStoreConfigurePage } from "./AwsParamterStoreConfigurePage"; const AwsParameterStoreConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx index 67230282e..6f56da795 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AWSSecretManagerAuthorizePage } from "./AwsSecretManagerAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx index 2a5bdbf70..2297575d7 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/AwsSecretManagerConfigurePage.tsx @@ -38,6 +38,7 @@ import { IntegrationMappingBehavior, IntegrationMetadataSyncMode } from "@app/hooks/api/integrations/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { Connection = "connection", @@ -206,6 +207,9 @@ export const AwsSecretManagerConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx index 7275bc2aa..97df1702c 100644 --- a/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AwsSecretManagerConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AwsSecretManagerConfigurePage } from "./AwsSecretManagerConfigurePage"; const AwsSecretManagerConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx index 792ed9010..7485068ab 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/AzureAppConfigurationConfigurePage.tsx @@ -26,6 +26,7 @@ import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const schema = z.object({ baseUrl: z @@ -133,6 +134,9 @@ export const AzureAppConfigurationConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx index 020941e0c..250160f4f 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureAppConfigurationConfigurePage } from "./AzureAppConfigurationConfigurePage"; const AzureAppConfigurationPageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx index 5a94048d5..d8371fcf4 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureAppConfigurationOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureAppConfigurationOauthCallbackPage } from "./AzureAppConfigurationOauthCallbackPage"; export const AzureAppConfigurationOauthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx index 4dca8b8c6..c9284f800 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureDevopsAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureDevopsAuthorizePage } from "./AzureDevopsAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx index 7935b615b..7615909ad 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/AzureDevopsConfigurePage.tsx @@ -21,6 +21,7 @@ import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const AzureDevopsConfigurePage = () => { const navigate = useNavigate(); @@ -82,6 +83,9 @@ export const AzureDevopsConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx index 620e71660..766963366 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureDevopsConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureDevopsConfigurePage } from "./AzureDevopsConfigurePage"; const AzureDevopsConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx index 6d14a4bb7..3975e7da1 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultAuthorizePage/route.tsx @@ -1,6 +1,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import z from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureKeyVaultAuthorizePage } from "./AzureKeyVaultAuthorizePage"; const PageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx index aaf65289a..4dd3c4a11 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/AzureKeyVaultConfigurePage.tsx @@ -16,6 +16,7 @@ import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const initialSyncBehaviors = [ { @@ -90,6 +91,9 @@ export const AzureKeyVaultConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx index 773fa5468..76a945ee3 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureKeyVaultConfigurePage } from "./AzureKeyVaultConfigurePage"; const AzureKeyVaultConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx index 2e3e03455..c2239f48e 100644 --- a/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/AzureKeyVaultOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { AzureKeyVaultOauthCallbackPage } from "./AzureKeyVaultOauthCallback"; export const AzureKeyVaultOauthCallbackQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx index 5d4d9710b..abf8d9f02 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/BitbucketConfigurePage.tsx @@ -23,6 +23,7 @@ import { useGetIntegrationAuthBitBucketWorkspaces } from "@app/hooks/api"; import { useGetIntegrationAuthBitBucketEnvironments } from "@app/hooks/api/integrationAuth/queries"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum BitbucketScope { Repo = "repo", @@ -131,6 +132,9 @@ export const BitbucketConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx index d10fbbc80..68b1acd63 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { BitbucketConfigurePage } from "./BitbucketConfigurePage"; const BitbucketConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx index be898d684..d02697846 100644 --- a/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/BitbucketOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { BitbucketOauthCallbackPage } from "./BitbucketOauthCallbackPage"; export const BitbucketOauthCallbackQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx index cd38ff7ec..7b54ba00e 100644 --- a/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/ChecklyAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { ChecklyAuthorizePage } from "./ChecklyAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx index 99b182bfe..63169a3d1 100644 --- a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/ChecklyConfigurePage.tsx @@ -27,6 +27,7 @@ import { useGetIntegrationAuthChecklyGroups } from "@app/hooks/api/integrationAuth"; import { useGetWorkspaceById } from "@app/hooks/api/workspace"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { Connection = "connection", @@ -115,6 +116,9 @@ export const ChecklyConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx index 832ecc202..652aae23a 100644 --- a/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/ChecklyConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { ChecklyConfigurePage } from "./ChecklyConfigurePage"; const ChecklyConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx index 0fb0e577b..b625dcce4 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CircleCIAuthorizePage } from "./CircleCIAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx index e91a3ca89..9ba050808 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/CircleCIConfigurePage.tsx @@ -21,6 +21,7 @@ import { ROUTE_PATHS } from "@app/const/routes"; import { useWorkspace } from "@app/context"; import { useCreateIntegration, useGetIntegrationAuthCircleCIOrganizations } from "@app/hooks/api"; import { CircleCiScope } from "@app/hooks/api/integrationAuth/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const formSchema = z.discriminatedUnion("scope", [ z.object({ @@ -105,6 +106,9 @@ export const CircleCIConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx index 7f3ed1d37..0c6bb61f0 100644 --- a/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CircleCIConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CircleCIConfigurePage } from "./CircleCIConfigurePage"; const CircleCIConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx index b0dd2c467..5b6e06c55 100644 --- a/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/Cloud66AuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { Cloud66AuthorizePage } from "./Cloud66AuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx index 73c410ce2..ace6e4f6c 100644 --- a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/Cloud66ConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const Cloud66ConfigurePage = () => { const navigate = useNavigate(); @@ -77,6 +78,9 @@ export const Cloud66ConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx index 422d0ce5f..1c7fe58d2 100644 --- a/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/Cloud66ConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { Cloud66ConfigurePage } from "./Cloud66ConfigurePage"; const Cloud66ConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx index 4f216cccb..10a49dbf8 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CloudflarePagesAuthorizePage } from "./CloudflarePagesAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx index 03be1dab6..1d9c188e6 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/CloudflarePagesConfigurePage.tsx @@ -20,6 +20,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const cloudflareEnvironments = [ { name: "Production", slug: "production" }, @@ -96,6 +97,9 @@ export const CloudflarePagesConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx index 29310625e..30be97266 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflarePagesConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CloudflarePagesConfigurePage } from "./CloudflarePagesConfigurePage"; const CloudflarePagesConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx index 0558baed2..731053ce0 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CloudflareWorkersAuthorizePage } from "./CloudflareWorkersAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx index 0f2149633..c650c47af 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/CloudflareWorkersConfigurePage.tsx @@ -12,6 +12,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const CloudflareWorkersConfigurePage = () => { const navigate = useNavigate(); @@ -69,6 +70,9 @@ export const CloudflareWorkersConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx index 1687628e3..ffbb56a83 100644 --- a/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CloudflareWorkersConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CloudflareWorkersConfigurePage } from "./CloudflareWorkersConfigurePage"; const CloudflareWorkersConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx index 443fc2267..b414b9819 100644 --- a/frontend/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CodefreshAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CodefreshAuthorizePage } from "./CodefreshAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx index 1961236d9..23dbcabf1 100644 --- a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/CodefreshConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const CodefreshConfigurePage = () => { const navigate = useNavigate(); @@ -73,6 +74,9 @@ export const CodefreshConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx index 717462908..5ddde68e6 100644 --- a/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/CodefreshConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { CodefreshConfigurePage } from "./CodefreshConfigurePage"; const CodefreshConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx index a49d94583..4fdee2ba6 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { DatabricksAuthorizePage } from "./DatabricksAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx index 73453c80a..69538a318 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/DatabricksConfigurePage.tsx @@ -26,6 +26,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const DatabricksConfigurePage = () => { const navigate = useNavigate(); @@ -89,6 +90,9 @@ export const DatabricksConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx index 76bb65f1f..e7e8ae2db 100644 --- a/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/DatabricksConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { DatabricksConfigurePage } from "./DatabricksConfigurePage"; const DatabricksConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx index 68277ce59..5b3a62175 100644 --- a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { DigitalOceanAppPlatformAuthorizePage } from "./DigitalOceanAppPlatformAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx index 61c2c5789..2bc740f05 100644 --- a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/DigitalOceanAppPlatformConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const DigitalOceanAppPlatformConfigurePage = () => { const navigate = useNavigate(); @@ -74,6 +75,9 @@ export const DigitalOceanAppPlatformConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx index 80eaf0bae..505206b71 100644 --- a/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/DigitalOceanAppPlatformConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { DigitalOceanAppPlatformConfigurePage } from "./DigitalOceanAppPlatformConfigurePage"; const DigitalOceanAppPlatformConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx index 44e78a708..3393aa029 100644 --- a/frontend/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/FlyioAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { FlyioAuthorizePage } from "./FlyioAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx index c0002c553..44c81d994 100644 --- a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/FlyioConfigurePage.tsx @@ -27,6 +27,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const FlyioConfigurePage = () => { const navigate = useNavigate(); @@ -88,6 +89,9 @@ export const FlyioConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx index 254068dbe..19b009c70 100644 --- a/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/FlyioConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { FlyioConfigurePage } from "./FlyioConfigurePage"; const FlyioConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/route.tsx index 92df323ce..6e48d2916 100644 --- a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GcpSecretManagerAuthorizePage } from "./GcpSecretManagerAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx index 698ec82aa..c0cb2c171 100644 --- a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/GcpSecretManagerConfigurePage.tsx @@ -33,6 +33,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { Connection = "connection", @@ -153,6 +154,9 @@ export const GcpSecretManagerConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/route.tsx index 644a9f858..cad85ec41 100644 --- a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GcpSecretManagerConfigurePage } from "./GcpSecretManagerConfigurePage"; const GcpConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/route.tsx index 3049d2bb6..239f583b2 100644 --- a/frontend/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GcpSecretManagerOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GcpSecretManagerOauthCallbackPage } from "./GcpSecretManagerOauthCallbackPage"; export const GcpSecretManagerOAuthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/route.tsx index 584592ed1..248a0e328 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GithubAuthorizePage } from "./GithubAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx index 01309b661..c51582075 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/GithubConfigurePage.tsx @@ -45,6 +45,7 @@ import { useGetIntegrationAuthGithubEnvs, useGetIntegrationAuthGithubOrgs } from "@app/hooks/api"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; enum TabSections { Connection = "connection", @@ -269,6 +270,9 @@ export const GithubConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/route.tsx index 6bcf6db77..9ff038882 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GithubConfigurePage } from "./GithubConfigurePage"; const GithubConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GithubOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/GithubOauthCallbackPage/route.tsx index 234d43a41..bf6f7a342 100644 --- a/frontend/src/pages/secret-manager/integrations/GithubOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GithubOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GithubOauthCallbackPage } from "./GithubOauthCallbackPage"; export const GithubOAuthCallbackPageQueryParamsSchema = z.object({ @@ -23,7 +25,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/route.tsx index 9b132210e..e38ac0809 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GitlabAuthorizePage } from "./GitlabAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx index f09e13024..b88b10b03 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/GitlabConfigurePage.tsx @@ -35,6 +35,7 @@ import { useGetIntegrationAuthTeams } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const gitLabEntities = [ { name: "Individual", value: "individual" }, @@ -177,6 +178,9 @@ export const GitlabConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/route.tsx index b09ab1cf0..64d3a3a36 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GitlabConfigurePage } from "./GitlabConfigurePage"; const GitlabConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/route.tsx index 9a4644845..f775a13fb 100644 --- a/frontend/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/GitlabOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { GitLabOAuthCallbackPage } from "./GitlabOauthCallbackPage"; export const GitlabOAuthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/route.tsx index 02d2600c7..d6b124572 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HashicorpVaultAuthorizePage } from "./HashicorpVaultAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx index c31a74371..3fbb6501f 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/HashicorpVaultConfigurePage.tsx @@ -29,6 +29,7 @@ import { useWorkspace } from "@app/context"; import { isValidPath } from "@app/helpers/string"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const generateFormSchema = (availableEnvironmentNames: string[]) => { return z.object({ @@ -103,6 +104,9 @@ export const HashicorpVaultConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/route.tsx index a298b9bba..1f2c31660 100644 --- a/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HashicorpVaultConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HashicorpVaultConfigurePage } from "./HashicorpVaultConfigurePage"; const HashicorpVaultConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/route.tsx index 7b9de974b..572b3a005 100644 --- a/frontend/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HasuraCloudAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HasuraCloudAuthorizePage } from "./HasuraCloudAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx index 939bd8254..06dbe61f1 100644 --- a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/HasuraCloudConfigurePage.tsx @@ -15,6 +15,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const schema = z.object({ secretPath: z.string().trim(), @@ -72,6 +73,9 @@ export const HasuraCloudConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/route.tsx index d7920bb1e..c475e5bd1 100644 --- a/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HasuraCloudConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HasuraCloudConfigurePage } from "./HasuraCloudConfigurePage"; const HasuraCloudConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/HerokuConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/HerokuConfigurePage.tsx index 394c37212..79763e475 100644 --- a/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/HerokuConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/HerokuConfigurePage.tsx @@ -18,6 +18,7 @@ import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const initialSyncBehaviors = [ { @@ -100,6 +101,9 @@ export const HerokuConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/route.tsx index a33757bc1..bcfaf2f3e 100644 --- a/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HerokuConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HerokuConfigurePage } from "./HerokuConfigurePage"; const HerokuConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/route.tsx index b180e9d6a..5587e930b 100644 --- a/frontend/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/HerokuOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { HerokuOAuthCallbackPage } from "./HerokuOauthCallbackPage"; export const HerokuOAuthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/route.tsx index 0778c8f5d..dd49bb33f 100644 --- a/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/LaravelForgeAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { LaravelForgeAuthorizePage } from "./LaravelForgeAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx index 2d7902c01..5c6ead598 100644 --- a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/LaravelForgeConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const LaravelForgeConfigurePage = () => { const navigate = useNavigate(); @@ -74,6 +75,9 @@ export const LaravelForgeConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/route.tsx index 98d95b670..3a58ecacf 100644 --- a/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/LaravelForgeConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { LaravelForgeConfigurePage } from "./LaravelForgeConfigurePage"; const LaravelForgeConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/NetlifyConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/NetlifyConfigurePage.tsx index 44a9acbd7..99cf2b434 100644 --- a/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/NetlifyConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/NetlifyConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const netlifyEnvironments = [ { name: "Local development", slug: "dev" }, @@ -81,6 +82,9 @@ export const NetlifyConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/route.tsx index 81f3877bd..2da84a523 100644 --- a/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/NetlifyConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { NetlifyConfigurePage } from "./NetlifyConfigurePage"; const NetlifyConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/route.tsx index d88dc29b2..37b667bf9 100644 --- a/frontend/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/NetlifyOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { NetlifyOauthCallbackPage } from "./NetlifyOauthCallbackPage"; export const NetlifyOAuthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/route.tsx index b0e2c10f8..1ddca538f 100644 --- a/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/NorthflankAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { NorthflankAuthorizePage } from "./NorthflankAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx index 9d99cc940..2338ea3ef 100644 --- a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/NorthflankConfigurePage.tsx @@ -18,6 +18,7 @@ import { useGetIntegrationAuthById, useGetIntegrationAuthNorthflankSecretGroups } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const NorthflankConfigurePage = () => { const navigate = useNavigate(); @@ -95,6 +96,9 @@ export const NorthflankConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/route.tsx index fe5c2283a..1d2db4fda 100644 --- a/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/NorthflankConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { NorthflankConfigurePage } from "./NorthflankConfigurePage"; const NorthflankConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/route.tsx index c50832858..0064e2a57 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { OctopusDeployAuthorizePage } from "./OctopusDeployAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx index 2e9d699ba..93f0e58fc 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/OctopusDeployConfigurePage.tsx @@ -23,6 +23,7 @@ import { useGetIntegrationAuthOctopusDeploySpaces } from "@app/hooks/api/integrationAuth/queries"; import { OctopusDeployScope } from "@app/hooks/api/integrationAuth/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const formSchema = z.object({ scope: z.nativeEnum(OctopusDeployScope), @@ -137,6 +138,9 @@ export const OctopusDeployConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/route.tsx index 5c897a95c..264c17dcb 100644 --- a/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/OctopusDeployConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { OctopusDeployConfigurePage } from "./OctopusDeployConfigurePage"; const OctopusDeployConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/route.tsx index eddbca06f..e63745397 100644 --- a/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/QoveryAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { QoveryAuthorizePage } from "./QoveryAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx index 5041782aa..e2d294442 100644 --- a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/QoveryConfigurePage.tsx @@ -28,6 +28,7 @@ import { useGetIntegrationAuthQoveryProjects, useGetIntegrationAuthQoveryScopes } from "@app/hooks/api/integrationAuth/queries"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const qoveryScopes = [ { label: "Application", value: "application" }, @@ -178,6 +179,9 @@ export const QoveryConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/route.tsx index 1f14f02ca..ae6fb36fe 100644 --- a/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/QoveryConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { QoveryConfigurePage } from "./QoveryConfigurePage"; const QoveryConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RailwayAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RailwayAuthorizePage/route.tsx index d351b5f8f..8d66fbfc0 100644 --- a/frontend/src/pages/secret-manager/integrations/RailwayAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RailwayAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RailwayAuthorizePage } from "./RailwayAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx index 9b4162072..ea3894203 100644 --- a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/RailwayConfigurePage.tsx @@ -19,6 +19,7 @@ import { useGetIntegrationAuthRailwayEnvironments, useGetIntegrationAuthRailwayServices } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const RailwayConfigurePage = () => { const navigate = useNavigate(); @@ -110,6 +111,9 @@ export const RailwayConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/route.tsx index 411c960a5..aa7c9070f 100644 --- a/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RailwayConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RailwayConfigurePage } from "./RailwayConfigurePage"; const RailwayConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RenderAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RenderAuthorizePage/route.tsx index 3657f2a07..f8f25e764 100644 --- a/frontend/src/pages/secret-manager/integrations/RenderAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RenderAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RenderAuthorizePage } from "./RenderAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx index 40c00d209..f30ff6b14 100644 --- a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/RenderConfigurePage.tsx @@ -29,6 +29,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const schema = z.object({ selectedSourceEnvironment: z.string(), @@ -106,6 +107,9 @@ export const RenderConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/route.tsx index 8c676c800..59486fa96 100644 --- a/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RenderConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RenderConfigurePage } from "./RenderConfigurePage"; const RenderConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RundeckAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RundeckAuthorizePage/route.tsx index 3ff55ddb4..a878544b8 100644 --- a/frontend/src/pages/secret-manager/integrations/RundeckAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RundeckAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RundeckAuthorizePage } from "./RundeckAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx index 10b16c6ea..9b49bd8ec 100644 --- a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/RundeckConfigurePage.tsx @@ -20,6 +20,7 @@ import { ROUTE_PATHS } from "@app/const/routes"; import { useWorkspace } from "@app/context"; import { useCreateIntegration } from "@app/hooks/api"; import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const schema = z.object({ keyStoragePath: z.string().trim().min(1, { message: "Rundeck Key Storage path is required" }), @@ -73,6 +74,9 @@ export const RundeckConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/route.tsx index 3683b0fc0..866778387 100644 --- a/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/RundeckConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { RundeckConfigurePage } from "./RundeckConfigurePage"; const RundeskConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/route.tsx b/frontend/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/route.tsx index 26eb213aa..1e7ce337c 100644 --- a/frontend/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/SelectIntegrationAuthPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { SelectIntegrationAuthPage } from "./SelectIntegrationAuthPage"; const SelectIntegrationAuthPageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/route.tsx index 544867b50..a3b7a826e 100644 --- a/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/SupabaseAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { SupabaseAuthorizePage } from "./SupabaseAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx index 3cb604935..8c2aa0a09 100644 --- a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/SupabaseConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const SupabaseConfigurePage = () => { const navigate = useNavigate(); @@ -73,6 +74,9 @@ export const SupabaseConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/route.tsx index dc2f73eee..e5d50d350 100644 --- a/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/SupabaseConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { SupabaseConfigurePage } from "./SupabaseConfigurePage"; const SupabaseConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TeamcityAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TeamcityAuthorizePage/route.tsx index 9983c9b9c..e9bc5534b 100644 --- a/frontend/src/pages/secret-manager/integrations/TeamcityAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TeamcityAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TeamcityAuthorizePage } from "./TeamcityAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx index 24fda3be4..96770c19f 100644 --- a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/TeamcityConfigurePage.tsx @@ -21,6 +21,7 @@ import { useGetIntegrationAuthById, useGetIntegrationAuthTeamCityBuildConfigs } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const TeamcityConfigurePage = () => { const navigate = useNavigate(); @@ -92,6 +93,9 @@ export const TeamcityConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/route.tsx index 57301a69c..73532c919 100644 --- a/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TeamcityConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TeamcityConfigurePage } from "./TeamcityConfigurePage"; const TeamcityConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/route.tsx index d15f50f2c..ad936b182 100644 --- a/frontend/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TerraformCloudAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TerraformCloudAuthorizePage } from "./TerraformCloudAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx index 97f6bd166..a247144fa 100644 --- a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/TerraformCloudConfigurePage.tsx @@ -21,6 +21,7 @@ import { useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const initialSyncBehaviors = [ { @@ -107,6 +108,9 @@ export const TerraformCloudConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/route.tsx index 373e10839..ad2a48182 100644 --- a/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TerraformCloudConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TerraformCloudConfigurePage } from "./TerraformCloudConfigurePage"; const TerraformCloudConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TravisCIAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TravisCIAuthorizePage/route.tsx index 71e5c123b..eaa138804 100644 --- a/frontend/src/pages/secret-manager/integrations/TravisCIAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TravisCIAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TravisCIAuthorizePage } from "./TravisCIAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx index b6fff7fef..807a6a87e 100644 --- a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/TravisCIConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const TravisCIConfigurePage = () => { const navigate = useNavigate(); @@ -74,6 +75,9 @@ export const TravisCIConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/route.tsx index ecc256de4..cc5600ffd 100644 --- a/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/TravisCIConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { TravisCIConfigurePage } from "./TravisCIConfigurePage"; const TravisCIConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/VercelConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/VercelConfigurePage.tsx index fabccb6bb..4a421274c 100644 --- a/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/VercelConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/VercelConfigurePage.tsx @@ -29,6 +29,7 @@ import { useGetIntegrationAuthVercelCustomEnvironments } from "@app/hooks/api/integrationAuth"; import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; const vercelEnvironments = [ { name: "Development", slug: "development" }, @@ -134,6 +135,9 @@ export const VercelConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/route.tsx index 4b743ceb1..3f36ba5fa 100644 --- a/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/VercelConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { VercelConfigurePage } from "./VercelConfigurePage"; const VercelConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/VercelOauthCallbackPage/route.tsx b/frontend/src/pages/secret-manager/integrations/VercelOauthCallbackPage/route.tsx index fcdf72976..6bfa5dac8 100644 --- a/frontend/src/pages/secret-manager/integrations/VercelOauthCallbackPage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/VercelOauthCallbackPage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { VercelOauthCallbackPage } from "./VercelOauthCallbackPage"; export const VercelOAuthCallbackPageQueryParamsSchema = z.object({ @@ -22,7 +24,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/route.tsx b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/route.tsx index b81b4a7fd..1985bef00 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/route.tsx @@ -1,5 +1,7 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { WindmillAuthorizePage } from "./WindmillAuthorizePage"; export const Route = createFileRoute( @@ -14,7 +16,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx index 1d8094059..ad70c75ba 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/WindmillConfigurePage.tsx @@ -17,6 +17,7 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from "@app/hooks/api/integrationAuth"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; export const WindmillConfigurePage = () => { const navigate = useNavigate(); @@ -75,6 +76,9 @@ export const WindmillConfigurePage = () => { to: "/secret-manager/$projectId/integrations", params: { projectId: currentWorkspace.id + }, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations } }); } catch (err) { diff --git a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/route.tsx b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/route.tsx index 4ff5443b2..cc3442340 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/route.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillConfigurePage/route.tsx @@ -2,6 +2,8 @@ import { createFileRoute, linkOptions } from "@tanstack/react-router"; import { zodValidator } from "@tanstack/zod-adapter"; import { z } from "zod"; +import { IntegrationsListPageTabs } from "@app/types/integrations"; + import { WindmillConfigurePage } from "./WindmillConfigurePage"; const WindmillConfigurePageQueryParamsSchema = z.object({ @@ -21,7 +23,10 @@ export const Route = createFileRoute( label: "Integrations", link: linkOptions({ to: "/secret-manager/$projectId/integrations", - params + params, + search: { + selectedTab: IntegrationsListPageTabs.NativeIntegrations + } }) }, { diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index ca3a45155..968e098cf 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -60,6 +60,7 @@ import { Route as organizationKmsOverviewPageRouteImport } from './pages/organiz import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages/organization/IdentityDetailsByIDPage/route' import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route' import { Route as organizationCertManagerOverviewPageRouteImport } from './pages/organization/CertManagerOverviewPage/route' +import { Route as organizationAppConnectionsAppConnectionsPageRouteImport } from './pages/organization/AppConnections/AppConnectionsPage/route' import { Route as projectAccessControlPageRouteSshImport } from './pages/project/AccessControlPage/route-ssh' import { Route as projectAccessControlPageRouteSecretManagerImport } from './pages/project/AccessControlPage/route-secret-manager' import { Route as projectAccessControlPageRouteKmsImport } from './pages/project/AccessControlPage/route-kms' @@ -207,6 +208,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId', )() +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport = + createFileRoute( + '/_authenticate/_inject-org-details/_org-layout/organization/app-connections', + )() const AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/kms/$projectId', )() @@ -440,6 +445,14 @@ const AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRoute = getParentRoute: () => organizationLayoutRoute, } as any) +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport.update({ + id: '/app-connections', + path: '/app-connections', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + } as any) + const AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRoute = AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdImport.update({ id: '/kms/$projectId', @@ -612,6 +625,14 @@ const organizationCertManagerOverviewPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationAppConnectionsAppConnectionsPageRouteRoute = + organizationAppConnectionsAppConnectionsPageRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute, + } as any) + const projectAccessControlPageRouteSshRoute = projectAccessControlPageRouteSshImport.update({ id: '/access-management', @@ -918,10 +939,10 @@ const secretManagerIntegrationsDetailsByIDPageRouteRoute = const organizationAppConnectionsOauthCallbackPageRouteRoute = organizationAppConnectionsOauthCallbackPageRouteImport.update({ - id: '/app-connections/$appConnection/oauth/callback', - path: '/app-connections/$appConnection/oauth/callback', + id: '/$appConnection/oauth/callback', + path: '/$appConnection/oauth/callback', getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute, } as any) const certManagerCertAuthDetailsByIDPageRouteRoute = @@ -1852,6 +1873,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdImport parentRoute: typeof organizationLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' + path: '/app-connections' + fullPath: '/organization/app-connections' + preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' path: '/secret-manager/$projectId' @@ -1866,6 +1894,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdImport parentRoute: typeof organizationLayoutImport } + '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' + path: '/' + fullPath: '/organization/app-connections/' + preLoaderRoute: typeof organizationAppConnectionsAppConnectionsPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport + } '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': { id: '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' path: '/cert-manager/overview' @@ -2141,10 +2176,10 @@ declare module '@tanstack/react-router' { } '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback': { id: '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback' - path: '/app-connections/$appConnection/oauth/callback' + path: '/$appConnection/oauth/callback' fullPath: '/organization/app-connections/$appConnection/oauth/callback' preLoaderRoute: typeof organizationAppConnectionsOauthCallbackPageRouteImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport } '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId': { id: '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId' @@ -2834,6 +2869,24 @@ const AuthenticateInjectOrgDetailsOrgLayoutIntegrationsRouteWithChildren = AuthenticateInjectOrgDetailsOrgLayoutIntegrationsRouteChildren, ) +interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren { + organizationAppConnectionsAppConnectionsPageRouteRoute: typeof organizationAppConnectionsAppConnectionsPageRouteRoute + organizationAppConnectionsOauthCallbackPageRouteRoute: typeof organizationAppConnectionsOauthCallbackPageRouteRoute +} + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren = + { + organizationAppConnectionsAppConnectionsPageRouteRoute: + organizationAppConnectionsAppConnectionsPageRouteRoute, + organizationAppConnectionsOauthCallbackPageRouteRoute: + organizationAppConnectionsOauthCallbackPageRouteRoute, + } + +const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren = + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute._addFileChildren( + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren, + ) + interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute organizationAdminPageRouteRoute: typeof organizationAdminPageRouteRoute @@ -2843,6 +2896,7 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationSecretScanningPageRouteRoute: typeof organizationSecretScanningPageRouteRoute organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute organizationSettingsPageRouteRoute: typeof organizationSettingsPageRouteRoute + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren organizationCertManagerOverviewPageRouteRoute: typeof organizationCertManagerOverviewPageRouteRoute organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute organizationIdentityDetailsByIDPageRouteRoute: typeof organizationIdentityDetailsByIDPageRouteRoute @@ -2851,7 +2905,6 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationRoleByIDPageRouteRoute: typeof organizationRoleByIDPageRouteRoute organizationSecretManagerOverviewPageRouteRoute: typeof organizationSecretManagerOverviewPageRouteRoute organizationSshOverviewPageRouteRoute: typeof organizationSshOverviewPageRouteRoute - organizationAppConnectionsOauthCallbackPageRouteRoute: typeof organizationAppConnectionsOauthCallbackPageRouteRoute } const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren = @@ -2867,6 +2920,8 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationSecretSharingPageRouteRoute: organizationSecretSharingPageRouteRoute, organizationSettingsPageRouteRoute: organizationSettingsPageRouteRoute, + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: + AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, organizationCertManagerOverviewPageRouteRoute: organizationCertManagerOverviewPageRouteRoute, organizationGroupDetailsByIDPageRouteRoute: @@ -2882,8 +2937,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationSecretManagerOverviewPageRouteRoute, organizationSshOverviewPageRouteRoute: organizationSshOverviewPageRouteRoute, - organizationAppConnectionsOauthCallbackPageRouteRoute: - organizationAppConnectionsOauthCallbackPageRouteRoute, } const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteWithChildren = @@ -3539,8 +3592,10 @@ export interface FileRoutesByFullPath { '/organization/settings': typeof organizationSettingsPageRouteRoute '/cert-manager/$projectId': typeof certManagerLayoutRouteWithChildren '/kms/$projectId': typeof kmsLayoutRouteWithChildren + '/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren '/ssh/$projectId': typeof sshLayoutRouteWithChildren + '/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3708,6 +3763,7 @@ export interface FileRoutesByTo { '/kms/$projectId': typeof kmsLayoutRouteWithChildren '/secret-manager/$projectId': typeof secretManagerLayoutRouteWithChildren '/ssh/$projectId': typeof sshLayoutRouteWithChildren + '/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -3882,8 +3938,10 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutCertManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/kms/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutKmsProjectIdRouteWithChildren + '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSshProjectIdRouteWithChildren + '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview': typeof organizationCertManagerOverviewPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId': typeof organizationIdentityDetailsByIDPageRouteRoute @@ -4059,8 +4117,10 @@ export interface FileRouteTypes { | '/organization/settings' | '/cert-manager/$projectId' | '/kms/$projectId' + | '/organization/app-connections' | '/secret-manager/$projectId' | '/ssh/$projectId' + | '/organization/app-connections/' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4227,6 +4287,7 @@ export interface FileRouteTypes { | '/kms/$projectId' | '/secret-manager/$projectId' | '/ssh/$projectId' + | '/organization/app-connections' | '/organization/cert-manager/overview' | '/organization/groups/$groupId' | '/organization/identities/$identityId' @@ -4399,8 +4460,10 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/cert-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/kms/$projectId' + | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/ssh/$projectId' + | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' | '/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview' | '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' | '/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId' @@ -4759,6 +4822,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/secret-scanning", "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/settings", + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections", "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview", "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId", "/_authenticate/_inject-org-details/_org-layout/organization/identities/$identityId", @@ -4766,8 +4830,7 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/members/$membershipId", "/_authenticate/_inject-org-details/_org-layout/organization/roles/$roleId", "/_authenticate/_inject-org-details/_org-layout/organization/secret-manager/overview", - "/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview", - "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback" + "/_authenticate/_inject-org-details/_org-layout/organization/ssh/overview" ] }, "/_authenticate/_inject-org-details/admin/_admin-layout": { @@ -4827,6 +4890,14 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/kms/$projectId/_kms-layout" ] }, + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections": { + "filePath": "", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization", + "children": [ + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/", + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback" + ] + }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout", @@ -4841,6 +4912,10 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/ssh/$projectId/_ssh-layout" ] }, + "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/": { + "filePath": "organization/AppConnections/AppConnectionsPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/app-connections" + }, "/_authenticate/_inject-org-details/_org-layout/organization/cert-manager/overview": { "filePath": "organization/CertManagerOverviewPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -5119,7 +5194,7 @@ export const routeTree = rootRoute }, "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback": { "filePath": "organization/AppConnections/OauthCallbackPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization" + "parent": "/_authenticate/_inject-org-details/_org-layout/organization/app-connections" }, "/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/_secret-manager-layout/integrations/$integrationId": { "filePath": "secret-manager/IntegrationsDetailsByIDPage/route.tsx", diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 8cdd4087c..c284f816f 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -24,11 +24,13 @@ const organizationRoutes = route("/organization", [ route("/members/$membershipId", "organization/UserDetailsByIDPage/route.tsx"), route("/roles/$roleId", "organization/RoleByIDPage/route.tsx"), route("/identities/$identityId", "organization/IdentityDetailsByIDPage/route.tsx"), - - route( - "/app-connections/$appConnection/oauth/callback", - "organization/AppConnections/OauthCallbackPage/route.tsx" - ) + route("/app-connections", [ + index("organization/AppConnections/AppConnectionsPage/route.tsx"), + route( + "/$appConnection/oauth/callback", + "organization/AppConnections/OauthCallbackPage/route.tsx" + ) + ]) ]); const secretManagerRoutes = route("/secret-manager/$projectId", [ From c4da0305bafc94f43641b25dbbf08b1ffae0bf96 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 11 Feb 2025 16:19:37 -0800 Subject: [PATCH 35/41] improvement: supress eslint error and improve text --- .../components/MembersTab/components/AddMemberModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index 0bf666b4b..4926cc982 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -164,6 +164,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { helperText="You can invite new users to your organzation by typing out their email address" > ( <>

@@ -171,7 +172,9 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => {

All organization members are already assigned to this project.

)}

-

Add new users to your organization by typing out their email address.

+

+ Invite new users to your organization by typing out their email address. +

)} onCreateOption={(inputValue) => From 72780c61b4537c5bca69da4b462ffe68b16b39e6 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 11 Feb 2025 16:37:25 -0800 Subject: [PATCH 36/41] fix: check create member permission for invite ability --- .../MembersTab/components/AddMemberModal.tsx | 116 +++++++++++------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx index 4926cc982..9afedcbc5 100644 --- a/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx +++ b/frontend/src/pages/project/AccessControlPage/components/MembersTab/components/AddMemberModal.tsx @@ -7,7 +7,13 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { Button, FilterableSelect, FormControl, Modal, ModalContent } from "@app/components/v2"; import { CreatableSelect } from "@app/components/v2/CreatableSelect"; -import { useOrganization, useWorkspace } from "@app/context"; +import { + OrgPermissionActions, + OrgPermissionSubjects, + useOrganization, + useOrgPermission, + useWorkspace +} from "@app/context"; import { useAddUsersToOrg, useGetOrgUsers, @@ -42,6 +48,7 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { const { t } = useTranslation(); const { currentOrg } = useOrganization(); const { currentWorkspace } = useWorkspace(); + const { permission } = useOrgPermission(); const orgId = currentOrg?.id || ""; const workspaceId = currentWorkspace?.id || ""; @@ -140,6 +147,11 @@ export const AddMemberModal = ({ popUp, handlePopUpToggle }: Props) => { const { append } = useFieldArray({ control, name: "orgMemberships" }); + const canInviteNewMembers = permission.can( + OrgPermissionActions.Create, + OrgPermissionSubjects.Member + ); + return ( { isError={!!errors.orgMemberships?.length} errorText={errors.orgMemberships?.[0]?.message} label="Invite users to project" - helperText="You can invite new users to your organzation by typing out their email address" + helperText={ + canInviteNewMembers + ? "You can invite new users to your organization by typing out their email address" + : undefined + } > - ( - <> -

- {!filteredOrgUsers.length && ( -

All organization members are already assigned to this project.

- )} -

-

- Invite new users to your organization by typing out their email address. -

- - )} - onCreateOption={(inputValue) => - append({ label: inputValue, value: inputValue, isNewInvitee: true }) - } - formatCreateLabel={(inputValue) => `Invite "${inputValue}"`} - isValidNewOption={(input) => - Boolean(input) && - z.string().email().safeParse(input).success && - !orgUsers - ?.flatMap(({ user }) => { - const emails: string[] = []; + {canInviteNewMembers ? ( + ( + <> +

+ {!filteredOrgUsers.length && ( +

All organization members are already assigned to this project.

+ )} +

+

+ Invite new users to your organization by typing out their email address. +

+ + )} + onCreateOption={(inputValue) => + append({ label: inputValue, value: inputValue, isNewInvitee: true }) + } + formatCreateLabel={(inputValue) => `Invite "${inputValue}"`} + isValidNewOption={(input) => + Boolean(input) && + z.string().email().safeParse(input).success && + !orgUsers + ?.flatMap(({ user }) => { + const emails: string[] = []; - if (user.email) { - emails.push(user.email); - } + if (user.email) { + emails.push(user.email); + } - if (user.username) { - emails.push(user.username); - } + if (user.username) { + emails.push(user.username); + } - return emails; - }) - .includes(input) - } - className="w-full" - placeholder="Add one or more users..." - isMulti - name="members" - options={filteredOrgUsers} - value={field.value} - onChange={field.onChange} - /> + return emails; + }) + .includes(input) + } + className="w-full" + placeholder="Add one or more users..." + isMulti + name="members" + options={filteredOrgUsers} + value={field.value} + onChange={field.onChange} + /> + ) : ( + + )} )} /> From f0b6382f925db32f63bf6765edbe3704cd0caad1 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Tue, 11 Feb 2025 22:41:32 -0500 Subject: [PATCH 37/41] Revert "Revert "Root encrypted data to kms encryption"" --- backend/e2e-test/vitest-environment-knex.ts | 36 +- backend/package.json | 28 +- backend/src/@types/fastify.d.ts | 6 + backend/src/auto-start-migrations.ts | 105 ++++ backend/src/db/instance.ts | 9 + backend/src/db/knexfile.ts | 6 +- .../20250210101840_webhook-to-kms.ts | 127 +++++ ...250210101841_dynamic-secret-root-to-kms.ts | 108 ++++ .../20250210101841_secret-rotation-to-kms.ts | 100 ++++ .../20250210101842_identity-k8-auth-to-kms.ts | 190 +++++++ ...0250210101842_identity-oidc-auth-to-kms.ts | 138 +++++ .../20250210101845_directory-config-to-kms.ts | 484 ++++++++++++++++++ backend/src/db/migrations/utils/env-config.ts | 53 ++ backend/src/db/migrations/utils/kms.ts | 105 ---- .../src/db/migrations/utils/ring-buffer.ts | 19 + backend/src/db/migrations/utils/services.ts | 52 ++ backend/src/db/rename-migrations-to-mjs.ts | 56 ++ backend/src/db/schemas/dynamic-secrets.ts | 11 +- .../db/schemas/identity-kubernetes-auths.ts | 18 +- backend/src/db/schemas/identity-oidc-auths.ts | 11 +- backend/src/db/schemas/ldap-configs.ts | 25 +- backend/src/db/schemas/oidc-configs.ts | 18 +- backend/src/db/schemas/saml-configs.ts | 7 +- backend/src/db/schemas/secret-rotations.ts | 5 +- backend/src/db/schemas/webhooks.ts | 8 +- backend/src/ee/routes/v1/ldap-router.ts | 7 +- backend/src/ee/routes/v1/oidc-router.ts | 36 +- .../ee/routes/v1/project-template-router.ts | 2 +- backend/src/ee/routes/v1/saml-router.ts | 6 +- .../v1/user-additional-privilege-router.ts | 2 +- ...ity-project-additional-privilege-router.ts | 2 +- .../dynamic-secret-lease-dal.ts | 12 +- .../dynamic-secret-lease-queue.ts | 48 +- .../dynamic-secret-lease-service.ts | 46 +- .../dynamic-secret/dynamic-secret-service.ts | 51 +- backend/src/ee/services/hsm/hsm-fns.ts | 10 +- backend/src/ee/services/hsm/hsm-service.ts | 33 +- ...project-additional-privilege-v2-service.ts | 2 +- ...ty-project-additional-privilege-service.ts | 2 +- .../ldap-config/ldap-config-service.ts | 169 +----- .../ee/services/oidc/oidc-config-service.ts | 155 +----- .../services/permission/project-permission.ts | 2 +- .../project-template-service.ts | 2 +- .../project-template-types.ts | 2 +- ...oject-user-additional-privilege-service.ts | 2 +- .../saml-config/saml-config-service.ts | 200 ++------ .../secret-rotation-queue.ts | 49 +- .../secret-rotation-service.ts | 21 +- .../secret-snapshot-service.ts | 2 + .../services/secret-snapshot/snapshot-dal.ts | 2 +- backend/src/keystore/keystore.ts | 6 + backend/src/keystore/memory.ts | 38 ++ backend/src/lib/config/env.ts | 3 +- backend/src/lib/logger/logger.ts | 2 +- backend/src/main.ts | 66 ++- backend/src/server/app.ts | 7 +- backend/src/server/routes/index.ts | 44 +- .../sanitizedSchema/directory-config.ts | 42 ++ .../identitiy-additional-privilege.ts | 0 .../permission.ts | 0 .../user-additional-privilege.ts | 0 backend/src/server/routes/sanitizedSchemas.ts | 7 +- .../v1/identity-kubernetes-auth-router.ts | 20 +- .../routes/v1/identity-oidc-auth-router.ts | 18 +- .../identity-kubernetes-auth-service.ts | 223 ++------ .../identity-oidc-auth-service.ts | 157 +----- .../src/services/kms/kms-root-config-dal.ts | 16 +- backend/src/services/kms/kms-service.ts | 83 ++- .../project-role/project-role-service.ts | 2 +- backend/src/services/project/project-types.ts | 13 +- backend/src/services/secret/secret-queue.ts | 13 +- .../super-admin/super-admin-service.ts | 24 +- backend/src/services/webhook/webhook-fns.ts | 42 +- .../src/services/webhook/webhook-service.ts | 43 +- backend/tsconfig.json | 4 +- docker-compose.dev.yml | 20 +- docker-compose.prod.yml | 17 +- 77 files changed, 2205 insertions(+), 1295 deletions(-) create mode 100644 backend/src/auto-start-migrations.ts create mode 100644 backend/src/db/migrations/20250210101840_webhook-to-kms.ts create mode 100644 backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts create mode 100644 backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts create mode 100644 backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts create mode 100644 backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts create mode 100644 backend/src/db/migrations/20250210101845_directory-config-to-kms.ts create mode 100644 backend/src/db/migrations/utils/env-config.ts delete mode 100644 backend/src/db/migrations/utils/kms.ts create mode 100644 backend/src/db/migrations/utils/ring-buffer.ts create mode 100644 backend/src/db/migrations/utils/services.ts create mode 100644 backend/src/db/rename-migrations-to-mjs.ts create mode 100644 backend/src/keystore/memory.ts create mode 100644 backend/src/server/routes/sanitizedSchema/directory-config.ts rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/identitiy-additional-privilege.ts (100%) rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/permission.ts (100%) rename backend/src/server/routes/{santizedSchemas => sanitizedSchema}/user-additional-privilege.ts (100%) diff --git a/backend/e2e-test/vitest-environment-knex.ts b/backend/e2e-test/vitest-environment-knex.ts index 58f2bffeb..9dfb38aeb 100644 --- a/backend/e2e-test/vitest-environment-knex.ts +++ b/backend/e2e-test/vitest-environment-knex.ts @@ -23,14 +23,14 @@ export default { name: "knex-env", transformMode: "ssr", async setup() { - const logger = await initLogger(); - const cfg = initEnvConfig(logger); + const logger = initLogger(); + const envConfig = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: cfg.DB_CONNECTION_URI, - dbRootCert: cfg.DB_ROOT_CERT + dbConnectionUri: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT }); - const redis = new Redis(cfg.REDIS_URL); + const redis = new Redis(envConfig.REDIS_URL); await redis.flushdb("SYNC"); try { @@ -42,6 +42,7 @@ export default { }, true ); + await db.migrate.latest({ directory: path.join(__dirname, "../src/db/migrations"), extension: "ts", @@ -52,14 +53,24 @@ export default { directory: path.join(__dirname, "../src/db/seeds"), extension: "ts" }); - const smtp = mockSmtpServer(); - const queue = queueServiceFactory(cfg.REDIS_URL, { dbConnectionUrl: cfg.DB_CONNECTION_URI }); - const keyStore = keyStoreFactory(cfg.REDIS_URL); - const hsmModule = initializeHsmModule(); + const smtp = mockSmtpServer(); + const queue = queueServiceFactory(envConfig.REDIS_URL, { dbConnectionUrl: envConfig.DB_CONNECTION_URI }); + const keyStore = keyStoreFactory(envConfig.REDIS_URL); + + const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); - const server = await main({ db, smtp, logger, queue, keyStore, hsmModule: hsmModule.getModule(), redis }); + const server = await main({ + db, + smtp, + logger, + queue, + keyStore, + hsmModule: hsmModule.getModule(), + redis, + envConfig + }); // @ts-expect-error type globalThis.testServer = server; @@ -73,8 +84,8 @@ export default { organizationId: seedData1.organization.id, accessVersion: 1 }, - cfg.AUTH_SECRET, - { expiresIn: cfg.JWT_AUTH_LIFETIME } + envConfig.AUTH_SECRET, + { expiresIn: envConfig.JWT_AUTH_LIFETIME } ); } catch (error) { // eslint-disable-next-line @@ -109,3 +120,4 @@ export default { }; } }; + diff --git a/backend/package.json b/backend/package.json index 43409e619..4f8647eab 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,21 +45,21 @@ "test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts", "generate:component": "tsx ./scripts/create-backend-file.ts", "generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas", - "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest", - "auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up", - "auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down", - "auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list", - "auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status", - "auditlog-migration:unlock": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:unlock", - "auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback", + "auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.mjs --client pg migrate:latest", + "auditlog-migration:up": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:up", + "auditlog-migration:down": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:down", + "auditlog-migration:list": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:list", + "auditlog-migration:status": "knex --knexfile ./dist/db/auditlog-knexfile.mjs --client pg migrate:status", + "auditlog-migration:unlock": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:unlock", + "auditlog-migration:rollback": "knex --knexfile ./dist/db/auditlog-knexfile.mjs migrate:rollback", "migration:new": "tsx ./scripts/create-migration.ts", - "migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up", - "migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down", - "migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list", - "migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest", - "migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status", - "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback", - "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./src/db/knexfile.ts migrate:unlock", + "migration:up": "npm run auditlog-migration:up && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:up", + "migration:down": "npm run auditlog-migration:down && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:down", + "migration:list": "npm run auditlog-migration:list && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:list", + "migration:latest": "node ./dist/db/rename-migrations-to-mjs.mjs && npm run auditlog-migration:latest && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:latest", + "migration:status": "npm run auditlog-migration:status && knex --knexfile ./dist/db/knexfile.mjs --client pg migrate:status", + "migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./dist/db/knexfile.mjs migrate:rollback", + "migration:unlock": "npm run auditlog-migration:unlock && knex --knexfile ./dist/db/knexfile.mjs migrate:unlock", "migrate:org": "tsx ./scripts/migrate-organization.ts", "seed:new": "tsx ./scripts/create-seed-file.ts", "seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run", diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts index f3298625e..c02347038 100644 --- a/backend/src/@types/fastify.d.ts +++ b/backend/src/@types/fastify.d.ts @@ -93,6 +93,12 @@ import { TUserEngagementServiceFactory } from "@app/services/user-engagement/use import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service"; import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service"; +declare module "@fastify/request-context" { + interface RequestContextData { + reqId: string; + } +} + declare module "fastify" { interface Session { callbackPort: string; diff --git a/backend/src/auto-start-migrations.ts b/backend/src/auto-start-migrations.ts new file mode 100644 index 000000000..88f6dea69 --- /dev/null +++ b/backend/src/auto-start-migrations.ts @@ -0,0 +1,105 @@ +import path from "node:path"; + +import dotenv from "dotenv"; +import { Knex } from "knex"; +import { Logger } from "pino"; + +import { PgSqlLock } from "./keystore/keystore"; + +dotenv.config(); + +type TArgs = { + auditLogDb?: Knex; + applicationDb: Knex; + logger: Logger; +}; + +const isProduction = process.env.NODE_ENV === "production"; +const migrationConfig = { + directory: path.join(__dirname, "./db/migrations"), + loadExtensions: [".mjs", ".ts"], + tableName: "infisical_migrations" +}; + +const migrationStatusCheckErrorHandler = (err: Error) => { + // happens for first time in which the migration table itself is not created yet + // error: select * from "infisical_migrations" - relation "infisical_migrations" does not exist + if (err?.message?.includes("does not exist")) { + return true; + } + throw err; +}; + +export const runMigrations = async ({ applicationDb, auditLogDb, logger }: TArgs) => { + try { + // akhilmhdh(Feb 10 2025): 2 years from now remove this + if (isProduction) { + const migrationTable = migrationConfig.tableName; + const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); + if (hasMigrationTable) { + const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await applicationDb(migrationTable).update({ + name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + if (auditLogDb) { + const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); + if (hasMigrationTableInAuditLog) { + const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await auditLogDb(migrationTable).update({ + name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + } + } + + const shouldRunMigration = Boolean( + await applicationDb.migrate.status(migrationConfig).catch(migrationStatusCheckErrorHandler) + ); // db.length - code.length + if (!shouldRunMigration) { + logger.info("No migrations pending: Skipping migration process."); + return; + } + + if (auditLogDb) { + await auditLogDb.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); + logger.info("Running audit log migrations."); + + const didPreviousInstanceRunMigration = !(await auditLogDb.migrate + .status(migrationConfig) + .catch(migrationStatusCheckErrorHandler)); + if (didPreviousInstanceRunMigration) { + logger.info("No audit log migrations pending: Applied by previous instance. Skipping migration process."); + return; + } + + await auditLogDb.migrate.latest(migrationConfig); + logger.info("Finished audit log migrations."); + }); + } + + await applicationDb.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.BootUpMigration]); + logger.info("Running application migrations."); + + const didPreviousInstanceRunMigration = !(await applicationDb.migrate + .status(migrationConfig) + .catch(migrationStatusCheckErrorHandler)); + if (didPreviousInstanceRunMigration) { + logger.info("No application migrations pending: Applied by previous instance. Skipping migration process."); + return; + } + + await applicationDb.migrate.latest(migrationConfig); + logger.info("Finished application migrations."); + }); + } catch (err) { + logger.error(err, "Boot up migration failed"); + process.exit(1); + } +}; diff --git a/backend/src/db/instance.ts b/backend/src/db/instance.ts index d4a2a5b2c..5a8dd3d05 100644 --- a/backend/src/db/instance.ts +++ b/backend/src/db/instance.ts @@ -49,6 +49,9 @@ export const initDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); @@ -64,6 +67,9 @@ export const initDbConnection = ({ ca: Buffer.from(replicaDbCertificate, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); }); @@ -98,6 +104,9 @@ export const initAuditLogDbConnection = ({ ca: Buffer.from(dbRootCert, "base64").toString("ascii") } : false + }, + migrations: { + tableName: "infisical_migrations" } }); diff --git a/backend/src/db/knexfile.ts b/backend/src/db/knexfile.ts index 8af2b59ab..8cf80b744 100644 --- a/backend/src/db/knexfile.ts +++ b/backend/src/db/knexfile.ts @@ -38,7 +38,8 @@ export default { directory: "./seeds" }, migrations: { - tableName: "infisical_migrations" + tableName: "infisical_migrations", + loadExtensions: [".mjs"] } }, production: { @@ -62,7 +63,8 @@ export default { max: 10 }, migrations: { - tableName: "infisical_migrations" + tableName: "infisical_migrations", + loadExtensions: [".mjs"] } } } as Knex.Config; diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts new file mode 100644 index 000000000..c9b8e7fec --- /dev/null +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -0,0 +1,127 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); + const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + const hasUrl = await knex.schema.hasColumn(TableName.Webhook, "url"); + + const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (!hasEncryptedKey) t.binary("encryptedPassKey"); + if (!hasEncryptedUrl) t.binary("encryptedUrl"); + if (hasUrl) t.string("url").nullable().alter(); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + createCircularCache>>(25); + const webhooks = await knex(TableName.Webhook) + .where({}) + .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) + .select( + "url", + "encryptedSecretKey", + "iv", + "tag", + "keyEncoding", + "urlCipherText", + "urlIV", + "urlTag", + knex.ref("id").withSchema(TableName.Webhook), + "envId" + ) + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); + + const updatedWebhooks = await Promise.all( + webhooks.map(async (el) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: el.projectId + }, knex); + projectEncryptionRingBuffer.push(el.projectId, projectKmsService); + } + + let encryptedSecretKey = null; + if (el.encryptedSecretKey && el.iv && el.tag && el.keyEncoding) { + const decyptedSecretKey = infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.iv, + tag: el.tag, + ciphertext: el.encryptedSecretKey + }); + encryptedSecretKey = projectKmsService.encryptor({ + plainText: Buffer.from(decyptedSecretKey, "utf8") + }).cipherTextBlob; + } + + const decryptedUrl = + el.urlIV && el.urlTag && el.urlCipherText && el.keyEncoding + ? infisicalSymmetricDecrypt({ + keyEncoding: el.keyEncoding as SecretKeyEncoding, + iv: el.urlIV, + tag: el.urlTag, + ciphertext: el.urlCipherText + }) + : null; + + const encryptedUrl = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedUrl || el.url || "") + }).cipherTextBlob; + return { id: el.id, encryptedUrl, encryptedSecretKey, envId: el.envId }; + }) + ); + + for (let i = 0; i < updatedWebhooks.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.Webhook) + .insert( + updatedWebhooks.slice(i, i + BATCH_SIZE).map((el) => ({ + id: el.id, + envId: el.envId, + url: "", + encryptedUrl: el.encryptedUrl, + encryptedPassKey: el.encryptedSecretKey + })) + ) + .onConflict("id") + .merge(); + } + + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (!hasEncryptedUrl) t.binary("encryptedUrl").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedKey = await knex.schema.hasColumn(TableName.Webhook, "encryptedPassKey"); + const hasEncryptedUrl = await knex.schema.hasColumn(TableName.Webhook, "encryptedUrl"); + + const hasWebhookTable = await knex.schema.hasTable(TableName.Webhook); + if (hasWebhookTable) { + await knex.schema.alterTable(TableName.Webhook, (t) => { + if (hasEncryptedKey) t.dropColumn("encryptedPassKey"); + if (hasEncryptedUrl) t.dropColumn("encryptedUrl"); + }); + } +} diff --git a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts new file mode 100644 index 000000000..41dc6ba9f --- /dev/null +++ b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts @@ -0,0 +1,108 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); + const hasInputCiphertextColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputCiphertext"); + const hasInputIVColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputIV"); + const hasInputTagColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "inputTag"); + + const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (!hasEncryptedInputColumn) t.binary("encryptedInput"); + if (hasInputCiphertextColumn) t.text("inputCiphertext").nullable().alter(); + if (hasInputIVColumn) t.string("inputIV").nullable().alter(); + if (hasInputTagColumn) t.string("inputTag").nullable().alter(); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + createCircularCache>>(25); + + const dynamicSecretRootCredentials = await knex(TableName.DynamicSecret) + .join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.DynamicSecret}.folderId`) + .join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`) + .select(selectAllTableCols(TableName.DynamicSecret)) + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); + + const updatedDynamicSecrets = await Promise.all( + dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }, knex); + projectEncryptionRingBuffer.push(projectId, projectKmsService); + } + + const decryptedInputData = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.inputIV && el.inputTag && el.inputCiphertext && el.keyEncoding + ? infisicalSymmetricDecrypt({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + keyEncoding: el.keyEncoding as SecretKeyEncoding, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.inputIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.inputTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.inputCiphertext + }) + : ""; + + const encryptedInput = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedInputData) + }).cipherTextBlob; + + return { ...el, encryptedInput }; + }) + ); + + for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.DynamicSecret) + .insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (!hasEncryptedInputColumn) t.binary("encryptedInput").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedInputColumn = await knex.schema.hasColumn(TableName.DynamicSecret, "encryptedInput"); + + const hasDynamicSecretTable = await knex.schema.hasTable(TableName.DynamicSecret); + if (hasDynamicSecretTable) { + await knex.schema.alterTable(TableName.DynamicSecret, (t) => { + if (hasEncryptedInputColumn) t.dropColumn("encryptedInput"); + }); + } +} diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts new file mode 100644 index 000000000..567cace99 --- /dev/null +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -0,0 +1,100 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +export async function up(knex: Knex): Promise { + const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + + const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (!hasEncryptedRotationData) t.binary("encryptedRotationData"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const projectEncryptionRingBuffer = + createCircularCache>>(25); + + const secretRotations = await knex(TableName.SecretRotation) + .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretRotation}.envId`) + .select(selectAllTableCols(TableName.SecretRotation)) + .select(knex.ref("projectId").withSchema(TableName.Environment)) + .orderBy(`${TableName.Environment}.projectId` as "projectId"); + + const updatedRotationData = await Promise.all( + secretRotations.map(async ({ projectId, ...el }) => { + let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); + if (!projectKmsService) { + projectKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }, knex); + projectEncryptionRingBuffer.push(projectId, projectKmsService); + } + + const decryptedRotationData = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedDataTag && el.encryptedDataIV && el.encryptedData && el.keyEncoding + ? infisicalSymmetricDecrypt({ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + keyEncoding: el.keyEncoding as SecretKeyEncoding, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.encryptedDataIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.encryptedDataTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedData + }) + : ""; + + const encryptedRotationData = projectKmsService.encryptor({ + plainText: Buffer.from(decryptedRotationData) + }).cipherTextBlob; + return { ...el, encryptedRotationData }; + }) + ); + + for (let i = 0; i < updatedRotationData.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.SecretRotation) + .insert(updatedRotationData.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (!hasEncryptedRotationData) t.binary("encryptedRotationData").notNullable().alter(); + }); + } +} + +export async function down(knex: Knex): Promise { + const hasEncryptedRotationData = await knex.schema.hasColumn(TableName.SecretRotation, "encryptedRotationData"); + + const hasRotationTable = await knex.schema.hasTable(TableName.SecretRotation); + if (hasRotationTable) { + await knex.schema.alterTable(TableName.SecretRotation, (t) => { + if (hasEncryptedRotationData) t.dropColumn("encryptedRotationData"); + }); + } +} diff --git a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts new file mode 100644 index 000000000..3d62ab04f --- /dev/null +++ b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts @@ -0,0 +1,190 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptIdentityK8sAuth = async (knex: Knex) => { + const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesTokenReviewerJwt" + ); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesCaCertificate" + ); + const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); + + const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "encryptedCaCert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "caCertTag"); + const hasEncryptedTokenReviewerJwtColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedTokenReviewerJwt" + ); + const hasTokenReviewerJwtIVColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "tokenReviewerJwtIV" + ); + const hasTokenReviewerJwtTagColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "tokenReviewerJwtTag" + ); + + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + if (hasEncryptedTokenReviewerJwtColumn) t.text("encryptedTokenReviewerJwt").nullable().alter(); + if (hasTokenReviewerJwtIVColumn) t.string("tokenReviewerJwtIV").nullable().alter(); + if (hasTokenReviewerJwtTagColumn) t.string("tokenReviewerJwtTag").nullable().alter(); + + if (!hasEncryptedKubernetesTokenReviewerJwt) t.binary("encryptedKubernetesTokenReviewerJwt"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedKubernetesCaCertificate"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + createCircularCache>>(25); + const identityKubernetesConfigs = await knex(TableName.IdentityKubernetesAuth) + .join( + TableName.IdentityOrgMembership, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityKubernetesAuth}.identityId` + ) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) + .select(selectAllTableCols(TableName.IdentityKubernetesAuth)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), + knex.ref("orgId").withSchema(TableName.OrgBot) + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); + + const updatedIdentityKubernetesConfigs = []; + + for (const { encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el } of identityKubernetesConfigs) { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId + }, knex); + orgEncryptionRingBuffer.push(orgId, orgKmsService); + } + + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedTokenReviewerJwt = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.tokenReviewerJwtIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.tokenReviewerJwtTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedTokenReviewerJwt + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedTokenReviewerJwt) + }).cipherTextBlob; + const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + updatedIdentityKubernetesConfigs.push({ + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedKubernetesCaCertificate, + encryptedKubernetesTokenReviewerJwt + }); + } + + for (let i = 0; i < updatedIdentityKubernetesConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.IdentityKubernetesAuth) + .insert(updatedIdentityKubernetesConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (!hasEncryptedKubernetesTokenReviewerJwt) + t.binary("encryptedKubernetesTokenReviewerJwt").notNullable().alter(); + }); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptIdentityK8sAuth(knex); +} + +const dropIdentityK8sColumns = async (knex: Knex) => { + const hasEncryptedKubernetesTokenReviewerJwt = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesTokenReviewerJwt" + ); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityKubernetesAuth, + "encryptedKubernetesCaCertificate" + ); + const hasidentityKubernetesAuthTable = await knex.schema.hasTable(TableName.IdentityKubernetesAuth); + + if (hasidentityKubernetesAuthTable) { + await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (t) => { + if (hasEncryptedKubernetesTokenReviewerJwt) t.dropColumn("encryptedKubernetesTokenReviewerJwt"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedKubernetesCaCertificate"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropIdentityK8sColumns(knex); +} diff --git a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts new file mode 100644 index 000000000..dc87726a4 --- /dev/null +++ b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts @@ -0,0 +1,138 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptIdentityOidcAuth = async (knex: Knex) => { + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityOidcAuth, + "encryptedCaCertificate" + ); + const hasidentityOidcAuthTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); + + const hasEncryptedCaCertColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "encryptedCaCert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "caCertTag"); + + if (hasidentityOidcAuthTable) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + if (hasEncryptedCaCertColumn) t.text("encryptedCaCert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + + if (!hasEncryptedCertificateColumn) t.binary("encryptedCaCertificate"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + createCircularCache>>(25); + + const identityOidcConfig = await knex(TableName.IdentityOidcAuth) + .join( + TableName.IdentityOrgMembership, + `${TableName.IdentityOrgMembership}.identityId`, + `${TableName.IdentityOidcAuth}.identityId` + ) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.IdentityOrgMembership}.orgId`) + .select(selectAllTableCols(TableName.IdentityOidcAuth)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot), + knex.ref("orgId").withSchema(TableName.OrgBot) + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); + + const updatedIdentityOidcConfigs = await Promise.all( + identityOidcConfig.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId + }, knex); + orgEncryptionRingBuffer.push(orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + return { + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedCaCertificate + }; + } + ) + ); + + for (let i = 0; i < updatedIdentityOidcConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.IdentityOidcAuth) + .insert(updatedIdentityOidcConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptIdentityOidcAuth(knex); +} + +const dropIdentityOidcColumns = async (knex: Knex) => { + const hasEncryptedCertificateColumn = await knex.schema.hasColumn( + TableName.IdentityOidcAuth, + "encryptedCaCertificate" + ); + const hasidentityOidcTable = await knex.schema.hasTable(TableName.IdentityOidcAuth); + + if (hasidentityOidcTable) { + await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => { + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedCaCertificate"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropIdentityOidcColumns(knex); +} diff --git a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts new file mode 100644 index 000000000..05db40958 --- /dev/null +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -0,0 +1,484 @@ +import { Knex } from "knex"; + +import { inMemoryKeyStore } from "@app/keystore/memory"; +import { decryptSymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { selectAllTableCols } from "@app/lib/knex"; +import { initLogger } from "@app/lib/logger"; +import { KmsDataKey } from "@app/services/kms/kms-types"; + +import { SecretKeyEncoding, TableName } from "../schemas"; +import { getMigrationEnvConfig } from "./utils/env-config"; +import { createCircularCache } from "./utils/ring-buffer"; +import { getMigrationEncryptionServices } from "./utils/services"; + +const BATCH_SIZE = 500; +const reencryptSamlConfig = async (knex: Knex) => { + const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); + const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); + const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint"); + if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + createCircularCache>>(25); + + const samlConfigs = await knex(TableName.SamlConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.SamlConfig}.orgId`) + .select(selectAllTableCols(TableName.SamlConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); + + const updatedSamlConfigs = await Promise.all( + samlConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }, knex); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedEntryPoint = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedEntryPoint && el.entryPointIV && el.entryPointTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.entryPointIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.entryPointTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedEntryPoint + }) + : ""; + + const decryptedIssuer = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedIssuer && el.issuerIV && el.issuerTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.issuerIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.issuerTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedIssuer + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCert && el.certIV && el.certTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.certIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.certTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCert + }) + : ""; + + const encryptedSamlIssuer = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedIssuer) + }).cipherTextBlob; + const encryptedSamlCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + const encryptedSamlEntryPoint = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedEntryPoint) + }).cipherTextBlob; + return { ...el, encryptedSamlCertificate, encryptedSamlEntryPoint, encryptedSamlIssuer }; + } + ) + ); + + for (let i = 0; i < updatedSamlConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.SamlConfig) + .insert(updatedSamlConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (!hasEncryptedEntrypointColumn) t.binary("encryptedSamlEntryPoint").notNullable().alter(); + if (!hasEncryptedIssuerColumn) t.binary("encryptedSamlIssuer").notNullable().alter(); + if (!hasEncryptedCertificateColumn) t.binary("encryptedSamlCertificate").notNullable().alter(); + }); + } +}; + +const reencryptLdapConfig = async (knex: Knex) => { + const hasEncryptedLdapBindDNColum = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); + const hasEncryptedLdapBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); + const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); + + const hasEncryptedCACertColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedCACert"); + const hasCaCertIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertIV"); + const hasCaCertTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "caCertTag"); + const hasEncryptedBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindPass"); + const hasBindPassIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassIV"); + const hasBindPassTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindPassTag"); + const hasEncryptedBindDNColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedBindDN"); + const hasBindDNIVColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNIV"); + const hasBindDNTagColumn = await knex.schema.hasColumn(TableName.LdapConfig, "bindDNTag"); + + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (hasEncryptedCACertColumn) t.text("encryptedCACert").nullable().alter(); + if (hasCaCertIVColumn) t.string("caCertIV").nullable().alter(); + if (hasCaCertTagColumn) t.string("caCertTag").nullable().alter(); + if (hasEncryptedBindPassColumn) t.string("encryptedBindPass").nullable().alter(); + if (hasBindPassIVColumn) t.string("bindPassIV").nullable().alter(); + if (hasBindPassTagColumn) t.string("bindPassTag").nullable().alter(); + if (hasEncryptedBindDNColumn) t.string("encryptedBindDN").nullable().alter(); + if (hasBindDNIVColumn) t.string("bindDNIV").nullable().alter(); + if (hasBindDNTagColumn) t.string("bindDNTag").nullable().alter(); + + if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN"); + if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass"); + if (!hasEncryptedCertificateColumn) t.binary("encryptedLdapCaCertificate"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + createCircularCache>>(25); + + const ldapConfigs = await knex(TableName.LdapConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.LdapConfig}.orgId`) + .select(selectAllTableCols(TableName.LdapConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); + + const updatedLdapConfigs = await Promise.all( + ldapConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }, knex); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedBindDN = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedBindDN && el.bindDNIV && el.bindDNTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.bindDNIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.bindDNTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedBindDN + }) + : ""; + + const decryptedBindPass = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedBindPass && el.bindPassIV && el.bindPassTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.bindPassIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.bindPassTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedBindPass + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCACert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCACert + }) + : ""; + + const encryptedLdapBindDN = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedBindDN) + }).cipherTextBlob; + const encryptedLdapBindPass = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedBindPass) + }).cipherTextBlob; + const encryptedLdapCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + return { ...el, encryptedLdapBindPass, encryptedLdapBindDN, encryptedLdapCaCertificate }; + } + ) + ); + + for (let i = 0; i < updatedLdapConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.LdapConfig) + .insert(updatedLdapConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (!hasEncryptedLdapBindPassColumn) t.binary("encryptedLdapBindPass").notNullable().alter(); + if (!hasEncryptedLdapBindDNColum) t.binary("encryptedLdapBindDN").notNullable().alter(); + }); + } +}; + +const reencryptOidcConfig = async (knex: Knex) => { + const hasEncryptedOidcClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); + const hasEncryptedOidcClientSecretColumn = await knex.schema.hasColumn( + TableName.OidcConfig, + "encryptedOidcClientSecret" + ); + + const hasEncryptedClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientId"); + const hasClientIdIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdIV"); + const hasClientIdTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientIdTag"); + const hasEncryptedClientSecretColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedClientSecret"); + const hasClientSecretIVColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretIV"); + const hasClientSecretTagColumn = await knex.schema.hasColumn(TableName.OidcConfig, "clientSecretTag"); + + const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); + + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (hasEncryptedClientIdColumn) t.text("encryptedClientId").nullable().alter(); + if (hasClientIdIVColumn) t.string("clientIdIV").nullable().alter(); + if (hasClientIdTagColumn) t.string("clientIdTag").nullable().alter(); + if (hasEncryptedClientSecretColumn) t.text("encryptedClientSecret").nullable().alter(); + if (hasClientSecretIVColumn) t.string("clientSecretIV").nullable().alter(); + if (hasClientSecretTagColumn) t.string("clientSecretTag").nullable().alter(); + + if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId"); + if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret"); + }); + } + + initLogger(); + const envConfig = getMigrationEnvConfig(); + const keyStore = inMemoryKeyStore(); + const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); + const orgEncryptionRingBuffer = + createCircularCache>>(25); + + const oidcConfigs = await knex(TableName.OidcConfig) + .join(TableName.OrgBot, `${TableName.OrgBot}.orgId`, `${TableName.OidcConfig}.orgId`) + .select(selectAllTableCols(TableName.OidcConfig)) + .select( + knex.ref("encryptedSymmetricKey").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyIV").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyTag").withSchema(TableName.OrgBot), + knex.ref("symmetricKeyKeyEncoding").withSchema(TableName.OrgBot) + ) + .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); + + const updatedOidcConfigs = await Promise.all( + oidcConfigs.map( + async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { + let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: el.orgId + }, knex); + orgEncryptionRingBuffer.push(el.orgId, orgKmsService); + } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedClientId = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedClientId && el.clientIdIV && el.clientIdTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.clientIdIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.clientIdTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedClientId + }) + : ""; + + const decryptedClientSecret = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedClientSecret && el.clientSecretIV && el.clientSecretTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.clientSecretIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.clientSecretTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedClientSecret + }) + : ""; + + const encryptedOidcClientId = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedClientId) + }).cipherTextBlob; + const encryptedOidcClientSecret = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedClientSecret) + }).cipherTextBlob; + return { ...el, encryptedOidcClientId, encryptedOidcClientSecret }; + } + ) + ); + + for (let i = 0; i < updatedOidcConfigs.length; i += BATCH_SIZE) { + // eslint-disable-next-line no-await-in-loop + await knex(TableName.OidcConfig) + .insert(updatedOidcConfigs.slice(i, i + BATCH_SIZE)) + .onConflict("id") + .merge(); + } + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (!hasEncryptedOidcClientIdColumn) t.binary("encryptedOidcClientId").notNullable().alter(); + if (!hasEncryptedOidcClientSecretColumn) t.binary("encryptedOidcClientSecret").notNullable().alter(); + }); + } +}; + +export async function up(knex: Knex): Promise { + await reencryptSamlConfig(knex); + await reencryptLdapConfig(knex); + await reencryptOidcConfig(knex); +} + +const dropSamlConfigColumns = async (knex: Knex) => { + const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); + const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); + const hasSamlConfigTable = await knex.schema.hasTable(TableName.SamlConfig); + + if (hasSamlConfigTable) { + await knex.schema.alterTable(TableName.SamlConfig, (t) => { + if (hasEncryptedEntrypointColumn) t.dropColumn("encryptedSamlEntryPoint"); + if (hasEncryptedIssuerColumn) t.dropColumn("encryptedSamlIssuer"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedSamlCertificate"); + }); + } +}; + +const dropLdapConfigColumns = async (knex: Knex) => { + const hasEncryptedBindDN = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); + const hasEncryptedBindPass = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); + const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); + const hasLdapConfigTable = await knex.schema.hasTable(TableName.LdapConfig); + + if (hasLdapConfigTable) { + await knex.schema.alterTable(TableName.LdapConfig, (t) => { + if (hasEncryptedBindDN) t.dropColumn("encryptedLdapBindDN"); + if (hasEncryptedBindPass) t.dropColumn("encryptedLdapBindPass"); + if (hasEncryptedCertificateColumn) t.dropColumn("encryptedLdapCaCertificate"); + }); + } +}; + +const dropOidcConfigColumns = async (knex: Knex) => { + const hasEncryptedClientId = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); + const hasEncryptedClientSecret = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientSecret"); + const hasOidcConfigTable = await knex.schema.hasTable(TableName.OidcConfig); + + if (hasOidcConfigTable) { + await knex.schema.alterTable(TableName.OidcConfig, (t) => { + if (hasEncryptedClientId) t.dropColumn("encryptedOidcClientId"); + if (hasEncryptedClientSecret) t.dropColumn("encryptedOidcClientSecret"); + }); + } +}; + +export async function down(knex: Knex): Promise { + await dropSamlConfigColumns(knex); + await dropLdapConfigColumns(knex); + await dropOidcConfigColumns(knex); +} diff --git a/backend/src/db/migrations/utils/env-config.ts b/backend/src/db/migrations/utils/env-config.ts new file mode 100644 index 000000000..05ccae97b --- /dev/null +++ b/backend/src/db/migrations/utils/env-config.ts @@ -0,0 +1,53 @@ +import { z } from "zod"; + +import { zpStr } from "@app/lib/zod"; + +const envSchema = z + .object({ + DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default( + `postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}` + ), + DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()), + DB_HOST: zpStr(z.string().describe("Postgres database host").optional()), + DB_PORT: zpStr(z.string().describe("Postgres database port").optional()).default("5432"), + DB_USER: zpStr(z.string().describe("Postgres database username").optional()), + DB_PASSWORD: zpStr(z.string().describe("Postgres database password").optional()), + DB_NAME: zpStr(z.string().describe("Postgres database name").optional()), + // TODO(akhilmhdh): will be changed to one + ENCRYPTION_KEY: zpStr(z.string().optional()), + ROOT_ENCRYPTION_KEY: zpStr(z.string().optional()), + // HSM + 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) + }) + // 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: + Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined + })); + +export type TMigrationEnvConfig = z.infer; + +export const getMigrationEnvConfig = () => { + const parsedEnv = envSchema.safeParse(process.env); + if (!parsedEnv.success) { + // eslint-disable-next-line no-console + console.error("Invalid environment variables. Check the error below"); + // eslint-disable-next-line no-console + console.error( + "Migration is now automatic at startup. Please remove this step from your workflow and start the application as normal." + ); + // eslint-disable-next-line no-console + console.error(parsedEnv.error.issues); + process.exit(-1); + } + + return Object.freeze(parsedEnv.data); +}; diff --git a/backend/src/db/migrations/utils/kms.ts b/backend/src/db/migrations/utils/kms.ts deleted file mode 100644 index 9ed090978..000000000 --- a/backend/src/db/migrations/utils/kms.ts +++ /dev/null @@ -1,105 +0,0 @@ -import slugify from "@sindresorhus/slugify"; -import { Knex } from "knex"; - -import { TableName } from "@app/db/schemas"; -import { randomSecureBytes } from "@app/lib/crypto"; -import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; -import { alphaNumericNanoId } from "@app/lib/nanoid"; - -const getInstanceRootKey = async (knex: Knex) => { - const encryptionKey = process.env.ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; - // if root key its base64 encoded - const isBase64 = !process.env.ENCRYPTION_KEY; - if (!encryptionKey) throw new Error("ENCRYPTION_KEY variable needed for migration"); - const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8"); - - const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000"; - const kmsRootConfig = await knex(TableName.KmsServerRootConfig).where({ id: KMS_ROOT_CONFIG_UUID }).first(); - const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); - if (kmsRootConfig) { - const decryptedRootKey = cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer); - // set the flag so that other instancen nodes can start - return decryptedRootKey; - } - - const newRootKey = randomSecureBytes(32); - const encryptedRootKey = cipher.encrypt(newRootKey, encryptionKeyBuffer); - await knex(TableName.KmsServerRootConfig).insert({ - encryptedRootKey, - // eslint-disable-next-line - // @ts-ignore id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID - }); - return encryptedRootKey; -}; - -export const getSecretManagerDataKey = async (knex: Knex, projectId: string) => { - const KMS_VERSION = "v01"; - const KMS_VERSION_BLOB_LENGTH = 3; - const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); - const project = await knex(TableName.Project).where({ id: projectId }).first(); - if (!project) throw new Error("Missing project id"); - - const ROOT_ENCRYPTION_KEY = await getInstanceRootKey(knex); - - let secretManagerKmsKey; - const projectSecretManagerKmsId = project?.kmsSecretManagerKeyId; - if (projectSecretManagerKmsId) { - const kmsDoc = await knex(TableName.KmsKey) - .leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`) - .where({ [`${TableName.KmsKey}.id` as "id"]: projectSecretManagerKmsId }) - .first(); - if (!kmsDoc) throw new Error("missing kms"); - secretManagerKmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY); - } else { - const [kmsDoc] = await knex(TableName.KmsKey) - .insert({ - name: slugify(alphaNumericNanoId(8).toLowerCase()), - orgId: project.orgId, - isReserved: false - }) - .returning("*"); - - secretManagerKmsKey = randomSecureBytes(32); - const encryptedKeyMaterial = cipher.encrypt(secretManagerKmsKey, ROOT_ENCRYPTION_KEY); - await knex(TableName.InternalKms).insert({ - version: 1, - encryptedKey: encryptedKeyMaterial, - encryptionAlgorithm: SymmetricEncryption.AES_GCM_256, - kmsKeyId: kmsDoc.id - }); - } - - const encryptedSecretManagerDataKey = project?.kmsSecretManagerEncryptedDataKey; - let dataKey: Buffer; - if (!encryptedSecretManagerDataKey) { - dataKey = randomSecureBytes(); - // the below versioning we do it automatically in kms service - const unversionedDataKey = cipher.encrypt(dataKey, secretManagerKmsKey); - const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 - await knex(TableName.Project) - .where({ id: projectId }) - .update({ - kmsSecretManagerEncryptedDataKey: Buffer.concat([unversionedDataKey, versionBlob]) - }); - } else { - const cipherTextBlob = encryptedSecretManagerDataKey.subarray(0, -KMS_VERSION_BLOB_LENGTH); - dataKey = cipher.decrypt(cipherTextBlob, secretManagerKmsKey); - } - - return { - encryptor: ({ plainText }: { plainText: Buffer }) => { - const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey); - - // Buffer#1 encrypted text + Buffer#2 version number - const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3 - const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]); - return { cipherTextBlob }; - }, - decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: { cipherTextBlob: Buffer }) => { - const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH); - const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey); - return decryptedBlob; - } - }; -}; diff --git a/backend/src/db/migrations/utils/ring-buffer.ts b/backend/src/db/migrations/utils/ring-buffer.ts new file mode 100644 index 000000000..8e5c58662 --- /dev/null +++ b/backend/src/db/migrations/utils/ring-buffer.ts @@ -0,0 +1,19 @@ +export const createCircularCache = (bufferSize = 10) => { + const bufferItems: { id: string; item: T }[] = []; + let bufferIndex = 0; + + const push = (id: string, item: T) => { + if (bufferItems.length < bufferSize) { + bufferItems.push({ id, item }); + } else { + bufferItems[bufferIndex] = { id, item }; + } + bufferIndex = (bufferIndex + 1) % bufferSize; + }; + + const getItem = (id: string) => { + return bufferItems.find((i) => i.id === id)?.item; + }; + + return { push, getItem }; +}; diff --git a/backend/src/db/migrations/utils/services.ts b/backend/src/db/migrations/utils/services.ts new file mode 100644 index 000000000..731f703e2 --- /dev/null +++ b/backend/src/db/migrations/utils/services.ts @@ -0,0 +1,52 @@ +import { Knex } from "knex"; + +import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; +import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; +import { TKeyStoreFactory } from "@app/keystore/keystore"; +import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; +import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; +import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; +import { kmsServiceFactory } from "@app/services/kms/kms-service"; +import { orgDALFactory } from "@app/services/org/org-dal"; +import { projectDALFactory } from "@app/services/project/project-dal"; + +import { TMigrationEnvConfig } from "./env-config"; + +type TDependencies = { + envConfig: TMigrationEnvConfig; + db: Knex; + keyStore: TKeyStoreFactory; +}; + +export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { + // eslint-disable-next-line no-param-reassign + const hsmModule = initializeHsmModule(envConfig); + hsmModule.initialize(); + + const hsmService = hsmServiceFactory({ + hsmModule: hsmModule.getModule(), + envConfig + }); + + const orgDAL = orgDALFactory(db); + const kmsRootConfigDAL = kmsRootConfigDALFactory(db); + const kmsDAL = kmskeyDALFactory(db); + const internalKmsDAL = internalKmsDALFactory(db); + const projectDAL = projectDALFactory(db); + + const kmsService = kmsServiceFactory({ + kmsRootConfigDAL, + keyStore, + kmsDAL, + internalKmsDAL, + orgDAL, + projectDAL, + hsmService, + envConfig + }); + + await hsmService.startService(); + await kmsService.startService(); + + return { kmsService }; +}; diff --git a/backend/src/db/rename-migrations-to-mjs.ts b/backend/src/db/rename-migrations-to-mjs.ts new file mode 100644 index 000000000..d09b5097d --- /dev/null +++ b/backend/src/db/rename-migrations-to-mjs.ts @@ -0,0 +1,56 @@ +import path from "node:path"; + +import dotenv from "dotenv"; + +import { initAuditLogDbConnection, initDbConnection } from "./instance"; + +const isProduction = process.env.NODE_ENV === "production"; + +// Update with your config settings. . +dotenv.config({ + path: path.join(__dirname, "../../../.env.migration") +}); +dotenv.config({ + path: path.join(__dirname, "../../../.env") +}); + +const runRename = async () => { + if (!isProduction) return; + const migrationTable = "infisical_migrations"; + const applicationDb = initDbConnection({ + dbConnectionUri: process.env.DB_CONNECTION_URI as string, + dbRootCert: process.env.DB_ROOT_CERT + }); + + const auditLogDb = process.env.AUDIT_LOGS_DB_CONNECTION_URI + ? initAuditLogDbConnection({ + dbConnectionUri: process.env.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: process.env.AUDIT_LOGS_DB_ROOT_CERT + }) + : undefined; + + const hasMigrationTable = await applicationDb.schema.hasTable(migrationTable); + if (hasMigrationTable) { + const firstFile = (await applicationDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await applicationDb(migrationTable).update({ + name: applicationDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + if (auditLogDb) { + const hasMigrationTableInAuditLog = await auditLogDb.schema.hasTable(migrationTable); + if (hasMigrationTableInAuditLog) { + const firstFile = (await auditLogDb(migrationTable).where({}).first()) as { name: string }; + if (firstFile?.name?.includes(".ts")) { + await auditLogDb(migrationTable).update({ + name: auditLogDb.raw("REPLACE(name, '.ts', '.mjs')") + }); + } + } + } + await applicationDb.destroy(); + await auditLogDb?.destroy(); +}; + +void runRename(); diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index b27da396c..eaddea8fe 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const DynamicSecretsSchema = z.object({ @@ -14,16 +16,17 @@ export const DynamicSecretsSchema = z.object({ type: z.string(), defaultTTL: z.string(), maxTTL: z.string().nullable().optional(), - inputIV: z.string(), - inputCiphertext: z.string(), - inputTag: z.string(), + inputIV: z.string().nullable().optional(), + inputCiphertext: z.string().nullable().optional(), + inputTag: z.string().nullable().optional(), algorithm: z.string().default("aes-256-gcm"), keyEncoding: z.string().default("utf8"), folderId: z.string().uuid(), status: z.string().nullable().optional(), statusDetails: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedInput: zodBuffer }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/identity-kubernetes-auths.ts b/backend/src/db/schemas/identity-kubernetes-auths.ts index ed99dec86..85f210ff1 100644 --- a/backend/src/db/schemas/identity-kubernetes-auths.ts +++ b/backend/src/db/schemas/identity-kubernetes-auths.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const IdentityKubernetesAuthsSchema = z.object({ @@ -17,15 +19,17 @@ export const IdentityKubernetesAuthsSchema = z.object({ updatedAt: z.date(), identityId: z.string().uuid(), kubernetesHost: z.string(), - encryptedCaCert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), - encryptedTokenReviewerJwt: z.string(), - tokenReviewerJwtIV: z.string(), - tokenReviewerJwtTag: z.string(), + encryptedCaCert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), + encryptedTokenReviewerJwt: z.string().nullable().optional(), + tokenReviewerJwtIV: z.string().nullable().optional(), + tokenReviewerJwtTag: z.string().nullable().optional(), allowedNamespaces: z.string(), allowedNames: z.string(), - allowedAudience: z.string() + allowedAudience: z.string(), + encryptedKubernetesTokenReviewerJwt: zodBuffer, + encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() }); export type TIdentityKubernetesAuths = z.infer; diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts index 3d7d38c41..ebde5e7dc 100644 --- a/backend/src/db/schemas/identity-oidc-auths.ts +++ b/backend/src/db/schemas/identity-oidc-auths.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const IdentityOidcAuthsSchema = z.object({ @@ -15,15 +17,16 @@ export const IdentityOidcAuthsSchema = z.object({ accessTokenTrustedIps: z.unknown(), identityId: z.string().uuid(), oidcDiscoveryUrl: z.string(), - encryptedCaCert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), + encryptedCaCert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), boundIssuer: z.string(), boundAudiences: z.string(), boundClaims: z.unknown(), boundSubject: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedCaCertificate: zodBuffer.nullable().optional() }); export type TIdentityOidcAuths = z.infer; diff --git a/backend/src/db/schemas/ldap-configs.ts b/backend/src/db/schemas/ldap-configs.ts index 460c2cff6..778e7be6e 100644 --- a/backend/src/db/schemas/ldap-configs.ts +++ b/backend/src/db/schemas/ldap-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const LdapConfigsSchema = z.object({ @@ -12,22 +14,25 @@ export const LdapConfigsSchema = z.object({ orgId: z.string().uuid(), isActive: z.boolean(), url: z.string(), - encryptedBindDN: z.string(), - bindDNIV: z.string(), - bindDNTag: z.string(), - encryptedBindPass: z.string(), - bindPassIV: z.string(), - bindPassTag: z.string(), + encryptedBindDN: z.string().nullable().optional(), + bindDNIV: z.string().nullable().optional(), + bindDNTag: z.string().nullable().optional(), + encryptedBindPass: z.string().nullable().optional(), + bindPassIV: z.string().nullable().optional(), + bindPassTag: z.string().nullable().optional(), searchBase: z.string(), - encryptedCACert: z.string(), - caCertIV: z.string(), - caCertTag: z.string(), + encryptedCACert: z.string().nullable().optional(), + caCertIV: z.string().nullable().optional(), + caCertTag: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), groupSearchBase: z.string().default(""), groupSearchFilter: z.string().default(""), searchFilter: z.string().default(""), - uniqueUserAttribute: z.string().default("") + uniqueUserAttribute: z.string().default(""), + encryptedLdapBindDN: zodBuffer, + encryptedLdapBindPass: zodBuffer, + encryptedLdapCaCertificate: zodBuffer.nullable().optional() }); export type TLdapConfigs = z.infer; diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts index d7bf2f00f..76923aee8 100644 --- a/backend/src/db/schemas/oidc-configs.ts +++ b/backend/src/db/schemas/oidc-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const OidcConfigsSchema = z.object({ @@ -15,20 +17,22 @@ export const OidcConfigsSchema = z.object({ jwksUri: z.string().nullable().optional(), tokenEndpoint: z.string().nullable().optional(), userinfoEndpoint: z.string().nullable().optional(), - encryptedClientId: z.string(), + encryptedClientId: z.string().nullable().optional(), configurationType: z.string(), - clientIdIV: z.string(), - clientIdTag: z.string(), - encryptedClientSecret: z.string(), - clientSecretIV: z.string(), - clientSecretTag: z.string(), + clientIdIV: z.string().nullable().optional(), + clientIdTag: z.string().nullable().optional(), + encryptedClientSecret: z.string().nullable().optional(), + clientSecretIV: z.string().nullable().optional(), + clientSecretTag: z.string().nullable().optional(), allowedEmailDomains: z.string().nullable().optional(), isActive: z.boolean(), createdAt: z.date(), updatedAt: z.date(), orgId: z.string().uuid(), lastUsed: z.date().nullable().optional(), - manageGroupMemberships: z.boolean().default(false) + manageGroupMemberships: z.boolean().default(false), + encryptedOidcClientId: zodBuffer, + encryptedOidcClientSecret: zodBuffer }); export type TOidcConfigs = z.infer; diff --git a/backend/src/db/schemas/saml-configs.ts b/backend/src/db/schemas/saml-configs.ts index 67171469a..350e84492 100644 --- a/backend/src/db/schemas/saml-configs.ts +++ b/backend/src/db/schemas/saml-configs.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const SamlConfigsSchema = z.object({ @@ -23,7 +25,10 @@ export const SamlConfigsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), orgId: z.string().uuid(), - lastUsed: z.date().nullable().optional() + lastUsed: z.date().nullable().optional(), + encryptedSamlEntryPoint: zodBuffer, + encryptedSamlIssuer: zodBuffer, + encryptedSamlCertificate: zodBuffer }); export type TSamlConfigs = z.infer; diff --git a/backend/src/db/schemas/secret-rotations.ts b/backend/src/db/schemas/secret-rotations.ts index b491edc46..a3cd04ebb 100644 --- a/backend/src/db/schemas/secret-rotations.ts +++ b/backend/src/db/schemas/secret-rotations.ts @@ -5,6 +5,8 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const SecretRotationsSchema = z.object({ @@ -22,7 +24,8 @@ export const SecretRotationsSchema = z.object({ keyEncoding: z.string().nullable().optional(), envId: z.string().uuid(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + encryptedRotationData: zodBuffer }); export type TSecretRotations = z.infer; diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index a7aac2933..60f031fff 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -5,12 +5,14 @@ import { z } from "zod"; +import { zodBuffer } from "@app/lib/zod"; + import { TImmutableDBKeys } from "./models"; export const WebhooksSchema = z.object({ id: z.string().uuid(), secretPath: z.string().default("/"), - url: z.string(), + url: z.string().nullable().optional(), lastStatus: z.string().nullable().optional(), lastRunErrorMessage: z.string().nullable().optional(), isDisabled: z.boolean().default(false), @@ -25,7 +27,9 @@ export const WebhooksSchema = z.object({ urlCipherText: z.string().nullable().optional(), urlIV: z.string().nullable().optional(), urlTag: z.string().nullable().optional(), - type: z.string().default("general").nullable().optional() + type: z.string().default("general").nullable().optional(), + encryptedPassKey: zodBuffer.nullable().optional(), + encryptedUrl: zodBuffer }); export type TWebhooks = z.infer; diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index 735ba632c..2057677cf 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -14,7 +14,7 @@ import { FastifyRequest } from "fastify"; import LdapStrategy from "passport-ldapauth"; import { z } from "zod"; -import { LdapConfigsSchema, LdapGroupMapsSchema } from "@app/db/schemas"; +import { LdapGroupMapsSchema } from "@app/db/schemas"; import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types"; import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; import { getConfig } from "@app/lib/config/env"; @@ -22,6 +22,7 @@ import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { SanitizedLdapConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerLdapRouter = async (server: FastifyZodProvider) => { @@ -187,7 +188,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { caCert: z.string().trim().default("") }), response: { - 200: LdapConfigsSchema + 200: SanitizedLdapConfigSchema } }, handler: async (req) => { @@ -228,7 +229,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: LdapConfigsSchema + 200: SanitizedLdapConfigSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/oidc-router.ts b/backend/src/ee/routes/v1/oidc-router.ts index 71daa3446..df5c61fe4 100644 --- a/backend/src/ee/routes/v1/oidc-router.ts +++ b/backend/src/ee/routes/v1/oidc-router.ts @@ -11,13 +11,28 @@ import fastifySession from "@fastify/session"; import RedisStore from "connect-redis"; import { z } from "zod"; -import { OidcConfigsSchema } from "@app/db/schemas/oidc-configs"; +import { OidcConfigsSchema } from "@app/db/schemas"; import { OIDCConfigurationType } from "@app/ee/services/oidc/oidc-config-types"; import { getConfig } from "@app/lib/config/env"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ + id: true, + issuer: true, + authorizationEndpoint: true, + configurationType: true, + discoveryURL: true, + jwksUri: true, + tokenEndpoint: true, + userinfoEndpoint: true, + orgId: true, + isActive: true, + allowedEmailDomains: true, + manageGroupMemberships: true +}); + export const registerOidcRouter = async (server: FastifyZodProvider) => { const appCfg = getConfig(); const passport = new Authenticator({ key: "oidc", userProperty: "passportUser" }); @@ -142,7 +157,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { orgSlug: z.string().trim() }), response: { - 200: OidcConfigsSchema.pick({ + 200: SanitizedOidcConfigSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -214,7 +229,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ orgSlug: z.string() })), response: { - 200: OidcConfigsSchema.pick({ + 200: SanitizedOidcConfigSchema.pick({ id: true, issuer: true, authorizationEndpoint: true, @@ -327,20 +342,7 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => { } }), response: { - 200: OidcConfigsSchema.pick({ - id: true, - issuer: true, - authorizationEndpoint: true, - configurationType: true, - discoveryURL: true, - jwksUri: true, - tokenEndpoint: true, - userinfoEndpoint: true, - orgId: true, - isActive: true, - allowedEmailDomains: true, - manageGroupMemberships: true - }) + 200: SanitizedOidcConfigSchema } }, diff --git a/backend/src/ee/routes/v1/project-template-router.ts b/backend/src/ee/routes/v1/project-template-router.ts index 60f93d65d..cabf65337 100644 --- a/backend/src/ee/routes/v1/project-template-router.ts +++ b/backend/src/ee/routes/v1/project-template-router.ts @@ -9,7 +9,7 @@ import { ProjectTemplates } from "@app/lib/api-docs"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { AuthMode } from "@app/services/auth/auth-type"; const MAX_JSON_SIZE_LIMIT_IN_BYTES = 32_768; diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 933015a66..71facb22a 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -12,13 +12,13 @@ import { MultiSamlStrategy } from "@node-saml/passport-saml"; import { FastifyRequest } from "fastify"; import { z } from "zod"; -import { SamlConfigsSchema } from "@app/db/schemas"; import { SamlProviders, TGetSamlCfgDTO } from "@app/ee/services/saml-config/saml-config-types"; import { getConfig } from "@app/lib/config/env"; import { BadRequestError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { SanitizedSamlConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config"; import { AuthMode } from "@app/services/auth/auth-type"; type TSAMLConfig = { @@ -298,7 +298,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { cert: z.string() }), response: { - 200: SamlConfigsSchema + 200: SanitizedSamlConfigSchema } }, handler: async (req) => { @@ -333,7 +333,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { .partial() .merge(z.object({ organizationId: z.string() })), response: { - 200: SamlConfigsSchema + 200: SanitizedSamlConfigSchema } }, handler: async (req) => { diff --git a/backend/src/ee/routes/v1/user-additional-privilege-router.ts b/backend/src/ee/routes/v1/user-additional-privilege-router.ts index bb3e179dd..de37a4cde 100644 --- a/backend/src/ee/routes/v1/user-additional-privilege-router.ts +++ b/backend/src/ee/routes/v1/user-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege"; +import { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/sanitizedSchema/user-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts index 7934c3f90..d9c3a05b5 100644 --- a/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts +++ b/backend/src/ee/routes/v2/identity-project-additional-privilege-router.ts @@ -9,7 +9,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { slugSchema } from "@app/server/lib/schemas"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege"; +import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchema/identitiy-additional-privilege"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts index 810628030..e9f00f401 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-dal.ts @@ -37,11 +37,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { db.ref("type").withSchema(TableName.DynamicSecret).as("dynType"), db.ref("defaultTTL").withSchema(TableName.DynamicSecret).as("dynDefaultTTL"), db.ref("maxTTL").withSchema(TableName.DynamicSecret).as("dynMaxTTL"), - db.ref("inputIV").withSchema(TableName.DynamicSecret).as("dynInputIV"), - db.ref("inputTag").withSchema(TableName.DynamicSecret).as("dynInputTag"), - db.ref("inputCiphertext").withSchema(TableName.DynamicSecret).as("dynInputCiphertext"), - db.ref("algorithm").withSchema(TableName.DynamicSecret).as("dynAlgorithm"), - db.ref("keyEncoding").withSchema(TableName.DynamicSecret).as("dynKeyEncoding"), + db.ref("encryptedInput").withSchema(TableName.DynamicSecret).as("dynEncryptedInput"), db.ref("folderId").withSchema(TableName.DynamicSecret).as("dynFolderId"), db.ref("status").withSchema(TableName.DynamicSecret).as("dynStatus"), db.ref("statusDetails").withSchema(TableName.DynamicSecret).as("dynStatusDetails"), @@ -59,11 +55,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => { type: doc.dynType, defaultTTL: doc.dynDefaultTTL, maxTTL: doc.dynMaxTTL, - inputIV: doc.dynInputIV, - inputTag: doc.dynInputTag, - inputCiphertext: doc.dynInputCiphertext, - algorithm: doc.dynAlgorithm, - keyEncoding: doc.dynKeyEncoding, + encryptedInput: doc.dynEncryptedInput, folderId: doc.dynFolderId, status: doc.dynStatus, statusDetails: doc.dynStatusDetails, diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts index 9bdb1c24e..fa1a80ac3 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-queue.ts @@ -1,8 +1,10 @@ -import { SecretKeyEncoding } from "@app/db/schemas"; import { DisableRotationErrors } from "@app/ee/services/secret-rotation/secret-rotation-queue"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; +import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; +import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal"; import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types"; @@ -14,6 +16,8 @@ type TDynamicSecretLeaseQueueServiceFactoryDep = { dynamicSecretLeaseDAL: Pick; dynamicSecretDAL: Pick; dynamicSecretProviders: Record; + kmsService: Pick; + folderDAL: Pick; }; export type TDynamicSecretLeaseQueueServiceFactory = ReturnType; @@ -22,7 +26,9 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ queueService, dynamicSecretDAL, dynamicSecretProviders, - dynamicSecretLeaseDAL + dynamicSecretLeaseDAL, + kmsService, + folderDAL }: TDynamicSecretLeaseQueueServiceFactoryDep) => { const pruneDynamicSecret = async (dynamicSecretCfgId: string) => { await queueService.queue( @@ -76,15 +82,21 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId); if (!dynamicSecretLease) throw new DisableRotationErrors({ message: "Dynamic secret lease not found" }); + const folder = await folderDAL.findById(dynamicSecretLease.dynamicSecret.folderId); + if (!folder) + throw new NotFoundError({ + message: `Failed to find folder with ${dynamicSecretLease.dynamicSecret.folderId}` + }); + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: folder.projectId + }); + const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; await selectedProvider.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId); @@ -100,16 +112,22 @@ export const dynamicSecretLeaseQueueServiceFactory = ({ if ((dynamicSecretCfg.status as DynamicSecretStatus) !== DynamicSecretStatus.Deleting) throw new DisableRotationErrors({ message: "Document not deleted" }); + const folder = await folderDAL.findById(dynamicSecretCfg.folderId); + if (!folder) + throw new NotFoundError({ + message: `Failed to find folder with ${dynamicSecretCfg.folderId}` + }); + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: folder.projectId + }); + const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfgId }); if (dynamicSecretLeases.length) { const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id))); diff --git a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts index 830c1aa57..39e8ae7e2 100644 --- a/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts +++ b/backend/src/ee/services/dynamic-secret-lease/dynamic-secret-lease-service.ts @@ -1,7 +1,7 @@ import { ForbiddenError, subject } from "@casl/ability"; import ms from "ms"; -import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; +import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { @@ -9,9 +9,10 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { getConfig } from "@app/lib/config/env"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -37,6 +38,7 @@ type TDynamicSecretLeaseServiceFactoryDep = { folderDAL: Pick; permissionService: Pick; projectDAL: Pick; + kmsService: Pick; }; export type TDynamicSecretLeaseServiceFactory = ReturnType; @@ -49,7 +51,8 @@ export const dynamicSecretLeaseServiceFactory = ({ permissionService, dynamicSecretQueueService, projectDAL, - licenseService + licenseService, + kmsService }: TDynamicSecretLeaseServiceFactoryDep) => { const create = async ({ environmentSlug, @@ -104,13 +107,14 @@ export const dynamicSecretLeaseServiceFactory = ({ throw new BadRequestError({ message: `Max lease limit reached. Limit: ${appCfg.MAX_LEASE_LIMIT}` }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; + + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -160,6 +164,11 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const plan = await licenseService.getPlan(actorOrgId); if (!plan?.dynamicSecret) { throw new BadRequestError({ @@ -181,12 +190,7 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const selectedTTL = ttl || dynamicSecretCfg.defaultTTL; @@ -240,6 +244,11 @@ export const dynamicSecretLeaseServiceFactory = ({ subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path }) ); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); if (!folder) throw new NotFoundError({ @@ -253,12 +262,7 @@ export const dynamicSecretLeaseServiceFactory = ({ const dynamicSecretCfg = dynamicSecretLease.dynamicSecret; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: Buffer.from(dynamicSecretCfg.encryptedInput) }).toString() ) as object; const revokeResponse = await selectedProvider diff --git a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts index 631d5b6ba..eac5e2ecf 100644 --- a/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts +++ b/backend/src/ee/services/dynamic-secret/dynamic-secret-service.ts @@ -1,15 +1,16 @@ import { ForbiddenError, subject } from "@casl/ability"; -import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas"; +import { ActionProjectType } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionDynamicSecretActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrderByDirection, OrgServiceActor } from "@app/lib/types"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; @@ -42,6 +43,7 @@ type TDynamicSecretServiceFactoryDep = { folderDAL: Pick; projectDAL: Pick; permissionService: Pick; + kmsService: Pick; }; export type TDynamicSecretServiceFactory = ReturnType; @@ -54,7 +56,8 @@ export const dynamicSecretServiceFactory = ({ dynamicSecretProviders, permissionService, dynamicSecretQueueService, - projectDAL + projectDAL, + kmsService }: TDynamicSecretServiceFactoryDep) => { const create = async ({ path, @@ -108,16 +111,15 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(provider.inputs); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); - const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(inputs)); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const dynamicSecretCfg = await dynamicSecretDAL.create({ type: provider.type, version: 1, - inputIV: encryptedInput.iv, - inputTag: encryptedInput.tag, - inputCiphertext: encryptedInput.ciphertext, - algorithm: encryptedInput.algorithm, - keyEncoding: encryptedInput.encoding, + encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(inputs)) }).cipherTextBlob, maxTTL, defaultTTL, folderId: folder.id, @@ -180,15 +182,15 @@ export const dynamicSecretServiceFactory = ({ if (existingDynamicSecret) throw new BadRequestError({ message: "Provided dynamic secret already exist under the folder" }); } + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; const newInput = { ...decryptedStoredInput, ...(inputs || {}) }; const updatedInput = await selectedProvider.validateProviderInputs(newInput); @@ -196,13 +198,8 @@ export const dynamicSecretServiceFactory = ({ const isConnected = await selectedProvider.validateConnection(newInput); if (!isConnected) throw new BadRequestError({ message: "Provider connection failed" }); - const encryptedInput = infisicalSymmetricEncypt(JSON.stringify(updatedInput)); const updatedDynamicCfg = await dynamicSecretDAL.updateById(dynamicSecretCfg.id, { - inputIV: encryptedInput.iv, - inputTag: encryptedInput.tag, - inputCiphertext: encryptedInput.ciphertext, - algorithm: encryptedInput.algorithm, - keyEncoding: encryptedInput.encoding, + encryptedInput: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(updatedInput)) }).cipherTextBlob, maxTTL, defaultTTL, name: newName ?? name, @@ -315,13 +312,13 @@ export const dynamicSecretServiceFactory = ({ if (!dynamicSecretCfg) { throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` }); } + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const decryptedStoredInput = JSON.parse( - infisicalSymmetricDecrypt({ - keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding, - ciphertext: dynamicSecretCfg.inputCiphertext, - tag: dynamicSecretCfg.inputTag, - iv: dynamicSecretCfg.inputIV - }) + secretManagerDecryptor({ cipherTextBlob: dynamicSecretCfg.encryptedInput }).toString() ) as object; const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders]; const providerInputs = (await selectedProvider.validateProviderInputs(decryptedStoredInput)) as object; diff --git a/backend/src/ee/services/hsm/hsm-fns.ts b/backend/src/ee/services/hsm/hsm-fns.ts index 3124e1012..ef975a371 100644 --- a/backend/src/ee/services/hsm/hsm-fns.ts +++ b/backend/src/ee/services/hsm/hsm-fns.ts @@ -1,25 +1,23 @@ import * as pkcs11js from "pkcs11js"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmModule } from "./hsm-types"; -export const initializeHsmModule = () => { - const appCfg = getConfig(); - +export const initializeHsmModule = (envConfig: Pick) => { // Create a new instance of PKCS11 module const pkcs11 = new pkcs11js.PKCS11(); let isInitialized = false; const initialize = () => { - if (!appCfg.isHsmConfigured) { + if (!envConfig.isHsmConfigured) { return; } try { // Load the PKCS#11 module - pkcs11.load(appCfg.HSM_LIB_PATH!); + pkcs11.load(envConfig.HSM_LIB_PATH!); // Initialize the module pkcs11.C_Initialize(); diff --git a/backend/src/ee/services/hsm/hsm-service.ts b/backend/src/ee/services/hsm/hsm-service.ts index a1a0773fc..d35d17a24 100644 --- a/backend/src/ee/services/hsm/hsm-service.ts +++ b/backend/src/ee/services/hsm/hsm-service.ts @@ -1,12 +1,13 @@ import pkcs11js from "pkcs11js"; -import { getConfig } from "@app/lib/config/env"; +import { TEnvConfig } from "@app/lib/config/env"; import { logger } from "@app/lib/logger"; import { HsmKeyType, HsmModule } from "./hsm-types"; type THsmServiceFactoryDep = { hsmModule: HsmModule; + envConfig: Pick; }; export type THsmServiceFactory = ReturnType; @@ -15,9 +16,7 @@ type SyncOrAsync = T | Promise; type SessionCallback = (session: pkcs11js.Handle) => SyncOrAsync; // eslint-disable-next-line no-empty-pattern -export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsmServiceFactoryDep) => { - const appCfg = getConfig(); - +export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envConfig }: THsmServiceFactoryDep) => { // Constants for buffer structures const IV_LENGTH = 16; // Luna HSM typically expects 16-byte IV for cbc const BLOCK_SIZE = 16; @@ -63,11 +62,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm throw new Error("No slots available"); } - if (appCfg.HSM_SLOT >= slots.length) { - throw new Error(`HSM slot ${appCfg.HSM_SLOT} not found or not initialized`); + if (envConfig.HSM_SLOT >= slots.length) { + throw new Error(`HSM slot ${envConfig.HSM_SLOT} not found or not initialized`); } - const slotId = slots[appCfg.HSM_SLOT]; + const slotId = slots[envConfig.HSM_SLOT]; const startTime = Date.now(); while (Date.now() - startTime < MAX_TIMEOUT) { @@ -78,7 +77,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm // Login try { - pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, appCfg.HSM_PIN); + pkcs11.C_Login(sessionHandle, pkcs11js.CKU_USER, envConfig.HSM_PIN); logger.info("HSM: Successfully authenticated"); break; } catch (error) { @@ -86,7 +85,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm if (error instanceof pkcs11js.Pkcs11Error) { if (error.code === pkcs11js.CKR_PIN_INCORRECT) { // We throw instantly here to prevent further attempts, because if too many attempts are made, the HSM will potentially wipe all key material - logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${appCfg.HSM_SLOT}`); + logger.error(error, `HSM: Incorrect PIN detected for HSM slot ${envConfig.HSM_SLOT}`); throw new Error("HSM: Incorrect HSM Pin detected. Please check the HSM configuration."); } if (error.code === pkcs11js.CKR_USER_ALREADY_LOGGED_IN) { @@ -133,7 +132,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm }; const $findKey = (sessionHandle: pkcs11js.Handle, type: HsmKeyType) => { - const label = type === HsmKeyType.HMAC ? `${appCfg.HSM_KEY_LABEL}_HMAC` : appCfg.HSM_KEY_LABEL; + const label = type === HsmKeyType.HMAC ? `${envConfig.HSM_KEY_LABEL}_HMAC` : envConfig.HSM_KEY_LABEL; const keyType = type === HsmKeyType.HMAC ? pkcs11js.CKK_GENERIC_SECRET : pkcs11js.CKK_AES; const template = [ @@ -360,7 +359,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm }; const isActive = async () => { - if (!isInitialized || !appCfg.isHsmConfigured) { + if (!isInitialized || !envConfig.isHsmConfigured) { return false; } @@ -372,11 +371,11 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm logger.error(err, "HSM: Error testing PKCS#11 module"); } - return appCfg.isHsmConfigured && isInitialized && pkcs11TestPassed; + return envConfig.isHsmConfigured && isInitialized && pkcs11TestPassed; }; const startService = async () => { - if (!appCfg.isHsmConfigured || !pkcs11 || !isInitialized) return; + if (!envConfig.isHsmConfigured || !pkcs11 || !isInitialized) return; try { await $withSession(async (sessionHandle) => { @@ -395,7 +394,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_AES }, { type: pkcs11js.CKA_VALUE_LEN, value: AES_KEY_SIZE / 8 }, - { type: pkcs11js.CKA_LABEL, value: appCfg.HSM_KEY_LABEL! }, + { type: pkcs11js.CKA_LABEL, value: envConfig.HSM_KEY_LABEL! }, { type: pkcs11js.CKA_ENCRYPT, value: true }, // Allow encryption { type: pkcs11js.CKA_DECRYPT, value: true }, // Allow decryption ...genericAttributes @@ -410,7 +409,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm keyTemplate ); - logger.info(`HSM: Master key created successfully with label: ${appCfg.HSM_KEY_LABEL}`); + logger.info(`HSM: Master key created successfully with label: ${envConfig.HSM_KEY_LABEL}`); } // Check if HMAC key exists, create if not @@ -419,7 +418,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm { type: pkcs11js.CKA_CLASS, value: pkcs11js.CKO_SECRET_KEY }, { type: pkcs11js.CKA_KEY_TYPE, value: pkcs11js.CKK_GENERIC_SECRET }, { type: pkcs11js.CKA_VALUE_LEN, value: HMAC_KEY_SIZE / 8 }, // 256-bit key - { type: pkcs11js.CKA_LABEL, value: `${appCfg.HSM_KEY_LABEL!}_HMAC` }, + { type: pkcs11js.CKA_LABEL, value: `${envConfig.HSM_KEY_LABEL!}_HMAC` }, { type: pkcs11js.CKA_SIGN, value: true }, // Allow signing { type: pkcs11js.CKA_VERIFY, value: true }, // Allow verification ...genericAttributes @@ -434,7 +433,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 } }: THsm hmacKeyTemplate ); - logger.info(`HSM: HMAC key created successfully with label: ${appCfg.HSM_KEY_LABEL}_HMAC`); + logger.info(`HSM: HMAC key created successfully with label: ${envConfig.HSM_KEY_LABEL}_HMAC`); } // Get slot info to check supported mechanisms diff --git a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts index 3a38c0d65..eb9c66c1c 100644 --- a/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; +import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts index 16c0cc212..d74f9c504 100644 --- a/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts +++ b/backend/src/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index cafc7abf0..e22b18e1b 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,25 +1,18 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { OrgMembershipStatus, SecretKeyEncoding, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -59,7 +52,6 @@ type TLdapConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; - orgBotDAL: Pick; groupDAL: Pick; groupProjectDAL: Pick; projectKeyDAL: Pick; @@ -84,6 +76,7 @@ type TLdapConfigServiceFactoryDep = { licenseService: Pick; tokenService: Pick; smtpService: Pick; + kmsService: Pick; }; export type TLdapConfigServiceFactory = ReturnType; @@ -93,7 +86,6 @@ export const ldapConfigServiceFactory = ({ ldapGroupMapDAL, orgDAL, orgMembershipDAL, - orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -105,7 +97,8 @@ export const ldapConfigServiceFactory = ({ permissionService, licenseService, tokenService, - smtpService + smtpService, + kmsService }: TLdapConfigServiceFactoryDep) => { const createLdapCfg = async ({ actor, @@ -133,77 +126,23 @@ export const ldapConfigServiceFactory = ({ message: "Failed to create LDAP configuration due to plan restriction. Upgrade plan to create LDAP configuration." }); - - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); - const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const ldapConfig = await ldapConfigDAL.create({ orgId, isActive, url, - encryptedBindDN, - bindDNIV, - bindDNTag, - encryptedBindPass, - bindPassIV, - bindPassTag, uniqueUserAttribute, searchBase, searchFilter, groupSearchBase, groupSearchFilter, - encryptedCACert, - caCertIV, - caCertTag + encryptedLdapCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, + encryptedLdapBindDN: encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob, + encryptedLdapBindPass: encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob }); return ldapConfig; @@ -246,38 +185,21 @@ export const ldapConfigServiceFactory = ({ uniqueUserAttribute }; - const orgBot = await orgBotDAL.findOne({ orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot in organization with ID '${orgId}' not found`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); if (bindDN !== undefined) { - const { ciphertext: encryptedBindDN, iv: bindDNIV, tag: bindDNTag } = encryptSymmetric(bindDN, key); - updateQuery.encryptedBindDN = encryptedBindDN; - updateQuery.bindDNIV = bindDNIV; - updateQuery.bindDNTag = bindDNTag; + updateQuery.encryptedLdapBindDN = encryptor({ plainText: Buffer.from(bindDN) }).cipherTextBlob; } if (bindPass !== undefined) { - const { ciphertext: encryptedBindPass, iv: bindPassIV, tag: bindPassTag } = encryptSymmetric(bindPass, key); - updateQuery.encryptedBindPass = encryptedBindPass; - updateQuery.bindPassIV = bindPassIV; - updateQuery.bindPassTag = bindPassTag; + updateQuery.encryptedLdapBindPass = encryptor({ plainText: Buffer.from(bindPass) }).cipherTextBlob; } if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCACert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedLdapCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } const [ldapConfig] = await ldapConfigDAL.update({ orgId }, updateQuery); @@ -293,61 +215,24 @@ export const ldapConfigServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found in organization with ID ${ldapConfig.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: ldapConfig.orgId }); - const { - encryptedBindDN, - bindDNIV, - bindDNTag, - encryptedBindPass, - bindPassIV, - bindPassTag, - encryptedCACert, - caCertIV, - caCertTag - } = ldapConfig; - let bindDN = ""; - if (encryptedBindDN && bindDNIV && bindDNTag) { - bindDN = decryptSymmetric({ - ciphertext: encryptedBindDN, - key, - tag: bindDNTag, - iv: bindDNIV - }); + if (ldapConfig.encryptedLdapBindDN) { + bindDN = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindDN }).toString(); } let bindPass = ""; - if (encryptedBindPass && bindPassIV && bindPassTag) { - bindPass = decryptSymmetric({ - ciphertext: encryptedBindPass, - key, - tag: bindPassTag, - iv: bindPassIV - }); + if (ldapConfig.encryptedLdapBindPass) { + bindPass = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapBindPass }).toString(); } let caCert = ""; - if (encryptedCACert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCACert, - key, - tag: caCertTag, - iv: caCertIV - }); + if (ldapConfig.encryptedLdapCaCertificate) { + caCert = decryptor({ cipherTextBlob: ldapConfig.encryptedLdapCaCertificate }).toString(); } return { diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts index 0c037a2d3..52c8dd597 100644 --- a/backend/src/ee/services/oidc/oidc-config-service.ts +++ b/backend/src/ee/services/oidc/oidc-config-service.ts @@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; -import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; @@ -14,21 +14,14 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -70,7 +63,6 @@ type TOidcConfigServiceFactoryDep = { "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; orgMembershipDAL: Pick; - orgBotDAL: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; @@ -91,6 +83,7 @@ type TOidcConfigServiceFactoryDep = { projectDAL: Pick; projectBotDAL: Pick; auditLogService: Pick; + kmsService: Pick; }; export type TOidcConfigServiceFactory = ReturnType; @@ -103,7 +96,6 @@ export const oidcConfigServiceFactory = ({ licenseService, permissionService, tokenService, - orgBotDAL, smtpService, oidcConfigDAL, userGroupMembershipDAL, @@ -112,7 +104,8 @@ export const oidcConfigServiceFactory = ({ projectKeyDAL, projectDAL, projectBotDAL, - auditLogService + auditLogService, + kmsService }: TOidcConfigServiceFactoryDep) => { const getOidc = async (dto: TGetOidcCfgDTO) => { const org = await orgDAL.findOne({ slug: dto.orgSlug }); @@ -143,43 +136,19 @@ export const oidcConfigServiceFactory = ({ }); } - // decrypt and return cfg - const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot for organization with ID '${oidcCfg.orgId}' not found`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: oidcCfg.orgId }); - const { encryptedClientId, clientIdIV, clientIdTag, encryptedClientSecret, clientSecretIV, clientSecretTag } = - oidcCfg; - let clientId = ""; - if (encryptedClientId && clientIdIV && clientIdTag) { - clientId = decryptSymmetric({ - ciphertext: encryptedClientId, - key, - tag: clientIdTag, - iv: clientIdIV - }); + if (oidcCfg.encryptedOidcClientId) { + clientId = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientId }).toString(); } let clientSecret = ""; - if (encryptedClientSecret && clientSecretIV && clientSecretTag) { - clientSecret = decryptSymmetric({ - key, - tag: clientSecretTag, - iv: clientSecretIV, - ciphertext: encryptedClientSecret - }); + if (oidcCfg.encryptedOidcClientSecret) { + clientSecret = decryptor({ cipherTextBlob: oidcCfg.encryptedOidcClientSecret }).toString(); } return { @@ -540,12 +509,10 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); - const orgBot = await orgBotDAL.findOne({ orgId: org.id }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot for organization with ID '${org.id}' not found`, - name: "OrgBotNotFound" - }); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: org.id + }); const serverCfg = await getServerCfg(); if (isActive && !serverCfg.trustOidcEmails) { @@ -558,13 +525,6 @@ export const oidcConfigServiceFactory = ({ } } - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - const updateQuery: TOidcConfigsUpdate = { allowedEmailDomains, configurationType, @@ -580,22 +540,11 @@ export const oidcConfigServiceFactory = ({ }; if (clientId !== undefined) { - const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); - updateQuery.encryptedClientId = encryptedClientId; - updateQuery.clientIdIV = clientIdIV; - updateQuery.clientIdTag = clientIdTag; + updateQuery.encryptedOidcClientId = encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob; } if (clientSecret !== undefined) { - const { - ciphertext: encryptedClientSecret, - iv: clientSecretIV, - tag: clientSecretTag - } = encryptSymmetric(clientSecret, key); - - updateQuery.encryptedClientSecret = encryptedClientSecret; - updateQuery.clientSecretIV = clientSecretIV; - updateQuery.clientSecretTag = clientSecretTag; + updateQuery.encryptedOidcClientSecret = encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob; } const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery); @@ -647,61 +596,11 @@ export const oidcConfigServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: org.id }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: org.id, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: org.id }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedClientId, iv: clientIdIV, tag: clientIdTag } = encryptSymmetric(clientId, key); - const { - ciphertext: encryptedClientSecret, - iv: clientSecretIV, - tag: clientSecretTag - } = encryptSymmetric(clientSecret, key); - const oidcCfg = await oidcConfigDAL.create({ issuer, isActive, @@ -713,13 +612,9 @@ export const oidcConfigServiceFactory = ({ tokenEndpoint, userinfoEndpoint, orgId: org.id, - encryptedClientId, - clientIdIV, - clientIdTag, - encryptedClientSecret, - clientSecretIV, - clientSecretTag, - manageGroupMemberships + manageGroupMemberships, + encryptedOidcClientId: encryptor({ plainText: Buffer.from(clientId) }).cipherTextBlob, + encryptedOidcClientSecret: encryptor({ plainText: Buffer.from(clientSecret) }).cipherTextBlob }); return oidcCfg; diff --git a/backend/src/ee/services/permission/project-permission.ts b/backend/src/ee/services/permission/project-permission.ts index e9ba49127..657e9ce3a 100644 --- a/backend/src/ee/services/permission/project-permission.ts +++ b/backend/src/ee/services/permission/project-permission.ts @@ -6,7 +6,7 @@ import { CASL_ACTION_SCHEMA_NATIVE_ENUM } from "@app/ee/services/permission/permission-schemas"; import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { PermissionConditionSchema } from "./permission-types"; diff --git a/backend/src/ee/services/project-template/project-template-service.ts b/backend/src/ee/services/project-template/project-template-service.ts index 5afa58caf..b2430ac14 100644 --- a/backend/src/ee/services/project-template/project-template-service.ts +++ b/backend/src/ee/services/project-template/project-template-service.ts @@ -15,7 +15,7 @@ import { } from "@app/ee/services/project-template/project-template-types"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { OrgServiceActor } from "@app/lib/types"; -import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission"; +import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"; import { TProjectTemplateDALFactory } from "./project-template-dal"; diff --git a/backend/src/ee/services/project-template/project-template-types.ts b/backend/src/ee/services/project-template/project-template-types.ts index 6b600f386..c2764dc53 100644 --- a/backend/src/ee/services/project-template/project-template-types.ts +++ b/backend/src/ee/services/project-template/project-template-types.ts @@ -2,7 +2,7 @@ import { z } from "zod"; import { TProjectEnvironments } from "@app/db/schemas"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; export type TProjectTemplateEnvironment = Pick; diff --git a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts index 14586d5e2..6f87663b2 100644 --- a/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts +++ b/backend/src/ee/services/project-user-additional-privilege/project-user-additional-privilege-service.ts @@ -5,7 +5,7 @@ import ms from "ms"; import { ActionProjectType, TableName } from "@app/db/schemas"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorType } from "@app/services/auth/auth-type"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 068a45520..f22e2ad58 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -1,29 +1,15 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { - OrgMembershipStatus, - SecretKeyEncoding, - TableName, - TSamlConfigs, - TSamlConfigsUpdate, - TUsers -} from "@app/db/schemas"; +import { OrgMembershipStatus, TableName, TSamlConfigs, TSamlConfigsUpdate, TUsers } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { AuthTokenType } from "@app/services/auth/auth-type"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TokenType } from "@app/services/auth-token/auth-token-types"; import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; @@ -52,21 +38,19 @@ type TSamlConfigServiceFactoryDep = { TOrgDALFactory, "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" >; - identityMetadataDAL: Pick; orgMembershipDAL: Pick; - orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; tokenService: Pick; smtpService: Pick; + kmsService: Pick; }; export type TSamlConfigServiceFactory = ReturnType; export const samlConfigServiceFactory = ({ samlConfigDAL, - orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -75,7 +59,8 @@ export const samlConfigServiceFactory = ({ licenseService, tokenService, smtpService, - identityMetadataDAL + identityMetadataDAL, + kmsService }: TSamlConfigServiceFactoryDep) => { const createSamlCfg = async ({ cert, @@ -99,70 +84,18 @@ export const samlConfigServiceFactory = ({ "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to create SSO configuration." }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedEntryPoint, iv: entryPointIV, tag: entryPointTag } = encryptSymmetric(entryPoint, key); - const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); - const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); const samlConfig = await samlConfigDAL.create({ orgId, authProvider, isActive, - encryptedEntryPoint, - entryPointIV, - entryPointTag, - encryptedIssuer, - issuerIV, - issuerTag, - encryptedCert, - certIV, - certTag + encryptedSamlIssuer: encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob, + encryptedSamlEntryPoint: encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob, + encryptedSamlCertificate: encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob }); return samlConfig; @@ -190,40 +123,21 @@ export const samlConfigServiceFactory = ({ }); const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null }; - const orgBot = await orgBotDAL.findOne({ orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${orgId}'`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId }); if (entryPoint !== undefined) { - const { - ciphertext: encryptedEntryPoint, - iv: entryPointIV, - tag: entryPointTag - } = encryptSymmetric(entryPoint, key); - updateQuery.encryptedEntryPoint = encryptedEntryPoint; - updateQuery.entryPointIV = entryPointIV; - updateQuery.entryPointTag = entryPointTag; + updateQuery.encryptedSamlEntryPoint = encryptor({ plainText: Buffer.from(entryPoint) }).cipherTextBlob; } + if (issuer !== undefined) { - const { ciphertext: encryptedIssuer, iv: issuerIV, tag: issuerTag } = encryptSymmetric(issuer, key); - updateQuery.encryptedIssuer = encryptedIssuer; - updateQuery.issuerIV = issuerIV; - updateQuery.issuerTag = issuerTag; + updateQuery.encryptedSamlIssuer = encryptor({ plainText: Buffer.from(issuer) }).cipherTextBlob; } + if (cert !== undefined) { - const { ciphertext: encryptedCert, iv: certIV, tag: certTag } = encryptSymmetric(cert, key); - updateQuery.encryptedCert = encryptedCert; - updateQuery.certIV = certIV; - updateQuery.certTag = certTag; + updateQuery.encryptedSamlCertificate = encryptor({ plainText: Buffer.from(cert) }).cipherTextBlob; } const [ssoConfig] = await samlConfigDAL.update({ orgId }, updateQuery); @@ -233,14 +147,14 @@ export const samlConfigServiceFactory = ({ }; const getSaml = async (dto: TGetSamlCfgDTO) => { - let ssoConfig: TSamlConfigs | undefined; + let samlConfig: TSamlConfigs | undefined; if (dto.type === "org") { - ssoConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); - if (!ssoConfig) return; + samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId }); + if (!samlConfig) return; } else if (dto.type === "orgSlug") { const org = await orgDAL.findOne({ slug: dto.orgSlug }); if (!org) return; - ssoConfig = await samlConfigDAL.findOne({ orgId: org.id }); + samlConfig = await samlConfigDAL.findOne({ orgId: org.id }); } else if (dto.type === "ssoId") { // TODO: // We made this change because saml config ids were not moved over during the migration @@ -259,81 +173,51 @@ export const samlConfigServiceFactory = ({ const id = UUIDToMongoId[dto.id] ?? dto.id; - ssoConfig = await samlConfigDAL.findById(id); + samlConfig = await samlConfigDAL.findById(id); } - if (!ssoConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); + if (!samlConfig) throw new NotFoundError({ message: `Failed to find SSO data` }); // when dto is type id means it's internally used if (dto.type === "org") { const { permission } = await permissionService.getOrgPermission( dto.actor, dto.actorId, - ssoConfig.orgId, + samlConfig.orgId, dto.actorAuthMethod, dto.actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); } - const { - entryPointTag, - entryPointIV, - encryptedEntryPoint, - certTag, - certIV, - encryptedCert, - issuerTag, - issuerIV, - encryptedIssuer - } = ssoConfig; - - const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found in organization with ID '${ssoConfig.orgId}'`, - name: "OrgBotNotFound" - }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: samlConfig.orgId }); let entryPoint = ""; - if (encryptedEntryPoint && entryPointIV && entryPointTag) { - entryPoint = decryptSymmetric({ - ciphertext: encryptedEntryPoint, - key, - tag: entryPointTag, - iv: entryPointIV - }); + if (samlConfig.encryptedSamlEntryPoint) { + entryPoint = decryptor({ cipherTextBlob: samlConfig.encryptedSamlEntryPoint }).toString(); } let issuer = ""; - if (encryptedIssuer && issuerTag && issuerIV) { - issuer = decryptSymmetric({ - key, - tag: issuerTag, - iv: issuerIV, - ciphertext: encryptedIssuer - }); + if (samlConfig.encryptedSamlIssuer) { + issuer = decryptor({ cipherTextBlob: samlConfig.encryptedSamlIssuer }).toString(); } let cert = ""; - if (encryptedCert && certTag && certIV) { - cert = decryptSymmetric({ key, tag: certTag, iv: certIV, ciphertext: encryptedCert }); + if (samlConfig.encryptedSamlCertificate) { + cert = decryptor({ cipherTextBlob: samlConfig.encryptedSamlCertificate }).toString(); } return { - id: ssoConfig.id, - organization: ssoConfig.orgId, - orgId: ssoConfig.orgId, - authProvider: ssoConfig.authProvider, - isActive: ssoConfig.isActive, + id: samlConfig.id, + organization: samlConfig.orgId, + orgId: samlConfig.orgId, + authProvider: samlConfig.authProvider, + isActive: samlConfig.isActive, entryPoint, issuer, cert, - lastUsed: ssoConfig.lastUsed + lastUsed: samlConfig.lastUsed }; }; diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index 355507ecf..fdc493b9f 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -5,13 +5,9 @@ import { IAMClient } from "@aws-sdk/client-iam"; -import { SecretKeyEncoding, SecretType } from "@app/db/schemas"; +import { SecretType } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; -import { - encryptSymmetric128BitHexKeyUTF8, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; +import { encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -135,20 +131,15 @@ export const secretRotationQueueFactory = ({ // deep copy const provider = JSON.parse(JSON.stringify(rotationProvider)) as TSecretRotationProviderTemplate; + const { encryptor: secretManagerEncryptor, decryptor: secretManagerDecryptor } = + await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: secretRotation.projectId + }); - // now get the encrypted variable values - // in includes the inputs, the previous outputs - // internal mapping variables etc - const { encryptedDataTag, encryptedDataIV, encryptedData, keyEncoding } = secretRotation; - if (!encryptedDataTag || !encryptedDataIV || !encryptedData || !keyEncoding) { - throw new DisableRotationErrors({ message: "No inputs found" }); - } - const decryptedData = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: encryptedData, - iv: encryptedDataIV, - tag: encryptedDataTag - }); + const decryptedData = secretManagerDecryptor({ + cipherTextBlob: secretRotation.encryptedRotationData + }).toString(); const variables = JSON.parse(decryptedData) as TSecretRotationEncData; // rotation set cycle @@ -303,11 +294,9 @@ export const secretRotationQueueFactory = ({ outputs: newCredential.outputs, internal: newCredential.internal }); - const encVarData = infisicalSymmetricEncypt(JSON.stringify(variables)); - const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: secretRotation.projectId - }); + const encryptedRotationData = secretManagerEncryptor({ + plainText: Buffer.from(JSON.stringify(variables)) + }).cipherTextBlob; const numberOfSecretsRotated = rotationOutputs.length; if (shouldUseSecretV2Bridge) { @@ -323,11 +312,7 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedData: encVarData.ciphertext, - encryptedDataIV: encVarData.iv, - encryptedDataTag: encVarData.tag, - keyEncoding: encVarData.encoding, - algorithm: encVarData.algorithm, + encryptedRotationData, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" @@ -371,11 +356,7 @@ export const secretRotationQueueFactory = ({ await secretRotationDAL.updateById( rotationId, { - encryptedData: encVarData.ciphertext, - encryptedDataIV: encVarData.iv, - encryptedDataTag: encVarData.tag, - keyEncoding: encVarData.encoding, - algorithm: encVarData.algorithm, + encryptedRotationData, lastRotatedAt: new Date(), statusMessage: "Rotated successfull", status: "success" diff --git a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts index c456e1581..02da4b7ea 100644 --- a/backend/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -2,9 +2,11 @@ import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas"; -import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; +import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto/encryption"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; +import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { KmsDataKey } from "@app/services/kms/kms-types"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TSecretDALFactory } from "@app/services/secret/secret-dal"; @@ -30,6 +32,7 @@ type TSecretRotationServiceFactoryDep = { permissionService: Pick; secretRotationQueue: TSecretRotationQueueFactory; projectBotService: Pick; + kmsService: Pick; }; export type TSecretRotationServiceFactory = ReturnType; @@ -44,7 +47,8 @@ export const secretRotationServiceFactory = ({ folderDAL, secretDAL, projectBotService, - secretV2BridgeDAL + secretV2BridgeDAL, + kmsService }: TSecretRotationServiceFactoryDep) => { const getProviderTemplates = async ({ actor, @@ -156,7 +160,11 @@ export const secretRotationServiceFactory = ({ inputs: formattedInputs, creds: [] }; - const encData = infisicalSymmetricEncypt(JSON.stringify(unencryptedData)); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + const secretRotation = await secretRotationDAL.transaction(async (tx) => { const doc = await secretRotationDAL.create( { @@ -164,11 +172,8 @@ export const secretRotationServiceFactory = ({ secretPath, interval, envId: folder.envId, - encryptedDataTag: encData.tag, - encryptedDataIV: encData.iv, - encryptedData: encData.ciphertext, - algorithm: encData.algorithm, - keyEncoding: encData.encoding + encryptedRotationData: secretManagerEncryptor({ plainText: Buffer.from(JSON.stringify(unencryptedData)) }) + .cipherTextBlob }, tx ); diff --git a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts index 2e4ed0f93..1c34f6b3d 100644 --- a/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts +++ b/backend/src/ee/services/secret-snapshot/secret-snapshot-service.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ +// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error import { ForbiddenError, subject } from "@casl/ability"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; diff --git a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts index 8a9eeab8c..d8240f27e 100644 --- a/backend/src/ee/services/secret-snapshot/snapshot-dal.ts +++ b/backend/src/ee/services/secret-snapshot/snapshot-dal.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-await-in-loop */ +/* eslint-disable no-await-in-loop,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */ import { Knex } from "knex"; import { z } from "zod"; diff --git a/backend/src/keystore/keystore.ts b/backend/src/keystore/keystore.ts index dbfdfd063..a5f3c24c0 100644 --- a/backend/src/keystore/keystore.ts +++ b/backend/src/keystore/keystore.ts @@ -2,6 +2,12 @@ import { Redis } from "ioredis"; import { Redlock, Settings } from "@app/lib/red-lock"; +export enum PgSqlLock { + BootUpMigration = 2023, + SuperAdminInit = 2024, + KmsRootKeyInit = 2025 +} + export type TKeyStoreFactory = ReturnType; // all the key prefixes used must be set here to avoid conflict diff --git a/backend/src/keystore/memory.ts b/backend/src/keystore/memory.ts new file mode 100644 index 000000000..1fe78cf7e --- /dev/null +++ b/backend/src/keystore/memory.ts @@ -0,0 +1,38 @@ +import { Lock } from "@app/lib/red-lock"; + +import { TKeyStoreFactory } from "./keystore"; + +export const inMemoryKeyStore = (): TKeyStoreFactory => { + const store: Record = {}; + + return { + setItem: async (key, value) => { + store[key] = value; + return "OK"; + }, + setItemWithExpiry: async (key, value) => { + store[key] = value; + return "OK"; + }, + deleteItem: async (key) => { + delete store[key]; + return 1; + }, + getItem: async (key) => { + const value = store[key]; + if (typeof value === "string") { + return value; + } + return null; + }, + incrementBy: async () => { + return 1; + }, + acquireLock: () => { + return Promise.resolve({ + release: () => {} + }) as Promise; + }, + waitTillReady: async () => {} + }; +}; diff --git a/backend/src/lib/config/env.ts b/backend/src/lib/config/env.ts index 7f0f31728..801e937a2 100644 --- a/backend/src/lib/config/env.ts +++ b/backend/src/lib/config/env.ts @@ -258,7 +258,8 @@ const envSchema = z SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",") })); -let envCfg: Readonly>; +export type TEnvConfig = Readonly>; +let envCfg: TEnvConfig; export const getConfig = () => envCfg; // cannot import singleton logger directly as it needs config to load various transport diff --git a/backend/src/lib/logger/logger.ts b/backend/src/lib/logger/logger.ts index 9676496f7..170a0285f 100644 --- a/backend/src/lib/logger/logger.ts +++ b/backend/src/lib/logger/logger.ts @@ -98,7 +98,7 @@ const extractReqId = () => { } }; -export const initLogger = async () => { +export const initLogger = () => { const cfg = loggerConfig.parse(process.env); const targets: pino.TransportMultiOptions["targets"][number][] = [ { diff --git a/backend/src/main.ts b/backend/src/main.ts index 850298f89..461601fc0 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -2,14 +2,13 @@ import "./lib/telemetry/instrumentation"; import dotenv from "dotenv"; import { Redis } from "ioredis"; -import path from "path"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; +import { runMigrations } from "./auto-start-migrations"; import { initAuditLogDbConnection, initDbConnection } from "./db"; import { keyStoreFactory } from "./keystore/keystore"; -import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env"; -import { isMigrationMode } from "./lib/fn"; +import { formatSmtpConfig, initEnvConfig } from "./lib/config/env"; import { initLogger } from "./lib/logger"; import { queueServiceFactory } from "./queue"; import { main } from "./server/app"; @@ -19,58 +18,53 @@ import { smtpServiceFactory } from "./services/smtp/smtp-service"; dotenv.config(); const run = async () => { - const logger = await initLogger(); - const appCfg = initEnvConfig(logger); + const logger = initLogger(); + const envConfig = initEnvConfig(logger); const db = initDbConnection({ - dbConnectionUri: appCfg.DB_CONNECTION_URI, - dbRootCert: appCfg.DB_ROOT_CERT, - readReplicas: appCfg.DB_READ_REPLICAS?.map((el) => ({ + dbConnectionUri: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT, + readReplicas: envConfig.DB_READ_REPLICAS?.map((el) => ({ dbRootCert: el.DB_ROOT_CERT, dbConnectionUri: el.DB_CONNECTION_URI })) }); - const auditLogDb = appCfg.AUDIT_LOGS_DB_CONNECTION_URI + const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI ? initAuditLogDbConnection({ - dbConnectionUri: appCfg.AUDIT_LOGS_DB_CONNECTION_URI, - dbRootCert: appCfg.AUDIT_LOGS_DB_ROOT_CERT + dbConnectionUri: envConfig.AUDIT_LOGS_DB_CONNECTION_URI, + dbRootCert: envConfig.AUDIT_LOGS_DB_ROOT_CERT }) : undefined; - // Case: App is running in packaged mode (binary), and migration mode is enabled. - // Run the migrations and exit the process after completion. - if (IS_PACKAGED && isMigrationMode()) { - try { - logger.info("Running Postgres migrations.."); - await db.migrate.latest({ - directory: path.join(__dirname, "./db/migrations") - }); - logger.info("Postgres migrations completed"); - } catch (err) { - logger.error(err, "Failed to run migrations"); - process.exit(1); - } - - process.exit(0); - } + await runMigrations({ applicationDb: db, auditLogDb, logger }); const smtp = smtpServiceFactory(formatSmtpConfig()); - const queue = queueServiceFactory(appCfg.REDIS_URL, { - dbConnectionUrl: appCfg.DB_CONNECTION_URI, - dbRootCert: appCfg.DB_ROOT_CERT + const queue = queueServiceFactory(envConfig.REDIS_URL, { + dbConnectionUrl: envConfig.DB_CONNECTION_URI, + dbRootCert: envConfig.DB_ROOT_CERT }); await queue.initialize(); - const keyStore = keyStoreFactory(appCfg.REDIS_URL); - const redis = new Redis(appCfg.REDIS_URL); + const keyStore = keyStoreFactory(envConfig.REDIS_URL); + const redis = new Redis(envConfig.REDIS_URL); - const hsmModule = initializeHsmModule(); + const hsmModule = initializeHsmModule(envConfig); hsmModule.initialize(); - const server = await main({ db, auditLogDb, hsmModule: hsmModule.getModule(), smtp, logger, queue, keyStore, redis }); + const server = await main({ + db, + auditLogDb, + hsmModule: hsmModule.getModule(), + smtp, + logger, + queue, + keyStore, + redis, + envConfig + }); const bootstrap = await bootstrapCheck({ db }); // eslint-disable-next-line @@ -90,8 +84,8 @@ const run = async () => { }); await server.listen({ - port: appCfg.PORT, - host: appCfg.HOST, + port: envConfig.PORT, + host: envConfig.HOST, listenTextResolver: (address) => { void bootstrap(); return address; diff --git a/backend/src/server/app.ts b/backend/src/server/app.ts index ce1be4a04..26f556508 100644 --- a/backend/src/server/app.ts +++ b/backend/src/server/app.ts @@ -17,7 +17,7 @@ import { Knex } from "knex"; import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig, IS_PACKAGED } from "@app/lib/config/env"; +import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env"; import { CustomLogger } from "@app/lib/logger/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TQueueServiceFactory } from "@app/queue"; @@ -43,10 +43,11 @@ type TMain = { keyStore: TKeyStoreFactory; hsmModule: HsmModule; redis: Redis; + envConfig: TEnvConfig; }; // Run the server! -export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis }: TMain) => { +export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, keyStore, redis, envConfig }: TMain) => { const appCfg = getConfig(); const server = fastify({ @@ -127,7 +128,7 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key }) }); - await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule }); + await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule, envConfig }); await server.register(registerServeUI, { standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED, diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 8cebbdebd..10da81bf5 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -85,7 +85,7 @@ import { sshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certi import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig } from "@app/lib/config/env"; +import { getConfig, TEnvConfig } from "@app/lib/config/env"; import { TQueueServiceFactory } from "@app/queue"; import { readLimit } from "@app/server/config/rateLimiter"; import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue"; @@ -244,7 +244,8 @@ export const registerRoutes = async ( hsmModule, smtp: smtpService, queue: queueService, - keyStore + keyStore, + envConfig }: { auditLogDb?: Knex; db: Knex; @@ -252,6 +253,7 @@ export const registerRoutes = async ( smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory; + envConfig: TEnvConfig; } ) => { const appCfg = getConfig(); @@ -391,7 +393,8 @@ export const registerRoutes = async ( const licenseService = licenseServiceFactory({ permissionService, orgDAL, licenseDAL, keyStore }); const hsmService = hsmServiceFactory({ - hsmModule + hsmModule, + envConfig }); const kmsService = kmsServiceFactory({ @@ -401,7 +404,8 @@ export const registerRoutes = async ( internalKmsDAL, orgDAL, projectDAL, - hsmService + hsmService, + envConfig }); const externalKmsService = externalKmsServiceFactory({ @@ -447,7 +451,6 @@ export const registerRoutes = async ( const samlService = samlConfigServiceFactory({ identityMetadataDAL, permissionService, - orgBotDAL, orgDAL, orgMembershipDAL, userDAL, @@ -455,7 +458,8 @@ export const registerRoutes = async ( samlConfigDAL, licenseService, tokenService, - smtpService + smtpService, + kmsService }); const groupService = groupServiceFactory({ userDAL, @@ -506,7 +510,6 @@ export const registerRoutes = async ( ldapGroupMapDAL, orgDAL, orgMembershipDAL, - orgBotDAL, groupDAL, groupProjectDAL, projectKeyDAL, @@ -518,7 +521,8 @@ export const registerRoutes = async ( permissionService, licenseService, tokenService, - smtpService + smtpService, + kmsService }); const telemetryService = telemetryServiceFactory({ @@ -969,7 +973,8 @@ export const registerRoutes = async ( permissionService, webhookDAL, projectEnvDAL, - projectDAL + projectDAL, + kmsService }); const secretTagService = secretTagServiceFactory({ secretTagDAL, permissionService }); @@ -1149,7 +1154,8 @@ export const registerRoutes = async ( secretDAL, folderDAL, projectBotService, - secretV2BridgeDAL + secretV2BridgeDAL, + kmsService }); const integrationService = integrationServiceFactory({ @@ -1238,9 +1244,9 @@ export const registerRoutes = async ( identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - orgBotDAL, permissionService, - licenseService + licenseService, + kmsService }); const identityGcpAuthService = identityGcpAuthServiceFactory({ identityGcpAuthDAL, @@ -1272,7 +1278,7 @@ export const registerRoutes = async ( identityAccessTokenDAL, permissionService, licenseService, - orgBotDAL + kmsService }); const identityJwtAuthService = identityJwtAuthServiceFactory({ @@ -1289,7 +1295,9 @@ export const registerRoutes = async ( queueService, dynamicSecretLeaseDAL, dynamicSecretProviders, - dynamicSecretDAL + dynamicSecretDAL, + folderDAL, + kmsService }); const dynamicSecretService = dynamicSecretServiceFactory({ projectDAL, @@ -1299,7 +1307,8 @@ export const registerRoutes = async ( dynamicSecretProviders, folderDAL, permissionService, - licenseService + licenseService, + kmsService }); const dynamicSecretLeaseService = dynamicSecretLeaseServiceFactory({ projectDAL, @@ -1309,7 +1318,8 @@ export const registerRoutes = async ( dynamicSecretLeaseDAL, dynamicSecretProviders, folderDAL, - licenseService + licenseService, + kmsService }); const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({ auditLogDAL, @@ -1337,7 +1347,7 @@ export const registerRoutes = async ( licenseService, tokenService, smtpService, - orgBotDAL, + kmsService, permissionService, oidcConfigDAL, projectBotDAL, diff --git a/backend/src/server/routes/sanitizedSchema/directory-config.ts b/backend/src/server/routes/sanitizedSchema/directory-config.ts new file mode 100644 index 000000000..61be4d9cf --- /dev/null +++ b/backend/src/server/routes/sanitizedSchema/directory-config.ts @@ -0,0 +1,42 @@ +import { LdapConfigsSchema, OidcConfigsSchema, SamlConfigsSchema } from "@app/db/schemas"; + +export const SanitizedSamlConfigSchema = SamlConfigsSchema.pick({ + id: true, + orgId: true, + isActive: true, + lastUsed: true, + createdAt: true, + updatedAt: true, + authProvider: true +}); + +export const SanitizedLdapConfigSchema = LdapConfigsSchema.pick({ + updatedAt: true, + createdAt: true, + isActive: true, + orgId: true, + id: true, + url: true, + searchBase: true, + searchFilter: true, + groupSearchBase: true, + uniqueUserAttribute: true, + groupSearchFilter: true +}); + +export const SanitizedOidcConfigSchema = OidcConfigsSchema.pick({ + id: true, + orgId: true, + isActive: true, + createdAt: true, + updatedAt: true, + lastUsed: true, + issuer: true, + jwksUri: true, + discoveryURL: true, + tokenEndpoint: true, + userinfoEndpoint: true, + configurationType: true, + allowedEmailDomains: true, + authorizationEndpoint: true +}); diff --git a/backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts b/backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/identitiy-additional-privilege.ts rename to backend/src/server/routes/sanitizedSchema/identitiy-additional-privilege.ts diff --git a/backend/src/server/routes/santizedSchemas/permission.ts b/backend/src/server/routes/sanitizedSchema/permission.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/permission.ts rename to backend/src/server/routes/sanitizedSchema/permission.ts diff --git a/backend/src/server/routes/santizedSchemas/user-additional-privilege.ts b/backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts similarity index 100% rename from backend/src/server/routes/santizedSchemas/user-additional-privilege.ts rename to backend/src/server/routes/sanitizedSchema/user-additional-privilege.ts diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 67f01c9ba..4d645ac4b 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -11,7 +11,7 @@ import { } from "@app/db/schemas"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { UnpackedPermissionSchema } from "./santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "./sanitizedSchema/permission"; // sometimes the return data must be santizied to avoid leaking important values // always prefer pick over omit in zod @@ -201,10 +201,11 @@ export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({ }); export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ + encryptedInput: true, + keyEncoding: true, + inputCiphertext: true, inputIV: true, inputTag: true, - inputCiphertext: true, - keyEncoding: true, algorithm: true }); diff --git a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts index 3b3025179..263fa478e 100644 --- a/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts +++ b/backend/src/server/routes/v1/identity-kubernetes-auth-router.ts @@ -8,13 +8,19 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; -const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.omit({ - encryptedCaCert: true, - caCertIV: true, - caCertTag: true, - encryptedTokenReviewerJwt: true, - tokenReviewerJwtIV: true, - tokenReviewerJwtTag: true +const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({ + id: true, + accessTokenTTL: true, + accessTokenMaxTTL: true, + accessTokenNumUsesLimit: true, + accessTokenTrustedIps: true, + createdAt: true, + updatedAt: true, + identityId: true, + kubernetesHost: true, + allowedNamespaces: true, + allowedNames: true, + allowedAudience: true }).extend({ caCert: z.string(), tokenReviewerJwt: z.string() diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts index 431ed3f4f..7ce0b05b7 100644 --- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts +++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts @@ -12,10 +12,20 @@ import { validateOidcBoundClaimsField } from "@app/services/identity-oidc-auth/identity-oidc-auth-validators"; -const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.omit({ - encryptedCaCert: true, - caCertIV: true, - caCertTag: true +const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({ + id: true, + accessTokenTTL: true, + accessTokenMaxTTL: true, + accessTokenNumUsesLimit: true, + accessTokenTrustedIps: true, + identityId: true, + oidcDiscoveryUrl: true, + boundIssuer: true, + boundAudiences: true, + boundClaims: true, + boundSubject: true, + createdAt: true, + updatedAt: true }).extend({ caCert: z.string() }); diff --git a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts index 4508a255d..a5677894d 100644 --- a/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts +++ b/backend/src/services/identity-kubernetes-auth/identity-kubernetes-auth-service.ts @@ -3,28 +3,21 @@ import axios, { AxiosError } from "axios"; import https from "https"; import jwt from "jsonwebtoken"; -import { IdentityAuthMethod, SecretKeyEncoding, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; -import { - decryptSymmetric, - encryptSymmetric, - generateAsymmetricKeyPair, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; -import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TIdentityKubernetesAuthDALFactory } from "./identity-kubernetes-auth-dal"; import { extractK8sUsername } from "./identity-kubernetes-auth-fns"; import { @@ -43,9 +36,9 @@ type TIdentityKubernetesAuthServiceFactoryDep = { >; identityAccessTokenDAL: Pick; identityOrgMembershipDAL: Pick; - orgBotDAL: Pick; permissionService: Pick; licenseService: Pick; + kmsService: Pick; }; export type TIdentityKubernetesAuthServiceFactory = ReturnType; @@ -54,9 +47,9 @@ export const identityKubernetesAuthServiceFactory = ({ identityKubernetesAuthDAL, identityOrgMembershipDAL, identityAccessTokenDAL, - orgBotDAL, permissionService, - licenseService + licenseService, + kmsService }: TIdentityKubernetesAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: serviceAccountJwt }: TLoginKubernetesAuthDTO) => { const identityKubernetesAuth = await identityKubernetesAuthDAL.findOne({ identityId }); @@ -75,42 +68,21 @@ export const identityKubernetesAuthServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = - identityKubernetesAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); } let tokenReviewerJwt = ""; - if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { - tokenReviewerJwt = decryptSymmetric({ - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { + tokenReviewerJwt = decryptor({ + cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString(); } const { data } = await axios @@ -297,79 +269,25 @@ export const identityKubernetesAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: identityMembershipOrg.orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const { - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag - } = encryptSymmetric(tokenReviewerJwt, key); - const identityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => { const doc = await identityKubernetesAuthDAL.create( { identityId: identityMembershipOrg.identityId, kubernetesHost, - encryptedCaCert, - caCertIV, - caCertTag, - encryptedTokenReviewerJwt, - tokenReviewerJwtIV, - tokenReviewerJwtTag, allowedNamespaces, allowedNames, allowedAudience, accessTokenMaxTTL, accessTokenTTL, accessTokenNumUsesLimit, - accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps) + accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps), + encryptedKubernetesTokenReviewerJwt: encryptor({ plainText: Buffer.from(tokenReviewerJwt) }).cipherTextBlob, + encryptedKubernetesCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob }, tx ); @@ -455,61 +373,34 @@ export const identityKubernetesAuthServiceFactory = ({ : undefined }; - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCaCert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedKubernetesCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } if (tokenReviewerJwt !== undefined) { - const { - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag - } = encryptSymmetric(tokenReviewerJwt, key); - updateQuery.encryptedTokenReviewerJwt = encryptedTokenReviewerJwt; - updateQuery.tokenReviewerJwtIV = tokenReviewerJwtIV; - updateQuery.tokenReviewerJwtTag = tokenReviewerJwtTag; + updateQuery.encryptedKubernetesTokenReviewerJwt = encryptor({ + plainText: Buffer.from(tokenReviewerJwt) + }).cipherTextBlob; } const updatedKubernetesAuth = await identityKubernetesAuthDAL.updateById(identityKubernetesAuth.id, updateQuery); - const updatedCACert = - updatedKubernetesAuth.encryptedCaCert && updatedKubernetesAuth.caCertIV && updatedKubernetesAuth.caCertTag - ? decryptSymmetric({ - ciphertext: updatedKubernetesAuth.encryptedCaCert, - iv: updatedKubernetesAuth.caCertIV, - tag: updatedKubernetesAuth.caCertTag, - key - }) - : ""; + const updatedCACert = updatedKubernetesAuth.encryptedKubernetesCaCertificate + ? decryptor({ + cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesCaCertificate + }).toString() + : ""; - const updatedTokenReviewerJwt = - updatedKubernetesAuth.encryptedTokenReviewerJwt && - updatedKubernetesAuth.tokenReviewerJwtIV && - updatedKubernetesAuth.tokenReviewerJwtTag - ? decryptSymmetric({ - ciphertext: updatedKubernetesAuth.encryptedTokenReviewerJwt, - iv: updatedKubernetesAuth.tokenReviewerJwtIV, - tag: updatedKubernetesAuth.tokenReviewerJwtTag, - key - }) - : ""; + const updatedTokenReviewerJwt = updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt + ? decryptor({ + cipherTextBlob: updatedKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString() + : ""; return { ...updatedKubernetesAuth, @@ -545,41 +436,21 @@ export const identityKubernetesAuthServiceFactory = ({ ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity); - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag, encryptedTokenReviewerJwt, tokenReviewerJwtIV, tokenReviewerJwtTag } = - identityKubernetesAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityKubernetesAuth.encryptedKubernetesCaCertificate }).toString(); } let tokenReviewerJwt = ""; - if (encryptedTokenReviewerJwt && tokenReviewerJwtIV && tokenReviewerJwtTag) { - tokenReviewerJwt = decryptSymmetric({ - ciphertext: encryptedTokenReviewerJwt, - iv: tokenReviewerJwtIV, - tag: tokenReviewerJwtTag, - key - }); + if (identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt) { + tokenReviewerJwt = decryptor({ + cipherTextBlob: identityKubernetesAuth.encryptedKubernetesTokenReviewerJwt + }).toString(); } return { ...identityKubernetesAuth, caCert, tokenReviewerJwt, orgId: identityMembershipOrg.orgId }; diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts index a1dbed46b..ff7256a9c 100644 --- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts +++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts @@ -4,20 +4,12 @@ import https from "https"; import jwt from "jsonwebtoken"; import { JwksClient } from "jwks-rsa"; -import { IdentityAuthMethod, SecretKeyEncoding, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; +import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { getConfig } from "@app/lib/config/env"; -import { generateAsymmetricKeyPair } from "@app/lib/crypto"; -import { - decryptSymmetric, - encryptSymmetric, - generateSymmetricKey, - infisicalSymmetricDecrypt, - infisicalSymmetricEncypt -} from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; @@ -25,7 +17,8 @@ import { ActorType, AuthTokenType } from "../auth/auth-type"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal"; import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types"; -import { TOrgBotDALFactory } from "../org/org-bot-dal"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal"; import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns"; import { @@ -42,7 +35,7 @@ type TIdentityOidcAuthServiceFactoryDep = { identityAccessTokenDAL: Pick; permissionService: Pick; licenseService: Pick; - orgBotDAL: Pick; + kmsService: Pick; }; export type TIdentityOidcAuthServiceFactory = ReturnType; @@ -53,7 +46,7 @@ export const identityOidcAuthServiceFactory = ({ permissionService, licenseService, identityAccessTokenDAL, - orgBotDAL + kmsService }: TIdentityOidcAuthServiceFactoryDep) => { const login = async ({ identityId, jwt: oidcJwt }: TLoginOidcAuthDTO) => { const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); @@ -70,31 +63,14 @@ export const identityOidcAuthServiceFactory = ({ }); } - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const { encryptedCaCert, caCertIV, caCertTag } = identityOidcAuth; - let caCert = ""; - if (encryptedCaCert && caCertIV && caCertTag) { - caCert = decryptSymmetric({ - ciphertext: encryptedCaCert, - iv: caCertIV, - tag: caCertTag, - key - }); + if (identityOidcAuth.encryptedCaCertificate) { + caCert = decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString(); } const requestAgent = new https.Agent({ ca: caCert, rejectUnauthorized: !!caCert }); @@ -264,64 +240,17 @@ export const identityOidcAuthServiceFactory = ({ return extractIPDetails(accessTokenTrustedIp.ipAddress); }); - const orgBot = await orgBotDAL.transaction(async (tx) => { - const doc = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }, tx); - if (doc) return doc; - - const { privateKey, publicKey } = generateAsymmetricKeyPair(); - const key = generateSymmetricKey(); - const { - ciphertext: encryptedPrivateKey, - iv: privateKeyIV, - tag: privateKeyTag, - encoding: privateKeyKeyEncoding, - algorithm: privateKeyAlgorithm - } = infisicalSymmetricEncypt(privateKey); - const { - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - encoding: symmetricKeyKeyEncoding, - algorithm: symmetricKeyAlgorithm - } = infisicalSymmetricEncypt(key); - - return orgBotDAL.create( - { - name: "Infisical org bot", - publicKey, - privateKeyIV, - encryptedPrivateKey, - symmetricKeyIV, - symmetricKeyTag, - encryptedSymmetricKey, - symmetricKeyAlgorithm, - orgId: identityMembershipOrg.orgId, - privateKeyTag, - privateKeyAlgorithm, - privateKeyKeyEncoding, - symmetricKeyKeyEncoding - }, - tx - ); + const { encryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const { ciphertext: encryptedCaCert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - const identityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => { const doc = await identityOidcAuthDAL.create( { identityId: identityMembershipOrg.identityId, oidcDiscoveryUrl, - encryptedCaCert, - caCertIV, - caCertTag, + encryptedCaCertificate: encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob, boundIssuer, boundAudiences, boundClaims, @@ -415,38 +344,19 @@ export const identityOidcAuthServiceFactory = ({ : undefined }; - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); if (caCert !== undefined) { - const { ciphertext: encryptedCACert, iv: caCertIV, tag: caCertTag } = encryptSymmetric(caCert, key); - updateQuery.encryptedCaCert = encryptedCACert; - updateQuery.caCertIV = caCertIV; - updateQuery.caCertTag = caCertTag; + updateQuery.encryptedCaCertificate = encryptor({ plainText: Buffer.from(caCert) }).cipherTextBlob; } const updatedOidcAuth = await identityOidcAuthDAL.updateById(identityOidcAuth.id, updateQuery); - const updatedCACert = - updatedOidcAuth.encryptedCaCert && updatedOidcAuth.caCertIV && updatedOidcAuth.caCertTag - ? decryptSymmetric({ - ciphertext: updatedOidcAuth.encryptedCaCert, - iv: updatedOidcAuth.caCertIV, - tag: updatedOidcAuth.caCertTag, - key - }) - : ""; + const updatedCACert = updatedOidcAuth.encryptedCaCertificate + ? decryptor({ cipherTextBlob: updatedOidcAuth.encryptedCaCertificate }).toString() + : ""; return { ...updatedOidcAuth, @@ -476,27 +386,14 @@ export const identityOidcAuthServiceFactory = ({ const identityOidcAuth = await identityOidcAuthDAL.findOne({ identityId }); - const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId }); - if (!orgBot) { - throw new NotFoundError({ - message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`, - name: "OrgBotNotFound" - }); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: orgBot.encryptedSymmetricKey, - iv: orgBot.symmetricKeyIV, - tag: orgBot.symmetricKeyTag, - keyEncoding: orgBot.symmetricKeyKeyEncoding as SecretKeyEncoding + const { decryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.Organization, + orgId: identityMembershipOrg.orgId }); - const caCert = decryptSymmetric({ - ciphertext: identityOidcAuth.encryptedCaCert, - iv: identityOidcAuth.caCertIV, - tag: identityOidcAuth.caCertTag, - key - }); + const caCert = identityOidcAuth.encryptedCaCertificate + ? decryptor({ cipherTextBlob: identityOidcAuth.encryptedCaCertificate }).toString() + : ""; return { ...identityOidcAuth, orgId: identityMembershipOrg.orgId, caCert }; }; diff --git a/backend/src/services/kms/kms-root-config-dal.ts b/backend/src/services/kms/kms-root-config-dal.ts index f448e2df8..8745d286e 100644 --- a/backend/src/services/kms/kms-root-config-dal.ts +++ b/backend/src/services/kms/kms-root-config-dal.ts @@ -1,10 +1,24 @@ import { TDbClient } from "@app/db"; import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; +import { Knex } from "knex"; export type TKmsRootConfigDALFactory = ReturnType; export const kmsRootConfigDALFactory = (db: TDbClient) => { const kmsOrm = ormify(db, TableName.KmsServerRootConfig); - return kmsOrm; + + const findById = async (id: string, tx?: Knex) => { + try { + const result = await (tx || db)(TableName.KmsServerRootConfig) + .where({ id } as never) + .first("*"); + return result; + } catch (error) { + throw new DatabaseError({ error, name: "Find by id" }); + } + }; + + return { ...kmsOrm, findById }; }; diff --git a/backend/src/services/kms/kms-service.ts b/backend/src/services/kms/kms-service.ts index c41783860..babba4cb2 100644 --- a/backend/src/services/kms/kms-service.ts +++ b/backend/src/services/kms/kms-service.ts @@ -12,8 +12,8 @@ import { TExternalKmsProviderFns } from "@app/ee/services/external-kms/providers/model"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; -import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore"; -import { getConfig } from "@app/lib/config/env"; +import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; +import { TEnvConfig } from "@app/lib/config/env"; import { randomSecureBytes } from "@app/lib/crypto"; import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher"; import { generateHash } from "@app/lib/crypto/encryption"; @@ -44,23 +44,22 @@ type TKmsServiceFactoryDep = { kmsDAL: TKmsKeyDALFactory; projectDAL: Pick; orgDAL: Pick; - kmsRootConfigDAL: Pick; + kmsRootConfigDAL: Pick; keyStore: Pick; internalKmsDAL: Pick; hsmService: THsmServiceFactory; + envConfig: Pick; }; export type TKmsServiceFactory = ReturnType; -const KMS_ROOT_CREATION_WAIT_KEY = "wait_till_ready_kms_root_key"; -const KMS_ROOT_CREATION_WAIT_TIME = 10; - // akhilmhdh: Don't edit this value. This is measured for blob concatination in kms const KMS_VERSION = "v01"; const KMS_VERSION_BLOB_LENGTH = 3; const KmsSanitizedSchema = KmsKeysSchema.extend({ isExternal: z.boolean() }); export const kmsServiceFactory = ({ + envConfig, kmsDAL, kmsRootConfigDAL, keyStore, @@ -473,7 +472,8 @@ export const kmsServiceFactory = ({ } const kmsDecryptor = await decryptWithKmsKey({ - kmsId: kmsKeyId + kmsId: kmsKeyId, + tx: trx }); return kmsDecryptor({ @@ -635,10 +635,8 @@ export const kmsServiceFactory = ({ }; const $getBasicEncryptionKey = () => { - const appCfg = getConfig(); - - const encryptionKey = appCfg.ENCRYPTION_KEY || appCfg.ROOT_ENCRYPTION_KEY; - const isBase64 = !appCfg.ENCRYPTION_KEY; + const encryptionKey = envConfig.ENCRYPTION_KEY || envConfig.ROOT_ENCRYPTION_KEY; + const isBase64 = !envConfig.ENCRYPTION_KEY; if (!encryptionKey) throw new Error( "Root encryption key not found for KMS service. Did you set the ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY environment variables?" @@ -874,54 +872,33 @@ export const kmsServiceFactory = ({ return { id, name, orgId, isExternal }; }; - // akhilmhdh: a copy of this is made in migrations/utils/kms const startService = async () => { - const lock = await keyStore.acquireLock([`KMS_ROOT_CFG_LOCK`], 3000, { retryCount: 3 }).catch(() => null); - if (!lock) { - await keyStore.waitTillReady({ - key: KMS_ROOT_CREATION_WAIT_KEY, - keyCheckCb: (val) => val === "true", - waitingCb: () => logger.info("KMS. Waiting for leader to finish creation of KMS Root Key") + const kmsRootConfig = await kmsRootConfigDAL.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.KmsRootKeyInit]); + // check if KMS root key was already generated and saved in DB + const existingRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); + if (existingRootConfig) return existingRootConfig; + + logger.info("KMS: Generating new ROOT Key"); + const newRootKey = randomSecureBytes(32); + const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { + logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); + throw err; }); - } - // check if KMS root key was already generated and saved in DB - const kmsRootConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID); - - // case 1: a root key already exists in the DB - if (kmsRootConfig) { - if (lock) await lock.release(); - logger.info(`KMS: Encrypted ROOT Key found from DB. Decrypting. [strategy=${kmsRootConfig.encryptionStrategy}]`); - - const decryptedRootKey = await $decryptRootKey(kmsRootConfig); - - // set the flag so that other instance nodes can start - await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); - logger.info("KMS: Loading ROOT Key into Memory."); - ROOT_ENCRYPTION_KEY = decryptedRootKey; - return; - } - - // case 2: no config is found, so we create a new root key with basic encryption - logger.info("KMS: Generating new ROOT Key"); - const newRootKey = randomSecureBytes(32); - const encryptedRootKey = await $encryptRootKey(newRootKey, RootKeyEncryptionStrategy.Software).catch((err) => { - logger.error({ hsmEnabled: hsmService.isActive() }, "KMS: Failed to encrypt ROOT Key"); - throw err; + const newRootConfig = await kmsRootConfigDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: KMS_ROOT_CONFIG_UUID, + encryptedRootKey, + encryptionStrategy: RootKeyEncryptionStrategy.Software + }); + return newRootConfig; }); - await kmsRootConfigDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: KMS_ROOT_CONFIG_UUID, - encryptedRootKey, - encryptionStrategy: RootKeyEncryptionStrategy.Software - }); + const decryptedRootKey = await $decryptRootKey(kmsRootConfig); - // set the flag so that other instance nodes can start - await keyStore.setItemWithExpiry(KMS_ROOT_CREATION_WAIT_KEY, KMS_ROOT_CREATION_WAIT_TIME, "true"); - logger.info("KMS: Saved and loaded ROOT Key into memory"); - if (lock) await lock.release(); - ROOT_ENCRYPTION_KEY = newRootKey; + logger.info("KMS: Loading ROOT Key into Memory."); + ROOT_ENCRYPTION_KEY = decryptedRootKey; }; const updateEncryptionStrategy = async (strategy: RootKeyEncryptionStrategy) => { diff --git a/backend/src/services/project-role/project-role-service.ts b/backend/src/services/project-role/project-role-service.ts index 09bc6460d..1d695a5ff 100644 --- a/backend/src/services/project-role/project-role-service.ts +++ b/backend/src/services/project-role/project-role-service.ts @@ -9,7 +9,7 @@ import { ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { BadRequestError, NotFoundError } from "@app/lib/errors"; -import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission"; +import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { ActorAuthMethod } from "../auth/auth-type"; import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal"; diff --git a/backend/src/services/project/project-types.ts b/backend/src/services/project/project-types.ts index 2c6b8e2da..83a59b6af 100644 --- a/backend/src/services/project/project-types.ts +++ b/backend/src/services/project/project-types.ts @@ -4,8 +4,17 @@ import { ProjectType, TProjectKeys } from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { CaStatus } from "../certificate-authority/certificate-authority-types"; -import { KmsType } from "../kms/kms-types"; + +enum KmsType { + External = "external", + Internal = "internal" +} + +enum CaStatus { + ACTIVE = "active", + DISABLED = "disabled", + PENDING_CERTIFICATE = "pending-certificate" +} export enum ProjectFilterType { ID = "id", diff --git a/backend/src/services/secret/secret-queue.ts b/backend/src/services/secret/secret-queue.ts index dc973c0b1..00b0e7da8 100644 --- a/backend/src/services/secret/secret-queue.ts +++ b/backend/src/services/secret/secret-queue.ts @@ -1488,7 +1488,18 @@ export const secretQueueFactory = ({ }); queueService.start(QueueName.SecretWebhook, async (job) => { - await fnTriggerWebhook({ ...job.data, projectEnvDAL, webhookDAL, projectDAL }); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: job.data.projectId + }); + + await fnTriggerWebhook({ + ...job.data, + projectEnvDAL, + webhookDAL, + projectDAL, + secretManagerDecryptor: (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString() + }); }); return { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index b0fdd9c5c..9a6075423 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; -import { TKeyStoreFactory } from "@app/keystore/keystore"; +import { PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { getConfig } from "@app/lib/config/env"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { getUserPrivateKey } from "@app/lib/crypto/srp"; @@ -87,17 +87,21 @@ export const superAdminServiceFactory = ({ // reset on initialized await keyStore.deleteItem(ADMIN_CONFIG_KEY); - const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); - if (serverCfg) return; + const serverCfg = await serverCfgDAL.transaction(async (tx) => { + await tx.raw("SELECT pg_advisory_xact_lock(?)", [PgSqlLock.SuperAdminInit]); + const serverCfgInDB = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); + if (serverCfgInDB) return serverCfgInDB; - const newCfg = await serverCfgDAL.create({ - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - id: ADMIN_CONFIG_DB_UUID, - initialized: false, - allowSignUp: true, - defaultAuthOrgId: null + const newCfg = await serverCfgDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: ADMIN_CONFIG_DB_UUID, + initialized: false, + allowSignUp: true, + defaultAuthOrgId: null + }); + return newCfg; }); - return newCfg; + return serverCfg; }; const updateServerCfg = async ( diff --git a/backend/src/services/webhook/webhook-fns.ts b/backend/src/services/webhook/webhook-fns.ts index 58f51f880..e46f9db2a 100644 --- a/backend/src/services/webhook/webhook-fns.ts +++ b/backend/src/services/webhook/webhook-fns.ts @@ -3,9 +3,8 @@ import crypto from "node:crypto"; import { AxiosError } from "axios"; import picomatch from "picomatch"; -import { SecretKeyEncoding, TWebhooks } from "@app/db/schemas"; +import { TWebhooks } from "@app/db/schemas"; import { request } from "@app/lib/config/request"; -import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; @@ -16,28 +15,14 @@ import { WebhookType } from "./webhook-types"; const WEBHOOK_TRIGGER_TIMEOUT = 15 * 1000; -export const decryptWebhookDetails = (webhook: TWebhooks) => { - const { keyEncoding, iv, encryptedSecretKey, tag, urlCipherText, urlIV, urlTag, url } = webhook; +export const decryptWebhookDetails = (webhook: TWebhooks, decryptor: (value: Buffer) => string) => { + const { encryptedPassKey, encryptedUrl } = webhook; + + const decryptedUrl = decryptor(encryptedUrl); let decryptedSecretKey = ""; - let decryptedUrl = url; - - if (encryptedSecretKey) { - decryptedSecretKey = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: encryptedSecretKey, - iv: iv as string, - tag: tag as string - }); - } - - if (urlCipherText) { - decryptedUrl = infisicalSymmetricDecrypt({ - keyEncoding: keyEncoding as SecretKeyEncoding, - ciphertext: urlCipherText, - iv: urlIV as string, - tag: urlTag as string - }); + if (encryptedPassKey) { + decryptedSecretKey = decryptor(encryptedPassKey); } return { @@ -46,10 +31,14 @@ export const decryptWebhookDetails = (webhook: TWebhooks) => { }; }; -export const triggerWebhookRequest = async (webhook: TWebhooks, data: Record) => { +export const triggerWebhookRequest = async ( + webhook: TWebhooks, + decryptor: (value: Buffer) => string, + data: Record +) => { const headers: Record = {}; const payload = { ...data, timestamp: Date.now() }; - const { secretKey, url } = decryptWebhookDetails(webhook); + const { secretKey, url } = decryptWebhookDetails(webhook, decryptor); if (secretKey) { const webhookSign = crypto.createHmac("sha256", secretKey).update(JSON.stringify(payload)).digest("hex"); @@ -124,6 +113,7 @@ export type TFnTriggerWebhookDTO = { webhookDAL: Pick; projectEnvDAL: Pick; projectDAL: Pick; + secretManagerDecryptor: (value: Buffer) => string; }; // this is reusable function @@ -134,7 +124,8 @@ export const fnTriggerWebhook = async ({ projectId, webhookDAL, projectEnvDAL, - projectDAL + projectDAL, + secretManagerDecryptor }: TFnTriggerWebhookDTO) => { const webhooks = await webhookDAL.findAllWebhooks(projectId, environment); const toBeTriggeredHooks = webhooks.filter( @@ -148,6 +139,7 @@ export const fnTriggerWebhook = async ({ toBeTriggeredHooks.map((hook) => triggerWebhookRequest( hook, + secretManagerDecryptor, getWebhookPayload("secrets.modified", { workspaceName: project.name, workspaceId: projectId, diff --git a/backend/src/services/webhook/webhook-service.ts b/backend/src/services/webhook/webhook-service.ts index 26136aaf6..bb078e0f1 100644 --- a/backend/src/services/webhook/webhook-service.ts +++ b/backend/src/services/webhook/webhook-service.ts @@ -3,9 +3,10 @@ import { ForbiddenError } from "@casl/ability"; import { ActionProjectType, TWebhooksInsert } from "@app/db/schemas"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { NotFoundError } from "@app/lib/errors"; +import { TKmsServiceFactory } from "../kms/kms-service"; +import { KmsDataKey } from "../kms/kms-types"; import { TProjectDALFactory } from "../project/project-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TWebhookDALFactory } from "./webhook-dal"; @@ -23,6 +24,7 @@ type TWebhookServiceFactoryDep = { projectEnvDAL: TProjectEnvDALFactory; projectDAL: Pick; permissionService: Pick; + kmsService: Pick; }; export type TWebhookServiceFactory = ReturnType; @@ -31,7 +33,8 @@ export const webhookServiceFactory = ({ webhookDAL, projectEnvDAL, permissionService, - projectDAL + projectDAL, + kmsService }: TWebhookServiceFactoryDep) => { const createWebhook = async ({ actor, @@ -60,30 +63,20 @@ export const webhookServiceFactory = ({ message: `Environment with slug '${environment}' in project with ID '${projectId}' not found` }); + const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); const insertDoc: TWebhooksInsert = { - url: "", // deprecated - we are moving away from plaintext URLs envId: env.id, isDisabled: false, secretPath: secretPath || "/", - type + type, + encryptedUrl: secretManagerEncryptor({ plainText: Buffer.from(webhookUrl) }).cipherTextBlob }; if (webhookSecretKey) { - const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookSecretKey); - insertDoc.encryptedSecretKey = ciphertext; - insertDoc.iv = iv; - insertDoc.tag = tag; - insertDoc.algorithm = algorithm; - insertDoc.keyEncoding = encoding; - } - - if (webhookUrl) { - const { ciphertext, iv, tag, algorithm, encoding } = infisicalSymmetricEncypt(webhookUrl); - insertDoc.urlCipherText = ciphertext; - insertDoc.urlIV = iv; - insertDoc.urlTag = tag; - insertDoc.algorithm = algorithm; - insertDoc.keyEncoding = encoding; + insertDoc.encryptedPassKey = secretManagerEncryptor({ plainText: Buffer.from(webhookSecretKey) }).cipherTextBlob; } const webhook = await webhookDAL.create(insertDoc); @@ -140,12 +133,17 @@ export const webhookServiceFactory = ({ }); const project = await projectDAL.findById(webhook.projectId); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId: project.id + }); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); let webhookError: string | undefined; try { await triggerWebhookRequest( webhook, + (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString(), getWebhookPayload("test", { workspaceName: project.name, workspaceId: webhook.projectId, @@ -185,8 +183,13 @@ export const webhookServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks); const webhooks = await webhookDAL.findAllWebhooks(projectId, environment, secretPath); + const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ + type: KmsDataKey.SecretManager, + projectId + }); + return webhooks.map((w) => { - const { url } = decryptWebhookDetails(w); + const { url } = decryptWebhookDetails(w, (value) => secretManagerDecryptor({ cipherTextBlob: value }).toString()); return { ...w, url diff --git a/backend/tsconfig.json b/backend/tsconfig.json index fcf508922..90165acbe 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,7 +1,8 @@ { "ts-node": { // Do not forget to `npm i -D tsconfig-paths` - "require": ["tsconfig-paths/register"] + "require": ["tsconfig-paths/register"], + "files": true }, "compilerOptions": { "target": "esnext", @@ -19,6 +20,7 @@ "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, "skipLibCheck": true, "baseUrl": ".", "paths": { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 40d17c1b0..680a655d8 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -56,20 +56,6 @@ services: POSTGRES_USER: infisical POSTGRES_DB: infisical-test - db-migration: - container_name: infisical-db-migration - depends_on: - - db - build: - context: ./backend - dockerfile: Dockerfile.dev - env_file: .env - environment: - - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable - command: npm run migration:latest - volumes: - - ./backend/src:/app/src - backend: container_name: infisical-dev-api build: @@ -80,8 +66,6 @@ services: condition: service_started redis: condition: service_started - db-migration: - condition: service_completed_successfully env_file: - .env ports: @@ -192,7 +176,7 @@ services: depends_on: - openldap profiles: [ldap] - + keycloak: image: quay.io/keycloak/keycloak:26.1.0 restart: always @@ -202,7 +186,7 @@ services: command: start-dev ports: - 8088:8080 - profiles: [ sso ] + profiles: [sso] volumes: postgres-data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 77a1e04ab..d3526d841 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,18 +1,6 @@ version: "3" services: - db-migration: - container_name: infisical-db-migration - depends_on: - db: - condition: service_healthy - image: infisical/infisical:latest-postgres - env_file: .env - command: npm run migration:latest - pull_policy: always - networks: - - infisical - backend: container_name: infisical-backend restart: unless-stopped @@ -21,8 +9,6 @@ services: condition: service_healthy redis: condition: service_started - db-migration: - condition: service_completed_successfully image: infisical/infisical:latest-postgres pull_policy: always env_file: .env @@ -69,4 +55,5 @@ volumes: driver: local networks: - infisical: \ No newline at end of file + infisical: + From b3e72c338f11d7b6166af0f9b93cc2b5f616cfdc Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 12 Feb 2025 20:26:19 +0400 Subject: [PATCH 38/41] Update group-dal.ts --- backend/src/ee/services/group/group-dal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/ee/services/group/group-dal.ts b/backend/src/ee/services/group/group-dal.ts index fc38a2a9b..7702e90fb 100644 --- a/backend/src/ee/services/group/group-dal.ts +++ b/backend/src/ee/services/group/group-dal.ts @@ -111,7 +111,7 @@ export const groupDALFactory = (db: TDbClient) => { } if (search) { - void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike '%${search}%'`); + // void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike '%${search}%'`); } else if (username) { void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`); } From 296efa975c653027369aa7613c4bdb6eba35ef8d Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Wed, 12 Feb 2025 20:33:13 +0400 Subject: [PATCH 39/41] chore: fix lint --- .../20250210101840_webhook-to-kms.ts | 13 +- ...250210101841_dynamic-secret-root-to-kms.ts | 11 +- .../20250210101841_secret-rotation-to-kms.ts | 11 +- .../20250210101842_identity-k8-auth-to-kms.ts | 142 ++++++++++-------- ...0250210101842_identity-oidc-auth-to-kms.ts | 11 +- .../20250210101845_directory-config-to-kms.ts | 33 ++-- 6 files changed, 126 insertions(+), 95 deletions(-) diff --git a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts index c9b8e7fec..a2d856388 100644 --- a/backend/src/db/migrations/20250210101840_webhook-to-kms.ts +++ b/backend/src/db/migrations/20250210101840_webhook-to-kms.ts @@ -31,7 +31,7 @@ export async function up(knex: Knex): Promise { const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const projectEncryptionRingBuffer = createCircularCache>>(25); - const webhooks = await knex(TableName.Webhook) + const webhooks = await knex(TableName.Webhook) .where({}) .join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.Webhook}.envId`) .select( @@ -53,10 +53,13 @@ export async function up(knex: Knex): Promise { webhooks.map(async (el) => { let projectKmsService = projectEncryptionRingBuffer.getItem(el.projectId); if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId: el.projectId - }, knex); + projectKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.SecretManager, + projectId: el.projectId + }, + knex + ); projectEncryptionRingBuffer.push(el.projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts index 41dc6ba9f..dde1e7188 100644 --- a/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_dynamic-secret-root-to-kms.ts @@ -46,10 +46,13 @@ export async function up(knex: Knex): Promise { dynamicSecretRootCredentials.map(async ({ projectId, ...el }) => { let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }, knex); + projectKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.SecretManager, + projectId + }, + knex + ); projectEncryptionRingBuffer.push(projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts index 567cace99..e11ef926e 100644 --- a/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts +++ b/backend/src/db/migrations/20250210101841_secret-rotation-to-kms.ts @@ -39,10 +39,13 @@ export async function up(knex: Knex): Promise { secretRotations.map(async ({ projectId, ...el }) => { let projectKmsService = projectEncryptionRingBuffer.getItem(projectId); if (!projectKmsService) { - projectKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.SecretManager, - projectId - }, knex); + projectKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.SecretManager, + projectId + }, + knex + ); projectEncryptionRingBuffer.push(projectId, projectKmsService); } diff --git a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts index 3d62ab04f..934dce5e8 100644 --- a/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-k8-auth-to-kms.ts @@ -76,77 +76,87 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => { ) .orderBy(`${TableName.OrgBot}.orgId` as "orgId"); - const updatedIdentityKubernetesConfigs = []; + const updatedIdentityKubernetesConfigs = []; - for (const { encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el } of identityKubernetesConfigs) { - let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); - - if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ + for await (const { + encryptedSymmetricKey, + symmetricKeyKeyEncoding, + symmetricKeyTag, + symmetricKeyIV, + orgId, + ...el + } of identityKubernetesConfigs) { + let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); + + if (!orgKmsService) { + orgKmsService = await kmsService.createCipherPairWithDataKey( + { type: KmsDataKey.Organization, orgId - }, knex); - orgEncryptionRingBuffer.push(orgId, orgKmsService); - } - - const key = infisicalSymmetricDecrypt({ - ciphertext: encryptedSymmetricKey, - iv: symmetricKeyIV, - tag: symmetricKeyTag, - keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding - }); - - const decryptedTokenReviewerJwt = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.tokenReviewerJwtIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.tokenReviewerJwtTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedTokenReviewerJwt - }) - : ""; - - const decryptedCertificate = - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - el.encryptedCaCert && el.caCertIV && el.caCertTag - ? decryptSymmetric({ - key, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - iv: el.caCertIV, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - tag: el.caCertTag, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore This will be removed in next cycle so ignore the ts missing error - ciphertext: el.encryptedCaCert - }) - : ""; - - const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedTokenReviewerJwt) - }).cipherTextBlob; - const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ - plainText: Buffer.from(decryptedCertificate) - }).cipherTextBlob; - - updatedIdentityKubernetesConfigs.push({ - ...el, - accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), - encryptedKubernetesCaCertificate, - encryptedKubernetesTokenReviewerJwt - }); + }, + knex + ); + orgEncryptionRingBuffer.push(orgId, orgKmsService); } + const key = infisicalSymmetricDecrypt({ + ciphertext: encryptedSymmetricKey, + iv: symmetricKeyIV, + tag: symmetricKeyTag, + keyEncoding: symmetricKeyKeyEncoding as SecretKeyEncoding + }); + + const decryptedTokenReviewerJwt = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedTokenReviewerJwt && el.tokenReviewerJwtIV && el.tokenReviewerJwtTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.tokenReviewerJwtIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.tokenReviewerJwtTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedTokenReviewerJwt + }) + : ""; + + const decryptedCertificate = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + el.encryptedCaCert && el.caCertIV && el.caCertTag + ? decryptSymmetric({ + key, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + iv: el.caCertIV, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + tag: el.caCertTag, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore This will be removed in next cycle so ignore the ts missing error + ciphertext: el.encryptedCaCert + }) + : ""; + + const encryptedKubernetesTokenReviewerJwt = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedTokenReviewerJwt) + }).cipherTextBlob; + const encryptedKubernetesCaCertificate = orgKmsService.encryptor({ + plainText: Buffer.from(decryptedCertificate) + }).cipherTextBlob; + + updatedIdentityKubernetesConfigs.push({ + ...el, + accessTokenTrustedIps: JSON.stringify(el.accessTokenTrustedIps), + encryptedKubernetesCaCertificate, + encryptedKubernetesTokenReviewerJwt + }); + } + for (let i = 0; i < updatedIdentityKubernetesConfigs.length; i += BATCH_SIZE) { // eslint-disable-next-line no-await-in-loop await knex(TableName.IdentityKubernetesAuth) diff --git a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts index dc87726a4..011585bda 100644 --- a/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts +++ b/backend/src/db/migrations/20250210101842_identity-oidc-auth-to-kms.ts @@ -62,10 +62,13 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => { async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, orgId, ...el }) => { let orgKmsService = orgEncryptionRingBuffer.getItem(orgId); if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId - }, knex); + orgKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.Organization, + orgId + }, + knex + ); orgEncryptionRingBuffer.push(orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ diff --git a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts index 05db40958..f5107b301 100644 --- a/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts +++ b/backend/src/db/migrations/20250210101845_directory-config-to-kms.ts @@ -49,10 +49,13 @@ const reencryptSamlConfig = async (knex: Knex) => { async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); + orgKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.Organization, + orgId: el.orgId + }, + knex + ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ @@ -204,10 +207,13 @@ const reencryptLdapConfig = async (knex: Knex) => { async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); + orgKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.Organization, + orgId: el.orgId + }, + knex + ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ @@ -353,10 +359,13 @@ const reencryptOidcConfig = async (knex: Knex) => { async ({ encryptedSymmetricKey, symmetricKeyKeyEncoding, symmetricKeyTag, symmetricKeyIV, ...el }) => { let orgKmsService = orgEncryptionRingBuffer.getItem(el.orgId); if (!orgKmsService) { - orgKmsService = await kmsService.createCipherPairWithDataKey({ - type: KmsDataKey.Organization, - orgId: el.orgId - }, knex); + orgKmsService = await kmsService.createCipherPairWithDataKey( + { + type: KmsDataKey.Organization, + orgId: el.orgId + }, + knex + ); orgEncryptionRingBuffer.push(el.orgId, orgKmsService); } const key = infisicalSymmetricDecrypt({ From 7a3bfa9e4c8396f5837e9e2317fd194b5fd2d3f7 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 12 Feb 2025 17:34:53 +0000 Subject: [PATCH 40/41] improve query --- backend/src/ee/services/group/group-dal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/ee/services/group/group-dal.ts b/backend/src/ee/services/group/group-dal.ts index 7702e90fb..59f82c05d 100644 --- a/backend/src/ee/services/group/group-dal.ts +++ b/backend/src/ee/services/group/group-dal.ts @@ -111,7 +111,7 @@ export const groupDALFactory = (db: TDbClient) => { } if (search) { - // void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike '%${search}%'`); + void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike ?`, [`%${search}%`]); } else if (username) { void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`); } From e88ce49463d3fc82e140384e46f7eb7e2bbb3ef6 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 12 Feb 2025 13:10:07 -0500 Subject: [PATCH 41/41] Delete .github/workflows/deployment-pipeline.yml --- .github/workflows/deployment-pipeline.yml | 262 ---------------------- 1 file changed, 262 deletions(-) delete mode 100644 .github/workflows/deployment-pipeline.yml diff --git a/.github/workflows/deployment-pipeline.yml b/.github/workflows/deployment-pipeline.yml deleted file mode 100644 index f99412131..000000000 --- a/.github/workflows/deployment-pipeline.yml +++ /dev/null @@ -1,262 +0,0 @@ -name: Deployment pipeline -on: [workflow_dispatch] - -permissions: - id-token: write - contents: read - -concurrency: - group: "infisical-core-deployment" - cancel-in-progress: true - -jobs: - infisical-tests: - name: Integration tests - # https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview - uses: ./.github/workflows/run-backend-tests.yml - - infisical-image: - name: Build - runs-on: ubuntu-latest - needs: [infisical-tests] - steps: - - name: ☁️ Checkout source - uses: actions/checkout@v3 - - name: 📦 Install dependencies to test all dependencies - run: npm ci --only-production - working-directory: backend - - name: Save commit hashes for tag - id: commit - uses: pr-mpt/actions-commit-hash@v2 - - name: 🔧 Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: 🐋 Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set up Depot CLI - uses: depot/setup-action@v1 - - name: 🏗️ Build backend and push to docker hub - uses: depot/build-push-action@v1 - with: - project: 64mmf0n610 - token: ${{ secrets.DEPOT_PROJECT_TOKEN }} - push: true - context: . - file: Dockerfile.standalone-infisical - tags: | - infisical/staging_infisical:${{ steps.commit.outputs.short }} - infisical/staging_infisical:latest - platforms: linux/amd64,linux/arm64 - build-args: | - POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }} - INFISICAL_PLATFORM_VERSION=${{ steps.commit.outputs.short }} - - gamma-deployment: - name: Deploy to gamma - runs-on: ubuntu-latest - needs: [infisical-image] - environment: - name: Gamma - steps: - - uses: twingate/github-action@v1 - with: - # The Twingate Service Key used to connect Twingate to the proper service - # Learn more about [Twingate Services](https://docs.twingate.com/docs/services) - # - # Required - service-key: ${{ secrets.TWINGATE_SERVICE_KEY }} - - name: Checkout code - uses: actions/checkout@v2 - - name: Setup Node.js environment - uses: actions/setup-node@v2 - with: - node-version: "20" - - name: Change directory to backend and install dependencies - env: - DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }} - run: | - cd backend - npm install - npm run migration:latest - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - audience: sts.amazonaws.com - aws-region: us-east-1 - role-to-assume: arn:aws:iam::905418227878:role/deploy-new-ecs-img - - name: Save commit hashes for tag - id: commit - uses: pr-mpt/actions-commit-hash@v2 - - name: Download task definition - run: | - aws ecs describe-task-definition --task-definition infisical-core-gamma-stage --query taskDefinition > task-definition.json - - name: Render Amazon ECS task definition - id: render-web-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: task-definition.json - container-name: infisical-core - image: infisical/staging_infisical:${{ steps.commit.outputs.short }} - environment-variables: "LOG_LEVEL=info" - - name: Deploy to Amazon ECS service - uses: aws-actions/amazon-ecs-deploy-task-definition@v2 - with: - task-definition: ${{ steps.render-web-container.outputs.task-definition }} - service: infisical-core-gamma-stage - cluster: infisical-gamma-stage - wait-for-service-stability: true - - production-us: - name: US production deploy - runs-on: ubuntu-latest - needs: [gamma-deployment] - environment: - name: Production - steps: - - uses: twingate/github-action@v1 - with: - service-key: ${{ secrets.TWINGATE_SERVICE_KEY }} - - name: Checkout code - uses: actions/checkout@v2 - - name: Setup Node.js environment - uses: actions/setup-node@v2 - with: - node-version: "20" - - name: Change directory to backend and install dependencies - env: - DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }} - AUDIT_LOGS_DB_CONNECTION_URI: ${{ secrets.AUDIT_LOGS_DB_CONNECTION_URI }} - run: | - cd backend - npm install - npm run migration:latest - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - audience: sts.amazonaws.com - aws-region: us-east-1 - role-to-assume: arn:aws:iam::381492033652:role/gha-make-prod-deployment - - name: Save commit hashes for tag - id: commit - uses: pr-mpt/actions-commit-hash@v2 - - name: Download task definition - run: | - aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json - - name: Render Amazon ECS task definition - id: render-web-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: task-definition.json - container-name: infisical-core-platform - image: infisical/staging_infisical:${{ steps.commit.outputs.short }} - environment-variables: "LOG_LEVEL=info" - - name: Deploy to Amazon ECS service - uses: aws-actions/amazon-ecs-deploy-task-definition@v2 - with: - task-definition: ${{ steps.render-web-container.outputs.task-definition }} - service: infisical-core-platform - cluster: infisical-core-platform - wait-for-service-stability: true - - name: Post slack message - uses: slackapi/slack-github-action@v2.0.0 - with: - webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }} - webhook-type: incoming-webhook - payload: | - text: "*Deployment Status Update*: ${{ job.status }}" - blocks: - - type: "section" - text: - type: "mrkdwn" - text: "*Deployment Status Update*: ${{ job.status }}" - - type: "section" - fields: - - type: "mrkdwn" - text: "*Application:*\nInfisical Core" - - type: "mrkdwn" - text: "*Instance Type:*\nShared Infisical Cloud" - - type: "section" - fields: - - type: "mrkdwn" - text: "*Region:*\nUS" - - type: "mrkdwn" - text: "*Git Tag:*\n" - - - production-eu: - name: EU production deploy - runs-on: ubuntu-latest - needs: [production-us] - environment: - name: production-eu - steps: - - uses: twingate/github-action@v1 - with: - service-key: ${{ secrets.TWINGATE_SERVICE_KEY }} - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - audience: sts.amazonaws.com - aws-region: eu-central-1 - role-to-assume: arn:aws:iam::345594589636:role/gha-make-prod-deployment - - name: Checkout code - uses: actions/checkout@v2 - - name: Setup Node.js environment - uses: actions/setup-node@v2 - with: - node-version: "20" - - name: Change directory to backend and install dependencies - env: - DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }} - run: | - cd backend - npm install - npm run migration:latest - - name: Save commit hashes for tag - id: commit - uses: pr-mpt/actions-commit-hash@v2 - - name: Download task definition - run: | - aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json - - name: Render Amazon ECS task definition - id: render-web-container - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: task-definition.json - container-name: infisical-core-platform - image: infisical/staging_infisical:${{ steps.commit.outputs.short }} - environment-variables: "LOG_LEVEL=info" - - name: Deploy to Amazon ECS service - uses: aws-actions/amazon-ecs-deploy-task-definition@v2 - with: - task-definition: ${{ steps.render-web-container.outputs.task-definition }} - service: infisical-core-platform - cluster: infisical-core-platform - wait-for-service-stability: true - - name: Post slack message - uses: slackapi/slack-github-action@v2.0.0 - with: - webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }} - webhook-type: incoming-webhook - payload: | - text: "*Deployment Status Update*: ${{ job.status }}" - blocks: - - type: "section" - text: - type: "mrkdwn" - text: "*Deployment Status Update*: ${{ job.status }}" - - type: "section" - fields: - - type: "mrkdwn" - text: "*Application:*\nInfisical Core" - - type: "mrkdwn" - text: "*Instance Type:*\nShared Infisical Cloud" - - type: "section" - fields: - - type: "mrkdwn" - text: "*Region:*\nEU" - - type: "mrkdwn" - text: "*Git Tag:*\n" -