From 099cee7f3926b73a3f5467bcf17e0e512abbc126 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 3 May 2023 14:21:42 +0300 Subject: [PATCH] Begin refactoring backfilling and preparation operations into setup and start adding encryption metadata to models --- backend/src/config/index.ts | 14 +- backend/src/ee/helpers/secret.ts | 46 +--- backend/src/ee/services/EESecretService.ts | 12 +- backend/src/helpers/bot.ts | 23 +- backend/src/helpers/database.ts | 6 +- backend/src/helpers/secrets.ts | 55 +---- backend/src/helpers/workspace.ts | 1 - backend/src/index.ts | 21 +- backend/src/interfaces/utils/crypto.ts | 41 ++++ backend/src/interfaces/utils/index.ts | 1 + backend/src/models/backupPrivateKey.ts | 26 +++ backend/src/models/bot.ts | 27 +++ backend/src/models/integrationAuth.ts | 24 +++ backend/src/models/secretBlindIndexData.ts | 27 +++ backend/src/services/SecretService.ts | 12 -- backend/src/utils/crypto.ts | 139 ------------ backend/src/utils/crypto/index.ts | 237 +++++++++++++++++++++ backend/src/utils/setup/backfill.ts | 210 ++++++++++++++++++ backend/src/utils/setup/index.ts | 51 +++++ backend/src/validation/config.ts | 21 ++ backend/src/validation/index.ts | 1 + backend/src/variables/action.ts | 21 +- backend/src/variables/authentication.ts | 15 +- backend/src/variables/crypto.ts | 7 + backend/src/variables/environment.ts | 18 +- backend/src/variables/event.ts | 9 +- backend/src/variables/index.ts | 165 ++------------ backend/src/variables/integration.ts | 105 +++------ backend/src/variables/organization.ts | 12 +- backend/src/variables/permission.ts | 9 +- backend/src/variables/secret.ts | 9 +- backend/src/variables/smtp.ts | 15 +- backend/src/variables/stripe.ts | 9 +- backend/src/variables/token.ts | 15 +- backend/src/variables/user.ts | 6 +- backend/src/variables/workspace.ts | 0 backend/tests/helper/helper.ts | 6 +- 37 files changed, 797 insertions(+), 619 deletions(-) create mode 100644 backend/src/interfaces/utils/crypto.ts create mode 100644 backend/src/interfaces/utils/index.ts delete mode 100644 backend/src/utils/crypto.ts create mode 100644 backend/src/utils/crypto/index.ts create mode 100644 backend/src/utils/setup/backfill.ts create mode 100644 backend/src/utils/setup/index.ts create mode 100644 backend/src/validation/config.ts create mode 100644 backend/src/validation/index.ts create mode 100644 backend/src/variables/crypto.ts delete mode 100644 backend/src/variables/workspace.ts diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 5dce2bc09..326597db3 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,4 +1,5 @@ import InfisicalClient from 'infisical-node'; +import { validateEncryptionKey } from '../validation'; const client = new InfisicalClient({ token: process.env.INFISICAL_TOKEN! @@ -6,7 +7,18 @@ const client = new InfisicalClient({ export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000; export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue; -export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue; +export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue; // TODO: deprecate in favor of INFISICAL_ENCRYPTION_KEY +export const getRootEncryptionKey = async (): Promise => { + const encryptionKey = (await client.getSecret('ROOT_ENCRYPTION_KEY')).secretValue; + + if (encryptionKey) { + // validate [encryptionKey] to make sure it is in base64 format and 256-bit + validateEncryptionKey(encryptionKey); + return encryptionKey; + } + + return encryptionKey; +} export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10; export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d'; export const getJwtAuthSecret = async () => (await client.getSecret('JWT_AUTH_SECRET')).secretValue; diff --git a/backend/src/ee/helpers/secret.ts b/backend/src/ee/helpers/secret.ts index 0bf172581..c60b1a18c 100644 --- a/backend/src/ee/helpers/secret.ts +++ b/backend/src/ee/helpers/secret.ts @@ -93,52 +93,8 @@ const markDeletedSecretVersionsHelper = async ({ ); }; -/** - * Initialize secret versioning by setting previously unversioned - * secrets to version 1 and begin populating secret versions. - */ -const initSecretVersioningHelper = async () => { - await Secret.updateMany( - { version: { $exists: false } }, - { $set: { version: 1 } } - ); - - const unversionedSecrets: ISecret[] = await Secret.aggregate([ - { - $lookup: { - from: "secretversions", - localField: "_id", - foreignField: "secret", - as: "versions", - }, - }, - { - $match: { - versions: { $size: 0 }, - }, - }, - ]); - - if (unversionedSecrets.length > 0) { - await addSecretVersionsHelper({ - secretVersions: unversionedSecrets.map( - (s, idx) => - new SecretVersion({ - ...s, - secret: s._id, - version: s.version ? s.version : 1, - isDeleted: false, - workspace: s.workspace, - environment: s.environment, - }) - ), - }); - } -}; - export { takeSecretSnapshotHelper, addSecretVersionsHelper, - markDeletedSecretVersionsHelper, - initSecretVersioningHelper, + markDeletedSecretVersionsHelper }; diff --git a/backend/src/ee/services/EESecretService.ts b/backend/src/ee/services/EESecretService.ts index e408e848e..e84e35816 100644 --- a/backend/src/ee/services/EESecretService.ts +++ b/backend/src/ee/services/EESecretService.ts @@ -3,8 +3,7 @@ import { ISecretVersion } from '../models'; import { takeSecretSnapshotHelper, addSecretVersionsHelper, - markDeletedSecretVersionsHelper, - initSecretVersioningHelper + markDeletedSecretVersionsHelper } from '../helpers/secret'; import EELicenseService from './EELicenseService'; @@ -64,15 +63,6 @@ class EESecretService { secretIds }); } - - /** - * Initialize secret versioning by setting previously unversioned - * secrets to version 1 and begin populating secret versions. - */ - static async initSecretVersioning() { - if (!EELicenseService.isLicenseValid) return; - await initSecretVersioningHelper(); - } } export default EESecretService; \ No newline at end of file diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index 96ad452d9..fe7aba31f 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -13,10 +13,10 @@ import { } from "../models"; import { generateKeyPair, - encryptSymmetric, - decryptSymmetric, - decryptAsymmetric, -} from "../utils/crypto"; + encryptSymmetric128BitHexKeyUTF8, + decryptSymmetric128BitHexKeyUTF8, + decryptAsymmetric +} from '../utils/crypto'; import { SECRET_SHARED, AUTH_MODE_JWT, @@ -26,7 +26,6 @@ import { } from "../variables"; import { getEncryptionKey } from "../config"; import { BotNotFoundError, UnauthorizedRequestError } from "../utils/errors"; -import { validateMembership } from "../helpers/membership"; import { validateUserClientForWorkspace } from "../helpers/user"; import { validateServiceAccountClientForWorkspace } from "../helpers/serviceAccount"; @@ -120,7 +119,7 @@ const createBot = async ({ workspaceId: Types.ObjectId; }) => { const { publicKey, privateKey } = generateKeyPair(); - const { ciphertext, iv, tag } = encryptSymmetric({ + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ plaintext: privateKey, key: await getEncryptionKey(), }); @@ -161,14 +160,14 @@ const getSecretsHelper = async ({ }); secrets.forEach((secret: ISecret) => { - const secretKey = decryptSymmetric({ + const secretKey = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretKeyCiphertext, iv: secret.secretKeyIV, tag: secret.secretKeyTag, key, }); - const secretValue = decryptSymmetric({ + const secretValue = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secret.secretValueCiphertext, iv: secret.secretValueIV, tag: secret.secretValueTag, @@ -202,7 +201,7 @@ const getKey = async ({ workspaceId }: { workspaceId: string }) => { if (!bot) throw new Error("Failed to find bot"); if (!bot.isActive) throw new Error("Bot is not active"); - const privateKeyBot = decryptSymmetric({ + const privateKeyBot = decryptSymmetric128BitHexKeyUTF8({ ciphertext: bot.encryptedPrivateKey, iv: bot.iv, tag: bot.tag, @@ -234,7 +233,7 @@ const encryptSymmetricHelper = async ({ plaintext: string; }) => { const key = await getKey({ workspaceId: workspaceId.toString() }); - const { ciphertext, iv, tag } = encryptSymmetric({ + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ plaintext, key, }); @@ -266,7 +265,7 @@ const decryptSymmetricHelper = async ({ tag: string; }) => { const key = await getKey({ workspaceId: workspaceId.toString() }); - const plaintext = decryptSymmetric({ + const plaintext = decryptSymmetric128BitHexKeyUTF8({ ciphertext, iv, tag, @@ -281,5 +280,5 @@ export { createBot, getSecretsHelper, encryptSymmetricHelper, - decryptSymmetricHelper, + decryptSymmetricHelper }; diff --git a/backend/src/helpers/database.ts b/backend/src/helpers/database.ts index 4bfaf1305..fa8c35155 100644 --- a/backend/src/helpers/database.ts +++ b/backend/src/helpers/database.ts @@ -1,6 +1,4 @@ import mongoose from 'mongoose'; -import { EESecretService } from '../ee/services'; -import { SecretService } from '../services'; import { getLogger } from '../utils/logger'; /** @@ -21,9 +19,7 @@ const initDatabaseHelper = async ({ mongoose.Schema.Types.String.checkRequired(v => typeof v === 'string'); (await getLogger("database")).info("Database connection established"); - - await EESecretService.initSecretVersioning(); - await SecretService.initSecretBlindIndexDataHelper(); + } catch (err) { (await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`); } diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index c184bad9c..fe39859d8 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -54,9 +54,9 @@ import { } from '../variables'; import crypto from 'crypto'; import * as argon2 from 'argon2'; -import { - encryptSymmetric, - decryptSymmetric +import { + encryptSymmetric128BitHexKeyUTF8, + decryptSymmetric128BitHexKeyUTF8 } from '../utils/crypto'; import { getEncryptionKey } from '../config'; import { TelemetryService } from '../services'; @@ -220,48 +220,6 @@ const validateClientForSecrets = async ({ }); } -/** - * Initialize secret blind index data by setting previously - * un-initialized projects to have secret blind index data - * (Ensures that all projects have associated blind index data) - */ -const initSecretBlindIndexDataHelper = async () => { - const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace'); - const workspaceIdsToBlindIndex = await Workspace.distinct('_id', { - _id: { - $nin: workspaceIdsBlindIndexed - } - }); - - const secretBlindIndexDataToInsert = await Promise.all( - workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => { - const salt = crypto.randomBytes(16).toString('base64'); - - const { - ciphertext: encryptedSaltCiphertext, - iv: saltIV, - tag: saltTag - } = encryptSymmetric({ - plaintext: salt, - key: await getEncryptionKey() - }); - - const secretBlindIndexData = new SecretBlindIndexData({ - workspace: workspaceToBlindIndex, - encryptedSaltCiphertext, - saltIV, - saltTag - }) - - return secretBlindIndexData; - }) - ); - - if (secretBlindIndexDataToInsert.length > 0) { - await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert); - } -} - /** * Create secret blind index data containing encrypted blind index [salt] * for workspace with id [workspaceId] @@ -280,7 +238,7 @@ const createSecretBlindIndexDataHelper = async ({ ciphertext: encryptedSaltCiphertext, iv: saltIV, tag: saltTag - } = encryptSymmetric({ + } = encryptSymmetric128BitHexKeyUTF8({ plaintext: salt, key: await getEncryptionKey() }); @@ -314,7 +272,7 @@ const getSecretBlindIndexSaltHelper = async ({ if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError(); // decrypt workspace salt - const salt = decryptSymmetric({ + const salt = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secretBlindIndexData.encryptedSaltCiphertext, iv: secretBlindIndexData.saltIV, tag: secretBlindIndexData.saltTag, @@ -376,7 +334,7 @@ const generateSecretBlindIndexHelper = async ({ if (!secretBlindIndexData) throw SecretBlindIndexDataNotFoundError(); // decrypt workspace salt - const salt = decryptSymmetric({ + const salt = decryptSymmetric128BitHexKeyUTF8({ ciphertext: secretBlindIndexData.encryptedSaltCiphertext, iv: secretBlindIndexData.saltIV, tag: secretBlindIndexData.saltTag, @@ -934,7 +892,6 @@ const deleteSecretHelper = async ({ export { validateClientForSecret, validateClientForSecrets, - initSecretBlindIndexDataHelper, createSecretBlindIndexDataHelper, getSecretBlindIndexSaltHelper, generateSecretBlindIndexWithSaltHelper, diff --git a/backend/src/helpers/workspace.ts b/backend/src/helpers/workspace.ts index 5047a1967..1b6e7737e 100644 --- a/backend/src/helpers/workspace.ts +++ b/backend/src/helpers/workspace.ts @@ -28,7 +28,6 @@ import { AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_API_KEY } from '../variables'; -import { encryptSymmetric } from '../utils/crypto'; import { SecretService } from '../services'; /** diff --git a/backend/src/index.ts b/backend/src/index.ts index 7270470ce..e15f330b9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -4,15 +4,9 @@ dotenv.config(); import express from 'express'; import helmet from 'helmet'; import cors from 'cors'; -import * as Sentry from '@sentry/node'; import { DatabaseService } from './services'; import { setUpHealthEndpoint } from './services/health'; -import { initSmtp } from './services/smtp'; import { TelemetryService } from './services'; -import { setTransporter } from './helpers/nodemailer'; -import { createTestUserForDevelopment } from './utils/addDevelopmentUser'; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { patchRouterParam } = require('./utils/patchAsyncRoutes'); import cookieParser from 'cookie-parser'; import swaggerUi = require('swagger-ui-express'); @@ -77,22 +71,13 @@ import { getSiteURL, getSmtpHost } from './config'; +import { setup } from './utils/setup'; const main = async () => { TelemetryService.logTelemetryMessage(); - setTransporter(await initSmtp()); - await DatabaseService.initDatabase(await getMongoURL()); - if ((await getNodeEnv()) !== 'test') { - Sentry.init({ - dsn: await getSentryDSN(), - tracesSampleRate: 1.0, - debug: await getNodeEnv() === 'production' ? false : true, - environment: await getNodeEnv() - }); - } + await setup(); - patchRouterParam(); const app = express(); app.enable('trust proxy'); app.use(express.json()); @@ -176,7 +161,7 @@ const main = async () => { (await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`) }); - await createTestUserForDevelopment(); + // await createTestUserForDevelopment(); setUpHealthEndpoint(server); server.on('close', async () => { diff --git a/backend/src/interfaces/utils/crypto.ts b/backend/src/interfaces/utils/crypto.ts new file mode 100644 index 000000000..cc2c54e3b --- /dev/null +++ b/backend/src/interfaces/utils/crypto.ts @@ -0,0 +1,41 @@ +export interface IGenerateKeyPairOutput { + publicKey: string; + privateKey: string +} + +export interface IEncryptAsymmetricInput { + plaintext: string; + publicKey: string; + privateKey: string; +} + +export interface IEncryptAsymmetricOutput { + ciphertext: string; + nonce: string; +} + +export interface IDecryptAsymmetricInput { + ciphertext: string; + nonce: string; + publicKey: string; + privateKey: string; +} + +export interface IEncryptSymmetricInput { + plaintext: string; + key: string; +} + +export interface IEncryptSymmetricOutput { + ciphertext: string; + iv: string; + tag: string; +} + +export interface IDecryptSymmetricInput { + ciphertext: string; + iv: string; + tag: string; + key: string; +} + diff --git a/backend/src/interfaces/utils/index.ts b/backend/src/interfaces/utils/index.ts new file mode 100644 index 000000000..7d27c9105 --- /dev/null +++ b/backend/src/interfaces/utils/index.ts @@ -0,0 +1 @@ +export * from './crypto'; \ No newline at end of file diff --git a/backend/src/models/backupPrivateKey.ts b/backend/src/models/backupPrivateKey.ts index 70dcd6475..580b0ab38 100644 --- a/backend/src/models/backupPrivateKey.ts +++ b/backend/src/models/backupPrivateKey.ts @@ -1,4 +1,9 @@ import { Schema, model, Types } from 'mongoose'; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 +} from '../variables'; export interface IBackupPrivateKey { _id: Types.ObjectId; @@ -7,6 +12,9 @@ export interface IBackupPrivateKey { iv: string; tag: string; salt: string; + algorithm: string; + keySize: number; + keyEncoding: 'base64' | 'utf8'; verifier: string; } @@ -32,6 +40,24 @@ const backupPrivateKeySchema = new Schema( select: false, required: true }, + algorithm: { // the encryption algorithm used + type: String, + enum: [ALGORITHM_AES_256_GCM], + required: true + }, + keySize: { // the size of the key used in the algorithm + type: Number, + enum: [256], + required: true + }, + keyEncoding: { + type: String, + enum: [ + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 + ], + required: true + }, salt: { type: String, select: false, diff --git a/backend/src/models/bot.ts b/backend/src/models/bot.ts index c7e5a9abe..85b1c0adb 100644 --- a/backend/src/models/bot.ts +++ b/backend/src/models/bot.ts @@ -1,4 +1,10 @@ import { Schema, model, Types } from 'mongoose'; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_HEX, + ENCODING_SCHEME_BASE64 +} from '../variables'; export interface IBot { _id: Types.ObjectId; @@ -9,6 +15,9 @@ export interface IBot { encryptedPrivateKey: string; iv: string; tag: string; + algorithm: 'aes-256-gcm'; + keySize: 256; + keyEncoding: 'base64' | 'utf8'; } const botSchema = new Schema( @@ -45,6 +54,24 @@ const botSchema = new Schema( type: String, required: true, select: false + }, + algorithm: { // the encryption algorithm used + type: String, + enum: [ALGORITHM_AES_256_GCM], + required: true + }, + keySize: { // the size of the key used in the algorithm + type: Number, + enum: [256], + required: true + }, + keyEncoding: { + type: String, + enum: [ + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 + ], + required: true } }, { diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index ead969fc7..b55aa9b2f 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -14,6 +14,9 @@ import { INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 } from "../variables"; export interface IIntegrationAuth extends Document { @@ -31,6 +34,9 @@ export interface IIntegrationAuth extends Document { accessCiphertext?: string; accessIV?: string; accessTag?: string; + algorithm?: 'aes-256-gcm'; + keySize?: 256; + keyEncoding: 'utf8' | 'base64'; accessExpiresAt?: Date; } @@ -109,6 +115,24 @@ const integrationAuthSchema = new Schema( type: Date, select: false, }, + algorithm: { // the encryption algorithm used + type: String, + enum: [ALGORITHM_AES_256_GCM], + required: true + }, + keySize: { // the size of the key used in the algorithm + type: Number, + enum: [256], + required: true + }, + keyEncoding: { + type: String, + enum: [ + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 + ], + required: true + } }, { timestamps: true, diff --git a/backend/src/models/secretBlindIndexData.ts b/backend/src/models/secretBlindIndexData.ts index 47649dde4..47e82c053 100644 --- a/backend/src/models/secretBlindIndexData.ts +++ b/backend/src/models/secretBlindIndexData.ts @@ -1,4 +1,9 @@ import { Schema, model, Types, Document } from 'mongoose'; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 +} from '../variables'; export interface ISecretBlindIndexData extends Document { _id: Types.ObjectId; @@ -6,6 +11,9 @@ export interface ISecretBlindIndexData extends Document { encryptedSaltCiphertext: string; saltIV: string; saltTag: string; + algorithm: 'aes-256-gcm'; + keySize: 256; + keyEncoding: 'base64' | 'utf8' } const secretBlindIndexDataSchema = new Schema( @@ -26,7 +34,26 @@ const secretBlindIndexDataSchema = new Schema( saltTag: { type: String, required: true + }, + algorithm: { + type: String, + enum: [ALGORITHM_AES_256_GCM], + required: true + }, + keySize: { + type: Number, + enum: [256], + required: true + }, + keyEncoding: { + type: String, + enum: [ + ENCODING_SCHEME_UTF8, + ENCODING_SCHEME_BASE64 + ], + required: true } + } ); diff --git a/backend/src/services/SecretService.ts b/backend/src/services/SecretService.ts index c215431f1..5b5b73e5f 100644 --- a/backend/src/services/SecretService.ts +++ b/backend/src/services/SecretService.ts @@ -1,4 +1,3 @@ -// WIP import { Types } from 'mongoose'; import { ISecret @@ -11,7 +10,6 @@ import { DeleteSecretParams } from '../interfaces/services/SecretService'; import { - initSecretBlindIndexDataHelper, createSecretBlindIndexDataHelper, getSecretBlindIndexSaltHelper, generateSecretBlindIndexWithSaltHelper, @@ -24,16 +22,6 @@ import { } from '../helpers/secrets'; class SecretService { - - /** - * - * @param param0 h - * @returns - */ - - static async initSecretBlindIndexDataHelper() { - return await initSecretBlindIndexDataHelper(); - } /** * Create secret blind index data containing encrypted blind index salt diff --git a/backend/src/utils/crypto.ts b/backend/src/utils/crypto.ts deleted file mode 100644 index ec61b496e..000000000 --- a/backend/src/utils/crypto.ts +++ /dev/null @@ -1,139 +0,0 @@ -import nacl from 'tweetnacl'; -import util from 'tweetnacl-util'; -import AesGCM from './aes-gcm'; - -/** - * Return new base64, NaCl, public-private key pair. - * @returns {Object} obj - * @returns {String} obj.publicKey - base64, NaCl, public key - * @returns {String} obj.privateKey - base64, NaCl, private key - */ -const generateKeyPair = () => { - const pair = nacl.box.keyPair(); - - return ({ - publicKey: util.encodeBase64(pair.publicKey), - privateKey: util.encodeBase64(pair.secretKey) - }); -} - -/** - * Return assymmetrically encrypted [plaintext] using [publicKey] where - * [publicKey] likely belongs to the recipient. - * @param {Object} obj - * @param {String} obj.plaintext - plaintext to encrypt - * @param {String} obj.publicKey - public key of the recipient - * @param {String} obj.privateKey - private key of the sender (current user) - * @returns {Object} obj - * @returns {String} ciphertext - base64-encoded ciphertext - * @returns {String} nonce - base64-encoded nonce - */ -const encryptAsymmetric = ({ - plaintext, - publicKey, - privateKey -}: { - plaintext: string; - publicKey: string; - privateKey: string; -}) => { - const nonce = nacl.randomBytes(24); - const ciphertext = nacl.box( - util.decodeUTF8(plaintext), - nonce, - util.decodeBase64(publicKey), - util.decodeBase64(privateKey) - ); - - return { - ciphertext: util.encodeBase64(ciphertext), - nonce: util.encodeBase64(nonce) - }; -}; - -/** - * Return assymmetrically decrypted [ciphertext] using [privateKey] where - * [privateKey] likely belongs to the recipient. - * @param {Object} obj - * @param {String} obj.ciphertext - ciphertext to decrypt - * @param {String} obj.nonce - nonce - * @param {String} obj.publicKey - public key of the sender - * @param {String} obj.privateKey - private key of the receiver (current user) - * @param {String} plaintext - UTF8 plaintext - */ -const decryptAsymmetric = ({ - ciphertext, - nonce, - publicKey, - privateKey -}: { - ciphertext: string; - nonce: string; - publicKey: string; - privateKey: string; -}): string => { - const plaintext: any = nacl.box.open( - util.decodeBase64(ciphertext), - util.decodeBase64(nonce), - util.decodeBase64(publicKey), - util.decodeBase64(privateKey) - ); - - return util.encodeUTF8(plaintext); -}; - -/** - * Return symmetrically encrypted [plaintext] using [key]. - * @param {Object} obj - * @param {String} obj.plaintext - plaintext to encrypt - * @param {String} obj.key - hex key - */ -const encryptSymmetric = ({ - plaintext, - key -}: { - plaintext: string; - key: string; -}) => { - const obj = AesGCM.encrypt(plaintext, key); - const { ciphertext, iv, tag } = obj; - - return { - ciphertext, - iv, - tag - }; -}; - -/** - * Return symmetrically decypted [ciphertext] using [iv], [tag], - * and [key]. - * @param {Object} obj - * @param {String} obj.ciphertext - ciphertext to decrypt - * @param {String} obj.iv - iv - * @param {String} obj.tag - tag - * @param {String} obj.key - hex key - * - */ -const decryptSymmetric = ({ - ciphertext, - iv, - tag, - key -}: { - ciphertext: string; - iv: string; - tag: string; - key: string; -}): string => { - const plaintext = AesGCM.decrypt(ciphertext, iv, tag, key); - return plaintext; -}; - -export { - generateKeyPair, - encryptAsymmetric, - decryptAsymmetric, - encryptSymmetric, - decryptSymmetric -}; diff --git a/backend/src/utils/crypto/index.ts b/backend/src/utils/crypto/index.ts new file mode 100644 index 000000000..bfe9df85a --- /dev/null +++ b/backend/src/utils/crypto/index.ts @@ -0,0 +1,237 @@ +import crypto from 'crypto'; +import nacl from 'tweetnacl'; +import util from 'tweetnacl-util'; +import { + IGenerateKeyPairOutput, + IEncryptAsymmetricInput, + IEncryptAsymmetricOutput, + IDecryptAsymmetricInput, + IEncryptSymmetricInput, + IEncryptSymmetricOutput, + IDecryptSymmetricInput +} from '../../interfaces/utils'; +import { + BadRequestError, + InternalServerError +} from '../errors'; +import { + ALGORITHM_AES_256_GCM, + BLOCK_SIZE_BYTES_32, + BLOCK_SIZE_BYTES_16 +} from '../../variables'; +import { validateEncryptionKey } from '../../validation'; + +/** + * Return new base64, NaCl, public-private key pair. + * @returns {Object} obj + * @returns {String} obj.publicKey - (base64) NaCl, public key + * @returns {String} obj.privateKey - (base64), NaCl, private key + */ +const generateKeyPair = (): IGenerateKeyPairOutput => { + const pair = nacl.box.keyPair(); + + return ({ + publicKey: util.encodeBase64(pair.publicKey), + privateKey: util.encodeBase64(pair.secretKey) + }); +} + +/** + * Return assymmetrically encrypted [plaintext] using [publicKey] where + * [publicKey] likely belongs to the recipient. + * @param {Object} obj + * @param {String} obj.plaintext - plaintext to encrypt + * @param {String} obj.publicKey - (base64) Nacl public key of the recipient + * @param {String} obj.privateKey - (base64) Nacl private key of the sender (current user) + * @returns {Object} obj + * @returns {String} obj.ciphertext - (base64) ciphertext + * @returns {String} obj.nonce - (base64) nonce + */ +const encryptAsymmetric = ({ + plaintext, + publicKey, + privateKey +}: IEncryptAsymmetricInput): IEncryptAsymmetricOutput => { + const nonce = nacl.randomBytes(24); + const ciphertext = nacl.box( + util.decodeUTF8(plaintext), + nonce, + util.decodeBase64(publicKey), + util.decodeBase64(privateKey) + ); + + return { + ciphertext: util.encodeBase64(ciphertext), + nonce: util.encodeBase64(nonce) + }; +}; + +/** + * Return assymmetrically decrypted [ciphertext] using [privateKey] where + * [privateKey] likely belongs to the recipient. + * @param {Object} obj + * @param {String} obj.ciphertext - ciphertext to decrypt + * @param {String} obj.nonce - (base64) nonce + * @param {String} obj.publicKey - (base64) public key of the sender + * @param {String} obj.privateKey - (base64) private key of the receiver (current user) + * @returns {String} plaintext - (utf8) plaintext + */ +const decryptAsymmetric = ({ + ciphertext, + nonce, + publicKey, + privateKey +}: IDecryptAsymmetricInput): string => { + const plaintext: Uint8Array | null = nacl.box.open( + util.decodeBase64(ciphertext), + util.decodeBase64(nonce), + util.decodeBase64(publicKey), + util.decodeBase64(privateKey) + ); + + if (plaintext == null) throw BadRequestError({ + message: 'Invalid ciphertext or keys' + }); + + return util.encodeUTF8(plaintext); +}; + +/** + * Return symmetrically encrypted [plaintext] using [key]. + * @param {Object} obj + * @param {String} obj.plaintext - (utf8) plaintext to encrypt + * @param {String} obj.key - (base64) 256-bit key + * @returns {Object} obj + * @returns {String} obj.ciphertext (base64) ciphertext + * @returns {String} obj.iv (base64) iv + * @returns {String} obj.tag (base64) tag + */ +const encryptSymmetric = ({ + plaintext, + key +}: IEncryptSymmetricInput): IEncryptSymmetricOutput => { + validateEncryptionKey(key); + + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_32); + const secretKey = crypto.createSecretKey(key, 'base64'); + const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, secretKey, iv); + + let ciphertext = cipher.update(plaintext, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + + return { + ciphertext, + iv: iv.toString('base64'), + tag: cipher.getAuthTag().toString('base64') + }; +}; + +/** + * Return symmetrically decrypted [ciphertext] using [iv], [tag], + * and [key]. + * @param {Object} obj + * @param {String} obj.ciphertext - ciphertext to decrypt + * @param {String} obj.iv - (base64) 256-bit iv + * @param {String} obj.tag - (base64) tag + * @param {String} obj.key - (base64) 256-bit key + * @returns {String} cleartext - the deciphered ciphertext + */ +const decryptSymmetric = ({ + ciphertext, + iv, + tag, + key +}: IDecryptSymmetricInput): string => { + validateEncryptionKey(key); + + const secretKey = crypto.createSecretKey(key, 'base64'); + + const decipher = crypto.createDecipheriv( + ALGORITHM_AES_256_GCM, + secretKey, + Buffer.from(iv, 'base64') + ); + + decipher.setAuthTag(Buffer.from(tag, 'base64')); + + let cleartext = decipher.update(ciphertext, 'base64', 'utf8'); + cleartext += decipher.final('utf8'); + + return cleartext; +}; + +/** + * Return symmetrically encrypted [plaintext] using [key]. + * + * NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE + * ENCRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY + * THAT USES IT. USE encryptSymmetric() instead + * + * @param {Object} obj + * @param {String} obj.plaintext - (utf8) plaintext to encrypt + * @param {String} obj.key - (base64) 256-bit key + * @returns {Object} obj + * @returns {String} obj.ciphertext (base64) ciphertext + * @returns {String} obj.iv (base64) iv + * @returns {String} obj.tag (base64) tag + */ +const encryptSymmetric128BitHexKeyUTF8 = ({ + plaintext, + key +}: IEncryptSymmetricInput) => { + const iv = crypto.randomBytes(BLOCK_SIZE_BYTES_16); + const cipher = crypto.createCipheriv(ALGORITHM_AES_256_GCM, key, iv); + + let ciphertext = cipher.update(plaintext, 'utf8', 'base64'); + ciphertext += cipher.final('base64'); + + return { + ciphertext, + iv: iv.toString('base64'), + tag: cipher.getAuthTag().toString('base64') + }; +} +/** + * Return symmetrically decrypted [ciphertext] using [iv], [tag], + * and [key]. + * + * NOTE: THIS FUNCTION SHOULD NOT BE USED FOR ALL FUTURE + * DECRYPTION OPERATIONS UNLESS IT TOUCHES OLD FUNCTIONALITY + * THAT USES IT. USE decryptSymmetric() instead + * + * @param {Object} obj + * @param {String} obj.ciphertext - ciphertext to decrypt + * @param {String} obj.iv - (base64) 256-bit iv + * @param {String} obj.tag - (base64) tag + * @param {String} obj.key - (hex) 128-bit key + * @returns {String} cleartext - the deciphered ciphertext + */ +const decryptSymmetric128BitHexKeyUTF8 = ({ + ciphertext, + iv, + tag, + key +}: IDecryptSymmetricInput) => { + const decipher = crypto.createDecipheriv( + ALGORITHM_AES_256_GCM, + key, + Buffer.from(iv, 'base64') + ); + + decipher.setAuthTag(Buffer.from(tag, 'base64')); + + let cleartext = decipher.update(ciphertext, 'base64', 'utf8'); + cleartext += decipher.final('utf8'); + + return cleartext; +} + +export { + generateKeyPair, + encryptAsymmetric, + decryptAsymmetric, + encryptSymmetric, + decryptSymmetric, + encryptSymmetric128BitHexKeyUTF8, + decryptSymmetric128BitHexKeyUTF8 +}; diff --git a/backend/src/utils/setup/backfill.ts b/backend/src/utils/setup/backfill.ts new file mode 100644 index 000000000..4a06fe9ee --- /dev/null +++ b/backend/src/utils/setup/backfill.ts @@ -0,0 +1,210 @@ +import crypto from 'crypto'; +import { encryptSymmetric128BitHexKeyUTF8 } from '../crypto'; +import { EESecretService } from '../../ee/services'; +import { SecretVersion } from '../../ee/models'; +import { + Secret, + ISecret, + SecretBlindIndexData, + Workspace, + Bot, + BackupPrivateKey, + IntegrationAuth +} from '../../models'; +import { getEncryptionKey, getRootEncryptionKey } from '../../config'; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_UTF8 +} from '../../variables'; + +/** + * + */ +export const backfillSecretVersions = async () => { + await Secret.updateMany( + { version: { $exists: false } }, + { $set: { version: 1 } } + ); + + const unversionedSecrets: ISecret[] = await Secret.aggregate([ + { + $lookup: { + from: "secretversions", + localField: "_id", + foreignField: "secret", + as: "versions", + }, + }, + { + $match: { + versions: { $size: 0 }, + }, + }, + ]); + + if (unversionedSecrets.length > 0) { + await EESecretService.addSecretVersions({ + secretVersions: unversionedSecrets.map( + (s, idx) => + new SecretVersion({ + ...s, + secret: s._id, + version: s.version ? s.version : 1, + isDeleted: false, + workspace: s.workspace, + environment: s.environment, + }) + ), + }); + } +} + +export const backfillSecretBlindIndexData = async () => { + const workspaceIdsBlindIndexed = await SecretBlindIndexData.distinct('workspace'); + const workspaceIdsToBlindIndex = await Workspace.distinct('_id', { + _id: { + $nin: workspaceIdsBlindIndexed + } + }); + + const secretBlindIndexDataToInsert = await Promise.all( + workspaceIdsToBlindIndex.map(async (workspaceToBlindIndex) => { + const salt = crypto.randomBytes(16).toString('base64'); + + const { + ciphertext: encryptedSaltCiphertext, + iv: saltIV, + tag: saltTag + } = encryptSymmetric128BitHexKeyUTF8({ + plaintext: salt, + key: await getEncryptionKey() + }); + + const secretBlindIndexData = new SecretBlindIndexData({ + workspace: workspaceToBlindIndex, + encryptedSaltCiphertext, + saltIV, + saltTag + }) + + return secretBlindIndexData; + }) + ); + + if (secretBlindIndexDataToInsert.length > 0) { + await SecretBlindIndexData.insertMany(secretBlindIndexDataToInsert); + } +} + +export const backfillEncryptionMetadata = async () => { + + // backfill bot encryption metadata + await Bot.updateMany( + { + algorithm: { + $exists: false + }, + keySize: { + $exists: false + }, + keyEncoding: { + $exists: false + } + }, + { + $set: { + algorithm: ALGORITHM_AES_256_GCM, + keySize: 256, + keyEncoding: ENCODING_SCHEME_UTF8 + } + } + ); + + // backfill secret blind index encryption metadata + await SecretBlindIndexData.updateMany( + { + algorithm: { + $exists: false + }, + keySize: { + $exists: false + }, + keyEncoding: { + $exists: false + } + }, + { + $set: { + algorithm: ALGORITHM_AES_256_GCM, + keySize: 256, + keyEncoding: ENCODING_SCHEME_UTF8 + } + } + ); + + // backfill backup private key encryption metadata + await BackupPrivateKey.updateMany( + { + algorithm: { + $exists: false + }, + keySize: { + $exists: false + }, + keyEncoding: { + $exists: false + } + }, + { + $set: { + algorithm: ALGORITHM_AES_256_GCM, + keySize: 256, + keyEncoding: ENCODING_SCHEME_UTF8 + } + } + ); + + // backfill integration auth encryption metadata + await IntegrationAuth.updateMany( + { + + }, + { + $set: { + algorithm: ALGORITHM_AES_256_GCM, + + } + } + ); + + // TODO: blind indices + // TODO: secret versions and snapshots etc. + + // TODO: re-encrypt keys logic + // TODO: how do you handle different parts of the software + // encrypting under different schemes? + + // const encryptionKey = await getEncryptionKey(); + // const rootEncryptionKey = await getRootEncryptionKey(); + // console.log('rootEncryptionKey: ', rootEncryptionKey); + + // if (encryptionKey && rootEncryptionKey) { + // // case: both the old encryption key and new encryption key are present + // // -> perform migration if needed + // console.log('rootEncryptionKey is defined'); + + // const bots = await Bot.find({ + // algorithm: ALGORITHM_AES_256_GCM, + // keySize: 256, + // keyEncoding: ENCODING_SCHEME_UTF8 + // }, 'encryptedPrivateKey iv tag'); + + // if (bots.length > 0) { + // // TODO: unencrypt and re-encrypt + // // TODO: unencrypt and re-encrypt blind-indices + // // probably then need to move this function + + // console.log('bots: ', bots); + // } + // } +} diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts new file mode 100644 index 000000000..059edf6f6 --- /dev/null +++ b/backend/src/utils/setup/index.ts @@ -0,0 +1,51 @@ +import * as Sentry from '@sentry/node'; +import { DatabaseService } from '../../services'; +import { setTransporter } from '../../helpers/nodemailer'; +import { initSmtp } from '../../services/smtp'; +import { createTestUserForDevelopment } from '../addDevelopmentUser' +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { patchRouterParam } = require('../patchAsyncRoutes'); +import { + backfillSecretVersions, + backfillSecretBlindIndexData, + backfillEncryptionMetadata +} from './backfill'; +import { + getNodeEnv, + getMongoURL, + getSentryDSN +} from '../../config'; + +/** + * Prepare Infisical upon startup. This includes tasks like: + * - Initializing SMTP configuration + * - Initializing the database connection + * - Initializing Sentry + * - Backfilling data + */ +export const setup = async () => { + // initializing SMTP configuration + setTransporter(await initSmtp()); + + // initializing the database connection + await DatabaseService.initDatabase(await getMongoURL()); + + // backfilling data + await backfillSecretVersions(); + await backfillSecretBlindIndexData(); + await backfillEncryptionMetadata(); + + // initializing Sentry + if ((await getNodeEnv()) !== 'development') { + Sentry.init({ + dsn: await getSentryDSN(), + tracesSampleRate: 1.0, + debug: await getNodeEnv() === 'production' ? false : true, + environment: await getNodeEnv() + }); + } + + patchRouterParam(); + await createTestUserForDevelopment(); +} + diff --git a/backend/src/validation/config.ts b/backend/src/validation/config.ts new file mode 100644 index 000000000..c96164929 --- /dev/null +++ b/backend/src/validation/config.ts @@ -0,0 +1,21 @@ +import { InternalServerError } from "../utils/errors"; + +/** + * Validate that the encryption key [encryptionKey] is in base64 format and 256-bit + * @param {String} encryptionKey - the encryption key to validate + */ +export const validateEncryptionKey = (encryptionKey: string): Buffer => { + + const keyBuffer = Buffer.from(encryptionKey, 'base64') + const decoded = keyBuffer.toString('base64'); + + if (decoded !== encryptionKey) throw InternalServerError({ + message: 'Failed to validate the format of the encryption key. Please check that it is in base64 format.' + }); + + if (keyBuffer.length !== 32) throw InternalServerError({ + message: 'Failed to validate that the encryption key is 256-bit. Please check that it is 256-bit.' + }); + + return keyBuffer; +}; \ No newline at end of file diff --git a/backend/src/validation/index.ts b/backend/src/validation/index.ts new file mode 100644 index 000000000..de8bde7ba --- /dev/null +++ b/backend/src/validation/index.ts @@ -0,0 +1 @@ +export * from './config'; \ No newline at end of file diff --git a/backend/src/variables/action.ts b/backend/src/variables/action.ts index 682b357a7..9d4fb18d9 100644 --- a/backend/src/variables/action.ts +++ b/backend/src/variables/action.ts @@ -1,15 +1,6 @@ -const ACTION_LOGIN = 'login'; -const ACTION_LOGOUT = 'logout'; -const ACTION_ADD_SECRETS = 'addSecrets'; -const ACTION_DELETE_SECRETS = 'deleteSecrets'; -const ACTION_UPDATE_SECRETS = 'updateSecrets'; -const ACTION_READ_SECRETS = 'readSecrets'; - -export { - ACTION_LOGIN, - ACTION_LOGOUT, - ACTION_ADD_SECRETS, - ACTION_DELETE_SECRETS, - ACTION_UPDATE_SECRETS, - ACTION_READ_SECRETS -} \ No newline at end of file +export const ACTION_LOGIN = 'login'; +export const ACTION_LOGOUT = 'logout'; +export const ACTION_ADD_SECRETS = 'addSecrets'; +export const ACTION_DELETE_SECRETS = 'deleteSecrets'; +export const ACTION_UPDATE_SECRETS = 'updateSecrets'; +export const ACTION_READ_SECRETS = 'readSecrets'; \ No newline at end of file diff --git a/backend/src/variables/authentication.ts b/backend/src/variables/authentication.ts index 2fe1f4fc6..ac19d2756 100644 --- a/backend/src/variables/authentication.ts +++ b/backend/src/variables/authentication.ts @@ -1,11 +1,4 @@ -const AUTH_MODE_JWT = 'jwt'; -const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount'; -const AUTH_MODE_SERVICE_TOKEN = 'serviceToken'; -const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate - -export { - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_API_KEY -} \ No newline at end of file +export const AUTH_MODE_JWT = 'jwt'; +export const AUTH_MODE_SERVICE_ACCOUNT = 'serviceAccount'; +export const AUTH_MODE_SERVICE_TOKEN = 'serviceToken'; +export const AUTH_MODE_API_KEY = 'apiKey'; // TODO: deprecate \ No newline at end of file diff --git a/backend/src/variables/crypto.ts b/backend/src/variables/crypto.ts new file mode 100644 index 000000000..3b8c7820d --- /dev/null +++ b/backend/src/variables/crypto.ts @@ -0,0 +1,7 @@ +export const ALGORITHM_AES_256_GCM = 'aes-256-gcm'; +export const BLOCK_SIZE_BYTES_32 = 32; +export const BLOCK_SIZE_BYTES_16 = 16; + +export const ENCODING_SCHEME_UTF8 = 'utf8'; +export const ENCODING_SCHEME_HEX = 'hex'; +export const ENCODING_SCHEME_BASE64 = 'base64'; \ No newline at end of file diff --git a/backend/src/variables/environment.ts b/backend/src/variables/environment.ts index 44d7cdbb2..b068b3d36 100644 --- a/backend/src/variables/environment.ts +++ b/backend/src/variables/environment.ts @@ -1,14 +1,6 @@ // environments -const ENV_DEV = 'dev'; -const ENV_TESTING = 'test'; -const ENV_STAGING = 'staging'; -const ENV_PROD = 'prod'; -const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]); - -export { - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD, - ENV_SET -} \ No newline at end of file +export const ENV_DEV = 'dev'; +export const ENV_TESTING = 'test'; +export const ENV_STAGING = 'staging'; +export const ENV_PROD = 'prod'; +export const ENV_SET = new Set([ENV_DEV, ENV_TESTING, ENV_STAGING, ENV_PROD]); \ No newline at end of file diff --git a/backend/src/variables/event.ts b/backend/src/variables/event.ts index 4477e8e02..1c55eb3ec 100644 --- a/backend/src/variables/event.ts +++ b/backend/src/variables/event.ts @@ -1,7 +1,2 @@ -const EVENT_PUSH_SECRETS = 'pushSecrets'; -const EVENT_PULL_SECRETS = 'pullSecrets'; - -export { - EVENT_PUSH_SECRETS, - EVENT_PULL_SECRETS -} \ No newline at end of file +export const EVENT_PUSH_SECRETS = 'pushSecrets'; +export const EVENT_PULL_SECRETS = 'pullSecrets'; \ No newline at end of file diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index 979de21ab..5695bd547 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -1,152 +1,13 @@ -import { - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD, - ENV_SET, -} from "./environment"; -import { - INTEGRATION_AZURE_KEY_VAULT, - INTEGRATION_AWS_PARAMETER_STORE, - INTEGRATION_AWS_SECRET_MANAGER, - INTEGRATION_HEROKU, - INTEGRATION_VERCEL, - INTEGRATION_NETLIFY, - INTEGRATION_GITHUB, - INTEGRATION_GITLAB, - INTEGRATION_RENDER, - INTEGRATION_RAILWAY, - INTEGRATION_FLYIO, - INTEGRATION_CIRCLECI, - INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE, - INTEGRATION_SET, - INTEGRATION_OAUTH2, - INTEGRATION_AZURE_TOKEN_URL, - INTEGRATION_HEROKU_TOKEN_URL, - INTEGRATION_VERCEL_TOKEN_URL, - INTEGRATION_NETLIFY_TOKEN_URL, - INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITLAB_TOKEN_URL, - INTEGRATION_HEROKU_API_URL, - INTEGRATION_GITLAB_API_URL, - INTEGRATION_VERCEL_API_URL, - INTEGRATION_NETLIFY_API_URL, - INTEGRATION_RENDER_API_URL, - INTEGRATION_RAILWAY_API_URL, - INTEGRATION_FLYIO_API_URL, - INTEGRATION_CIRCLECI_API_URL, - INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_SUPABASE_API_URL, - getIntegrationOptions -} from "./integration"; -import { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED } from "./organization"; -import { SECRET_SHARED, SECRET_PERSONAL } from "./secret"; -import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from "./event"; -import { - ACTION_LOGIN, - ACTION_LOGOUT, - ACTION_ADD_SECRETS, - ACTION_UPDATE_SECRETS, - ACTION_DELETE_SECRETS, - ACTION_READ_SECRETS -} from './action'; -import { - SMTP_HOST_SENDGRID, - SMTP_HOST_MAILGUN, - SMTP_HOST_SOCKETLABS, - SMTP_HOST_ZOHOMAIL -} from './smtp'; -import { PLAN_STARTER, PLAN_PRO } from './stripe'; -import { - MFA_METHOD_EMAIL -} from './user'; -import { - TOKEN_EMAIL_CONFIRMATION, - TOKEN_EMAIL_MFA, - TOKEN_EMAIL_ORG_INVITATION, - TOKEN_EMAIL_PASSWORD_RESET -} from './token'; -import { - PERMISSION_READ_SECRETS, - PERMISSION_WRITE_SECRETS -} from './permission'; -import { - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_API_KEY -} from './authentication'; - -export { - OWNER, - ADMIN, - MEMBER, - INVITED, - ACCEPTED, - SECRET_SHARED, - SECRET_PERSONAL, - ENV_DEV, - ENV_TESTING, - ENV_STAGING, - ENV_PROD, - ENV_SET, - INTEGRATION_AZURE_KEY_VAULT, - INTEGRATION_AWS_PARAMETER_STORE, - INTEGRATION_AWS_SECRET_MANAGER, - INTEGRATION_HEROKU, - INTEGRATION_VERCEL, - INTEGRATION_NETLIFY, - INTEGRATION_GITHUB, - INTEGRATION_GITLAB, - INTEGRATION_RENDER, - INTEGRATION_RAILWAY, - INTEGRATION_FLYIO, - INTEGRATION_CIRCLECI, - INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE, - INTEGRATION_SET, - INTEGRATION_OAUTH2, - INTEGRATION_AZURE_TOKEN_URL, - INTEGRATION_HEROKU_TOKEN_URL, - INTEGRATION_VERCEL_TOKEN_URL, - INTEGRATION_NETLIFY_TOKEN_URL, - INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITLAB_TOKEN_URL, - INTEGRATION_HEROKU_API_URL, - INTEGRATION_GITLAB_API_URL, - INTEGRATION_VERCEL_API_URL, - INTEGRATION_NETLIFY_API_URL, - INTEGRATION_RENDER_API_URL, - INTEGRATION_RAILWAY_API_URL, - INTEGRATION_FLYIO_API_URL, - INTEGRATION_CIRCLECI_API_URL, - INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_SUPABASE_API_URL, - EVENT_PUSH_SECRETS, - EVENT_PULL_SECRETS, - ACTION_LOGIN, - ACTION_LOGOUT, - ACTION_ADD_SECRETS, - ACTION_UPDATE_SECRETS, - ACTION_DELETE_SECRETS, - ACTION_READ_SECRETS, - PERMISSION_READ_SECRETS, - PERMISSION_WRITE_SECRETS, - getIntegrationOptions, - SMTP_HOST_SENDGRID, - SMTP_HOST_MAILGUN, - SMTP_HOST_SOCKETLABS, - SMTP_HOST_ZOHOMAIL, - PLAN_STARTER, - PLAN_PRO, - MFA_METHOD_EMAIL, - TOKEN_EMAIL_CONFIRMATION, - TOKEN_EMAIL_MFA, - TOKEN_EMAIL_ORG_INVITATION, - TOKEN_EMAIL_PASSWORD_RESET, - AUTH_MODE_JWT, - AUTH_MODE_SERVICE_ACCOUNT, - AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_API_KEY -}; +export * from './action'; +export * from './authentication'; +export * from './crypto'; +export * from './environment'; +export * from './event'; +export * from './integration'; +export * from './organization'; +export * from './permission'; +export * from './secret'; +export * from './smtp'; +export * from './stripe'; +export * from './token'; +export * from './user'; \ No newline at end of file diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index aac78968d..6efd96817 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -8,21 +8,21 @@ import { } from '../config'; // integrations -const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; -const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store'; -const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager'; -const INTEGRATION_HEROKU = "heroku"; -const INTEGRATION_VERCEL = "vercel"; -const INTEGRATION_NETLIFY = "netlify"; -const INTEGRATION_GITHUB = "github"; -const INTEGRATION_GITLAB = "gitlab"; -const INTEGRATION_RENDER = "render"; -const INTEGRATION_RAILWAY = "railway"; -const INTEGRATION_FLYIO = "flyio"; -const INTEGRATION_CIRCLECI = "circleci"; -const INTEGRATION_TRAVISCI = "travisci"; -const INTEGRATION_SUPABASE = 'supabase'; -const INTEGRATION_SET = new Set([ +export const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; +export const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store'; +export const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager'; +export const INTEGRATION_HEROKU = "heroku"; +export const INTEGRATION_VERCEL = "vercel"; +export const INTEGRATION_NETLIFY = "netlify"; +export const INTEGRATION_GITHUB = "github"; +export const INTEGRATION_GITLAB = "gitlab"; +export const INTEGRATION_RENDER = "render"; +export const INTEGRATION_RAILWAY = "railway"; +export const INTEGRATION_FLYIO = "flyio"; +export const INTEGRATION_CIRCLECI = "circleci"; +export const INTEGRATION_TRAVISCI = "travisci"; +export const INTEGRATION_SUPABASE = 'supabase'; +export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, INTEGRATION_VERCEL, @@ -37,31 +37,31 @@ const INTEGRATION_SET = new Set([ ]); // integration types -const INTEGRATION_OAUTH2 = "oauth2"; +export const INTEGRATION_OAUTH2 = "oauth2"; // integration oauth endpoints -const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`; -const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token'; -const INTEGRATION_VERCEL_TOKEN_URL = +export const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`; +export const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token'; +export const INTEGRATION_VERCEL_TOKEN_URL = "https://api.vercel.com/v2/oauth/access_token"; -const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token"; -const INTEGRATION_GITHUB_TOKEN_URL = +export const INTEGRATION_NETLIFY_TOKEN_URL = "https://api.netlify.com/oauth/token"; +export const INTEGRATION_GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token"; -const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token"; +export const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token"; // integration apps endpoints -const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com"; -const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api"; -const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com"; -const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com"; -const INTEGRATION_RENDER_API_URL = "https://api.render.com"; -const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2"; -const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; -const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; -const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; -const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; +export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com"; +export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api"; +export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com"; +export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com"; +export const INTEGRATION_RENDER_API_URL = "https://api.render.com"; +export const INTEGRATION_RAILWAY_API_URL = "https://backboard.railway.app/graphql/v2"; +export const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql"; +export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; +export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; +export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; -const getIntegrationOptions = async () => { +export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ { name: 'Heroku', @@ -202,41 +202,4 @@ const getIntegrationOptions = async () => { ] return INTEGRATION_OPTIONS; -} - - -export { - INTEGRATION_AZURE_KEY_VAULT, - INTEGRATION_AWS_PARAMETER_STORE, - INTEGRATION_AWS_SECRET_MANAGER, - INTEGRATION_HEROKU, - INTEGRATION_VERCEL, - INTEGRATION_NETLIFY, - INTEGRATION_GITHUB, - INTEGRATION_GITLAB, - INTEGRATION_RENDER, - INTEGRATION_RAILWAY, - INTEGRATION_FLYIO, - INTEGRATION_CIRCLECI, - INTEGRATION_TRAVISCI, - INTEGRATION_SUPABASE, - INTEGRATION_SET, - INTEGRATION_OAUTH2, - INTEGRATION_AZURE_TOKEN_URL, - INTEGRATION_HEROKU_TOKEN_URL, - INTEGRATION_VERCEL_TOKEN_URL, - INTEGRATION_NETLIFY_TOKEN_URL, - INTEGRATION_GITHUB_TOKEN_URL, - INTEGRATION_GITLAB_API_URL, - INTEGRATION_HEROKU_API_URL, - INTEGRATION_GITLAB_TOKEN_URL, - INTEGRATION_VERCEL_API_URL, - INTEGRATION_NETLIFY_API_URL, - INTEGRATION_RENDER_API_URL, - INTEGRATION_RAILWAY_API_URL, - INTEGRATION_FLYIO_API_URL, - INTEGRATION_CIRCLECI_API_URL, - INTEGRATION_TRAVISCI_API_URL, - INTEGRATION_SUPABASE_API_URL, - getIntegrationOptions -}; +} \ No newline at end of file diff --git a/backend/src/variables/organization.ts b/backend/src/variables/organization.ts index 80c7102c1..4f5620236 100644 --- a/backend/src/variables/organization.ts +++ b/backend/src/variables/organization.ts @@ -1,12 +1,10 @@ // membership roles -const OWNER = "owner"; -const ADMIN = "admin"; -const MEMBER = "member"; +export const OWNER = "owner"; +export const ADMIN = "admin"; +export const MEMBER = "member"; // membership statuses -const INVITED = "invited"; +export const INVITED = "invited"; // -- organization -const ACCEPTED = "accepted"; - -export { OWNER, ADMIN, MEMBER, INVITED, ACCEPTED }; +export const ACCEPTED = "accepted"; \ No newline at end of file diff --git a/backend/src/variables/permission.ts b/backend/src/variables/permission.ts index 769344d7f..98c9ef538 100644 --- a/backend/src/variables/permission.ts +++ b/backend/src/variables/permission.ts @@ -1,7 +1,2 @@ -const PERMISSION_READ_SECRETS = 'read'; -const PERMISSION_WRITE_SECRETS = 'write'; - -export { - PERMISSION_READ_SECRETS, - PERMISSION_WRITE_SECRETS -} \ No newline at end of file +export const PERMISSION_READ_SECRETS = 'read'; +export const PERMISSION_WRITE_SECRETS = 'write'; \ No newline at end of file diff --git a/backend/src/variables/secret.ts b/backend/src/variables/secret.ts index 31cbcf951..571e66b9d 100644 --- a/backend/src/variables/secret.ts +++ b/backend/src/variables/secret.ts @@ -1,8 +1,3 @@ // secrets -const SECRET_SHARED = 'shared'; -const SECRET_PERSONAL = 'personal'; - -export { - SECRET_SHARED, - SECRET_PERSONAL -} \ No newline at end of file +export const SECRET_SHARED = 'shared'; +export const SECRET_PERSONAL = 'personal'; \ No newline at end of file diff --git a/backend/src/variables/smtp.ts b/backend/src/variables/smtp.ts index a88f229bd..5b4dbd191 100644 --- a/backend/src/variables/smtp.ts +++ b/backend/src/variables/smtp.ts @@ -1,11 +1,4 @@ -const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net'; -const SMTP_HOST_MAILGUN = 'smtp.mailgun.org'; -const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com'; -const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com'; - -export { - SMTP_HOST_SENDGRID, - SMTP_HOST_MAILGUN, - SMTP_HOST_SOCKETLABS, - SMTP_HOST_ZOHOMAIL -} \ No newline at end of file +export const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net'; +export const SMTP_HOST_MAILGUN = 'smtp.mailgun.org'; +export const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com'; +export const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com'; \ No newline at end of file diff --git a/backend/src/variables/stripe.ts b/backend/src/variables/stripe.ts index ecdbd98ae..7b6ae8fa1 100644 --- a/backend/src/variables/stripe.ts +++ b/backend/src/variables/stripe.ts @@ -1,7 +1,2 @@ -const PLAN_STARTER = 'starter'; -const PLAN_PRO = 'pro'; - -export { - PLAN_STARTER, - PLAN_PRO -} \ No newline at end of file +export const PLAN_STARTER = 'starter'; +export const PLAN_PRO = 'pro'; \ No newline at end of file diff --git a/backend/src/variables/token.ts b/backend/src/variables/token.ts index ecb63990f..2ced95d9c 100644 --- a/backend/src/variables/token.ts +++ b/backend/src/variables/token.ts @@ -1,11 +1,4 @@ -const TOKEN_EMAIL_CONFIRMATION = 'emailConfirmation'; -const TOKEN_EMAIL_MFA = 'emailMfa'; -const TOKEN_EMAIL_ORG_INVITATION = 'organizationInvitation'; -const TOKEN_EMAIL_PASSWORD_RESET = 'passwordReset'; - -export { - TOKEN_EMAIL_CONFIRMATION, - TOKEN_EMAIL_MFA, - TOKEN_EMAIL_ORG_INVITATION, - TOKEN_EMAIL_PASSWORD_RESET -} \ No newline at end of file +export const TOKEN_EMAIL_CONFIRMATION = 'emailConfirmation'; +export const TOKEN_EMAIL_MFA = 'emailMfa'; +export const TOKEN_EMAIL_ORG_INVITATION = 'organizationInvitation'; +export const TOKEN_EMAIL_PASSWORD_RESET = 'passwordReset'; \ No newline at end of file diff --git a/backend/src/variables/user.ts b/backend/src/variables/user.ts index baa27d35d..7e5b53c4b 100644 --- a/backend/src/variables/user.ts +++ b/backend/src/variables/user.ts @@ -1,5 +1 @@ -const MFA_METHOD_EMAIL = 'email'; - -export { - MFA_METHOD_EMAIL -} \ No newline at end of file +export const MFA_METHOD_EMAIL = 'email'; \ No newline at end of file diff --git a/backend/src/variables/workspace.ts b/backend/src/variables/workspace.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/backend/tests/helper/helper.ts b/backend/tests/helper/helper.ts index c59c8d43b..d72a101c1 100644 --- a/backend/tests/helper/helper.ts +++ b/backend/tests/helper/helper.ts @@ -10,7 +10,9 @@ const jsrp = require('jsrp'); // eslint-disable-next-line @typescript-eslint/no-var-requires const axios = require('axios'); import { plainTextWorkspaceKey, testWorkspaceId } from "../../src/utils/addDevelopmentUser"; -import { encryptSymmetric } from "../../src/utils/crypto"; +import { + encryptSymmetric128BitHexKeyUTF8 +} from '../../src/utils/crypto'; interface TokenData { token: string; @@ -64,7 +66,7 @@ export const getJWTFromTestUser = (): Promise => { export const getServiceTokenFromTestUser = async () => { const loggedInUserDetails = await getJWTFromTestUser() const randomBytes = crypto.randomBytes(16).toString('hex'); - const { ciphertext, iv, tag } = encryptSymmetric({ + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ plaintext: plainTextWorkspaceKey, key: randomBytes, });