diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index c5cb52663..9d6978e9c 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -3,99 +3,156 @@ import { GITLAB_URL } from "../variables"; import InfisicalClient from "infisical-node"; export const client = new InfisicalClient({ - token: process.env.INFISICAL_TOKEN!, + token: process.env.INFISICAL_TOKEN! }); export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000; export const getEncryptionKey = async () => { const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} +}; export const getRootEncryptionKey = async () => { const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} -export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true" -export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; -export const getAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? (await client.getSecret("AUTH_SECRET")).secretValue; -export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; -export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; -export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; -export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) -export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; -export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; +}; +export const getInviteOnlySignup = async () => + (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"; +export const getSaltRounds = async () => + parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; +export const getAuthSecret = async () => + (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? + (await client.getSecret("AUTH_SECRET")).secretValue; +export const getJwtAuthLifetime = async () => + (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; +export const getJwtMfaLifetime = async () => + (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; +export const getJwtRefreshLifetime = async () => + (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; +export const getJwtServiceSecret = async () => + (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) +export const getJwtSignupLifetime = async () => + (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; +export const getJwtProviderAuthLifetime = async () => + (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; -export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; -export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; +export const getNodeEnv = async () => + (await client.getSecret("NODE_ENV")).secretValue || "production"; +export const getVerboseErrorOutput = async () => + (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; export const getLokiHost = async () => (await client.getSecret("LOKI_HOST")).secretValue; export const getClientIdAzure = async () => (await client.getSecret("CLIENT_ID_AZURE")).secretValue; -export const getClientIdHeroku = async () => (await client.getSecret("CLIENT_ID_HEROKU")).secretValue; -export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; -export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; -export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; -export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; -export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; -export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; -export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; -export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; -export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; -export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; -export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; -export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; -export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; -export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; -export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; +export const getClientIdHeroku = async () => + (await client.getSecret("CLIENT_ID_HEROKU")).secretValue; +export const getClientIdVercel = async () => + (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; +export const getClientIdNetlify = async () => + (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; +export const getClientIdGitHub = async () => + (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; +export const getClientIdGitLab = async () => + (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; +export const getClientIdBitBucket = async () => + (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; +export const getClientIdGCPSecretManager = async () => + (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; +export const getClientSecretAzure = async () => + (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; +export const getClientSecretHeroku = async () => + (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; +export const getClientSecretVercel = async () => + (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; +export const getClientSecretNetlify = async () => + (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; +export const getClientSecretGitHub = async () => + (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; +export const getClientSecretGitLab = async () => + (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; +export const getClientSecretBitBucket = async () => + (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; +export const getClientSecretGCPSecretManager = async () => + (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; +export const getClientSlugVercel = async () => + (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; -export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; -export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; -export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; -export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; -export const getClientIdGitLabLogin = async () => (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; -export const getClientSecretGitLabLogin = async () => (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; -export const getUrlGitLabLogin = async () => (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; +export const getClientIdGoogleLogin = async () => + (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; +export const getClientSecretGoogleLogin = async () => + (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; +export const getClientIdGitHubLogin = async () => + (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; +export const getClientSecretGitHubLogin = async () => + (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; +export const getClientIdGitLabLogin = async () => + (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; +export const getClientSecretGitLabLogin = async () => + (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; +export const getUrlGitLabLogin = async () => + (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; -export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; -export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; +export const getPostHogHost = async () => + (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; +export const getPostHogProjectApiKey = async () => + (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || + "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; export const getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue; export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue; export const getSmtpHost = async () => (await client.getSecret("SMTP_HOST")).secretValue; -export const getSmtpSecure = async () => (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false; -export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; +export const getSmtpSecure = async () => + (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false; +export const getSmtpPort = async () => + parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; export const getSmtpUsername = async () => (await client.getSecret("SMTP_USERNAME")).secretValue; export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue; -export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue; -export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; +export const getSmtpFromAddress = async () => + (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue; +export const getSmtpFromName = async () => + (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; -export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; -export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; -export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; -export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; +export const getSecretScanningWebhookProxy = async () => + (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; +export const getSecretScanningWebhookSecret = async () => + (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; +export const getSecretScanningGitAppId = async () => + (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; +export const getSecretScanningPrivateKey = async () => + (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue; +export const getIsInfisicalCloud = async () => + (await client.getSecret("INFISICAL_CLOUD")).secretValue === "true"; export const getLicenseKey = async () => { const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} +}; export const getLicenseServerKey = async () => { const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} -export const getLicenseServerUrl = async () => (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com"; +}; +export const getLicenseServerUrl = async () => + (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com"; -export const getTelemetryEnabled = async () => (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true; +export const getTelemetryEnabled = async () => + (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true; export const getLoopsApiKey = async () => (await client.getSecret("LOOPS_API_KEY")).secretValue; -export const getSmtpConfigured = async () => (await client.getSecret("SMTP_HOST")).secretValue == "" || (await client.getSecret("SMTP_HOST")).secretValue == undefined ? false : true +export const getSmtpConfigured = async () => + (await client.getSecret("SMTP_HOST")).secretValue == "" || + (await client.getSecret("SMTP_HOST")).secretValue == undefined + ? false + : true; export const getHttpsEnabled = async () => { if ((await getNodeEnv()) != "production") { // no https for anything other than prod - return false + return false; } - if ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") { + if ( + (await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || + (await client.getSecret("HTTPS_ENABLED")).secretValue == "" + ) { // default when no value present - return true + return true; } - return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true -} + return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true; +}; diff --git a/backend/src/index.ts b/backend/src/index.ts index e27e2fe33..a9861f1a6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -82,6 +82,7 @@ import { healthCheck } from "./routes/status"; import { RouteNotFoundError } from "./utils/errors"; import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { + getMongoURL, getNodeEnv, getPort, getSecretScanningGitAppId, @@ -102,8 +103,10 @@ let handler: null | any = null; const main = async () => { const port = await getPort(); - await setup(); + // initializing the database connection + await DatabaseService.initDatabase(await getMongoURL()); const serverCfg = await serverConfigInit(); + await setup(); await EELicenseService.initGlobalFeatureSet(); diff --git a/backend/src/utils/setup/backfillData.ts b/backend/src/utils/setup/backfillData.ts index 319c2605b..b12f51df0 100644 --- a/backend/src/utils/setup/backfillData.ts +++ b/backend/src/utils/setup/backfillData.ts @@ -2,12 +2,12 @@ import crypto from "crypto"; import { Types } from "mongoose"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; import { EESecretService } from "../../ee/services"; -import { redisClient } from "../../services/RedisService" -import { - IPType, - ISecretVersion, - Role, - SecretSnapshot, +import { redisClient } from "../../services/RedisService"; +import { + IPType, + ISecretVersion, + Role, + SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models"; @@ -30,7 +30,7 @@ import { Workspace } from "../../models"; import { generateKeyPair } from "../../utils/crypto"; -import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; +import { client, getEncryptionKey, getIsInfisicalCloud, getRootEncryptionKey } from "../../config"; import { ADMIN, ALGORITHM_AES_256_GCM, @@ -47,6 +47,7 @@ import { memberProjectPermissions } from "../../ee/services/ProjectRoleService"; import { logger } from "../logging"; +import { getServerConfig, updateServerConfig } from "../../config/serverConfig"; /** * Backfill secrets to ensure that they're all versioned and have @@ -693,7 +694,7 @@ export const backfillUserAuthMethods = async () => { export const backfillPermission = async () => { const lockKey = "backfill_permission_lock"; - const timeout = 900000; // 15 min lock timeout in milliseconds + const timeout = 900000; // 15 min lock timeout in milliseconds const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX"); if (lock) { @@ -705,13 +706,16 @@ export const backfillPermission = async () => { $exists: true, $ne: [] }, - role: MEMBER, + role: MEMBER }) .populate<{ workspace: IWorkspace }>("workspace") .lean(); // group memberships that need the same permission set - const roleMap = new Map(); + const roleMap = new Map< + string, + { membershipIds: string[]; permissions: any[]; organizationId: string; workspaceId: string } + >(); for (const membership of memberships) { // get permissions of members except secret permission @@ -729,11 +733,11 @@ export const backfillPermission = async () => { }); // environments that are not listed in deniedPermissions should be set to allowed for both read & and write - membership.workspace.environments.forEach(env => { + membership.workspace.environments.forEach((env) => { if (!secretAccessRule?.[env.slug]) { secretAccessRule[env.slug] = { read: true, write: true }; } - }) + }); const secretPermissions: any = []; Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => { @@ -769,21 +773,27 @@ export const backfillPermission = async () => { const value = roleMap.get(key); if (value) { value.membershipIds.push(membership._id.toString()); - value.organizationId = membership.workspace.organization.toString() - value.workspaceId = membership.workspace._id.toString() + value.organizationId = membership.workspace.organization.toString(); + value.workspaceId = membership.workspace._id.toString(); } else { - roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() }); + roleMap.set(key, { + membershipIds: [membership._id.toString()], + permissions: [...customPermissions, ...secretPermissions], + organizationId: membership.workspace.organization.toString(), + workspaceId: membership.workspace._id.toString() + }); } } for (const [key, value] of roleMap.entries()) { - const { membershipIds, permissions, workspaceId, organizationId } = value - const membership_identity = crypto.randomBytes(3).toString("hex") + const { membershipIds, permissions, workspaceId, organizationId } = value; + const membership_identity = crypto.randomBytes(3).toString("hex"); const role = new Role({ name: `Limited [${membership_identity.toUpperCase()}]`, organization: organizationId, workspace: workspaceId, - description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system", + description: + "This role was auto generated by Infisical in effort to migrate your project members to our new permission system", isOrgRole: false, slug: `custom-role-${membership_identity}`, permissions: permissions @@ -792,7 +802,8 @@ export const backfillPermission = async () => { await role.save(); for (const id of membershipIds) { - await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop + await Membership.findByIdAndUpdate(id, { + // document db doesn't support update many so we must loop $set: { role: CUSTOM, customRole: role @@ -815,11 +826,9 @@ export const backfillPermission = async () => { ); logger.info("Backfill: Finished converting owner role to member"); - } catch (error) { logger.error(error, "An error occurred when running script [backfillPermission]"); } - } else { logger.info("Could not acquire lock for script [backfillPermission], skipping"); } @@ -838,4 +847,34 @@ export const migrateRoleFromOwnerToAdmin = async () => { ); logger.info("Backfill: Finished converting owner role to member"); -} \ No newline at end of file +}; + +export const migrationAssignSuperadmin = async () => { + const users = await User.find({}).sort({ createdAt: 1 }).limit(2); + const serverCfg = getServerConfig(); + if (serverCfg.initialized) return; + + if (await getIsInfisicalCloud()) { + await updateServerConfig({ initialized: true }); + logger.info("Backfill: Infisical Cloud(initialized)"); + return; + } + + const isNewInstance = !users.length; + if (!isNewInstance) { + let superAdminUserId = ""; + const firstAccount = users?.[0]; + if (firstAccount.email === "test@localhost.local" && users.length === 2) { + superAdminUserId = users?.[1]?._id.toString(); + } else { + superAdminUserId = firstAccount._id.toString(); + } + + if (superAdminUserId) { + const user = await User.findByIdAndUpdate(superAdminUserId, { superAdmin: true }); + await updateServerConfig({ initialized: true }); + logger.info(`Migrated ${user?.email} to superuser`); + } + logger.info("Backfill: Migrated first infisical user to super admin"); + } +}; diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts index de087e684..e6bdd88a0 100644 --- a/backend/src/utils/setup/index.ts +++ b/backend/src/utils/setup/index.ts @@ -1,9 +1,8 @@ import * as Sentry from "@sentry/node"; -import { DatabaseService, TelemetryService } from "../../services"; +import { TelemetryService } from "../../services"; import { setTransporter } from "../../helpers/nodemailer"; import { EELicenseService } from "../../ee/services"; import { initSmtp } from "../../services/smtp"; -import { createTestUserForDevelopment } from "../addDevelopmentUser"; // eslint-disable-next-line @typescript-eslint/no-var-requires import { validateEncryptionKeysConfig } from "./validateConfig"; import { @@ -18,14 +17,15 @@ import { backfillServiceTokenMultiScope, backfillTrustedIps, backfillUserAuthMethods, - migrateRoleFromOwnerToAdmin + migrateRoleFromOwnerToAdmin, + migrationAssignSuperadmin } from "./backfillData"; import { reencryptBotOrgKeys, reencryptBotPrivateKeys, reencryptSecretBlindIndexDataSalts } from "./reencryptData"; -import { getMongoURL, getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; +import { getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; import { initializeGitHubStrategy, initializeGitLabStrategy, @@ -73,8 +73,6 @@ export const setup = async () => { // await reencryptBotPrivateKeys(); // await reencryptSecretBlindIndexDataSalts(); - // initializing the database connection - await DatabaseService.initDatabase(await getMongoURL()); await bootstrap({ transporter }); /** @@ -96,6 +94,7 @@ export const setup = async () => { await backfillUserAuthMethods(); // await backfillPermission(); await migrateRoleFromOwnerToAdmin(); + await migrationAssignSuperadmin(); // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY @@ -111,5 +110,7 @@ export const setup = async () => { environment: await getNodeEnv() }); - await createTestUserForDevelopment(); + // akhilmhdh: removed dev account as we have now admin account onboarding flow + // That will be user's first account going forward + // await createTestUserForDevelopment(); };