Merge branch 'main' into feature/north-flank-app-connection

This commit is contained in:
Victor Santos
2025-10-23 12:46:55 -03:00
282 changed files with 7525 additions and 2865 deletions
+4 -3
View File
@@ -49,9 +49,6 @@ RUN rm -fr ${SOFTHSM2_SOURCES}
# Install pkcs11-tool # Install pkcs11-tool
RUN apt-get install -y opensc RUN apt-get install -y opensc
RUN mkdir -p /etc/softhsm2/tokens && \
softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000
# ? App setup # ? App setup
# Install Infisical CLI # Install Infisical CLI
@@ -64,10 +61,14 @@ WORKDIR /app
COPY package.json package.json COPY package.json package.json
COPY package-lock.json package-lock.json COPY package-lock.json package-lock.json
COPY dev-entrypoint.sh dev-entrypoint.sh
RUN chmod +x dev-entrypoint.sh
RUN npm install RUN npm install
COPY . . COPY . .
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENTRYPOINT ["/app/dev-entrypoint.sh"]
CMD ["npm", "run", "dev:docker"] CMD ["npm", "run", "dev:docker"]
+4 -3
View File
@@ -50,9 +50,6 @@ RUN rm -fr ${SOFTHSM2_SOURCES}
# Install pkcs11-tool # Install pkcs11-tool
RUN apt-get install -y opensc RUN apt-get install -y opensc
RUN mkdir -p /etc/softhsm2/tokens && \
softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000
WORKDIR /openssl-build WORKDIR /openssl-build
RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
&& tar -xf openssl-3.1.2.tar.gz \ && tar -xf openssl-3.1.2.tar.gz \
@@ -77,6 +74,9 @@ WORKDIR /app
COPY package.json package.json COPY package.json package.json
COPY package-lock.json package-lock.json COPY package-lock.json package-lock.json
COPY dev-entrypoint.sh dev-entrypoint.sh
RUN chmod +x dev-entrypoint.sh
RUN npm install RUN npm install
COPY . . COPY . .
@@ -87,4 +87,5 @@ ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
# ENV NODE_OPTIONS=--force-fips # Note(Daniel): We can't set this on the node options because it may break for existing folks using the infisical/infisical-fips image. Instead we call crypto.setFips(true) at runtime. # ENV NODE_OPTIONS=--force-fips # Note(Daniel): We can't set this on the node options because it may break for existing folks using the infisical/infisical-fips image. Instead we call crypto.setFips(true) at runtime.
ENV FIPS_ENABLED=true ENV FIPS_ENABLED=true
ENTRYPOINT ["/app/dev-entrypoint.sh"]
CMD ["npm", "run", "dev:docker"] CMD ["npm", "run", "dev:docker"]
+16
View File
@@ -0,0 +1,16 @@
#!/bin/sh
update-ca-certificates
# Initialize SoftHSM token if it doesn't exist
if [ ! -f /etc/softhsm2/tokens/auth-app.db ]; then
echo "Initializing SoftHSM token..."
mkdir -p /etc/softhsm2/tokens
softhsm2-util --init-token --slot 0 --label "auth-app" --pin 1234 --so-pin 0000
echo "SoftHSM token initialized"
else
echo "SoftHSM token already exists, skipping initialization"
fi
exec "$@"
@@ -146,7 +146,8 @@ describe("Service token secret ops", async () => {
let folderId = ""; let folderId = "";
beforeAll(async () => { beforeAll(async () => {
initLogger(); initLogger();
await initEnvConfig(testSuperAdminDAL, logger);
await initEnvConfig(testHsmService, testKmsRootConfigDAL, testSuperAdminDAL, logger);
serviceToken = await createServiceToken( serviceToken = await createServiceToken(
[{ secretPath: "/**", environment: seedData1.environment.slug }], [{ secretPath: "/**", environment: seedData1.environment.slug }],
+1 -1
View File
@@ -158,7 +158,7 @@ describe("Secret V3 Router", async () => {
let folderId = ""; let folderId = "";
beforeAll(async () => { beforeAll(async () => {
initLogger(); initLogger();
await initEnvConfig(testSuperAdminDAL, logger); await initEnvConfig(testHsmService, testKmsRootConfigDAL, testSuperAdminDAL, logger);
const projectKeyRes = await testServer.inject({ const projectKeyRes = await testServer.inject({
method: "GET", method: "GET",
+23 -6
View File
@@ -6,7 +6,7 @@ import { crypto } from "@app/lib/crypto/cryptography";
import path from "path"; import path from "path";
import { seedData1 } from "@app/db/seed-data"; import { seedData1 } from "@app/db/seed-data";
import { getDatabaseCredentials, initEnvConfig } from "@app/lib/config/env"; import { getDatabaseCredentials, getHsmConfig, initEnvConfig } from "@app/lib/config/env";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { main } from "@app/server/app"; import { main } from "@app/server/app";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
@@ -20,6 +20,8 @@ import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
import { buildRedisFromConfig } from "@app/lib/config/redis"; import { buildRedisFromConfig } from "@app/lib/config/redis";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { bootstrapCheck } from "@app/server/boot-strap-check"; import { bootstrapCheck } from "@app/server/boot-strap-check";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true }); dotenv.config({ path: path.join(__dirname, "../../.env.test"), debug: true });
export default { export default {
@@ -28,6 +30,7 @@ export default {
async setup() { async setup() {
const logger = initLogger(); const logger = initLogger();
const databaseCredentials = getDatabaseCredentials(logger); const databaseCredentials = getDatabaseCredentials(logger);
const hsmConfig = getHsmConfig(logger);
const db = initDbConnection({ const db = initDbConnection({
dbConnectionUri: databaseCredentials.dbConnectionUri, dbConnectionUri: databaseCredentials.dbConnectionUri,
@@ -35,7 +38,19 @@ export default {
}); });
const superAdminDAL = superAdminDALFactory(db); const superAdminDAL = superAdminDALFactory(db);
const envCfg = await initEnvConfig(superAdminDAL, logger); const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const hsmModule = initializeHsmModule(hsmConfig);
hsmModule.initialize();
const hsmService = hsmServiceFactory({
hsmModule: hsmModule.getModule(),
envConfig: hsmConfig
});
await hsmService.startService();
const envCfg = await initEnvConfig(hsmService, kmsRootConfigDAL, superAdminDAL, logger);
const redis = buildRedisFromConfig(envCfg); const redis = buildRedisFromConfig(envCfg);
await redis.flushdb("SYNC"); await redis.flushdb("SYNC");
@@ -68,16 +83,14 @@ export default {
await queue.initialize(); await queue.initialize();
const hsmModule = initializeHsmModule(envCfg);
hsmModule.initialize();
const server = await main({ const server = await main({
db, db,
smtp, smtp,
logger, logger,
queue, queue,
keyStore, keyStore,
hsmModule: hsmModule.getModule(), hsmService,
kmsRootConfigDAL,
superAdminDAL, superAdminDAL,
redis, redis,
envConfig: envCfg envConfig: envCfg
@@ -92,6 +105,10 @@ export default {
// @ts-expect-error type // @ts-expect-error type
globalThis.testSuperAdminDAL = superAdminDAL; globalThis.testSuperAdminDAL = superAdminDAL;
// @ts-expect-error type // @ts-expect-error type
globalThis.testKmsRootConfigDAL = kmsRootConfigDAL;
// @ts-expect-error type
globalThis.testHsmService = hsmService;
// @ts-expect-error type
globalThis.jwtAuthToken = crypto.jwt().sign( globalThis.jwtAuthToken = crypto.jwt().sign(
{ {
authTokenType: AuthTokenType.ACCESS_TOKEN, authTokenType: AuthTokenType.ACCESS_TOKEN,
+4
View File
@@ -1,7 +1,9 @@
import { FastifyInstance, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify"; import { FastifyInstance, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault } from "fastify";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { CustomLogger } from "@app/lib/logger/logger"; import { CustomLogger } from "@app/lib/logger/logger";
import { ZodTypeProvider } from "@app/server/plugins/fastify-zod"; import { ZodTypeProvider } from "@app/server/plugins/fastify-zod";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
declare global { declare global {
@@ -16,5 +18,7 @@ declare global {
// used only for testing // used only for testing
const testServer: FastifyZodProvider; const testServer: FastifyZodProvider;
const testSuperAdminDAL: TSuperAdminDALFactory; const testSuperAdminDAL: TSuperAdminDALFactory;
const testKmsRootConfigDAL: TKmsRootConfigDALFactory;
const testHsmService: THsmServiceFactory;
const jwtAuthToken: string; const jwtAuthToken: string;
} }
+4
View File
@@ -48,6 +48,7 @@ import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh
import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service"; import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service"; import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { TSubOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types"; import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity"; import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service"; import { TAdditionalPrivilegeServiceFactory } from "@app/services/additional-privilege/additional-privilege-service";
@@ -182,6 +183,8 @@ declare module "fastify" {
type: ActorType; type: ActorType;
id: string; id: string;
orgId: string; orgId: string;
parentOrgId: string;
rootOrgId: string;
}; };
rateLimits: RateLimitConfiguration; rateLimits: RateLimitConfiguration;
// passport data // passport data
@@ -335,6 +338,7 @@ declare module "fastify" {
additionalPrivilege: TAdditionalPrivilegeServiceFactory; additionalPrivilege: TAdditionalPrivilegeServiceFactory;
role: TRoleServiceFactory; role: TRoleServiceFactory;
convertor: TConvertorServiceFactory; convertor: TConvertorServiceFactory;
subOrganization: TSubOrgServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer
@@ -3,13 +3,14 @@ import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory"; import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas"; import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
@@ -25,10 +26,12 @@ export async function up(knex: Knex): Promise<void> {
if (hasUrl) t.string("url").nullable().alter(); if (hasUrl) t.string("url").nullable().alter();
}); });
} }
initLogger(); initLogger();
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -4,13 +4,14 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas"; import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
@@ -30,8 +31,12 @@ export async function up(knex: Knex): Promise<void> {
} }
initLogger(); initLogger();
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -4,13 +4,14 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas"; import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
@@ -24,8 +25,11 @@ export async function up(knex: Knex): Promise<void> {
} }
initLogger(); initLogger();
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -4,13 +4,14 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
const reencryptIdentityK8sAuth = async (knex: Knex) => { const reencryptIdentityK8sAuth = async (knex: Knex) => {
@@ -55,9 +56,11 @@ const reencryptIdentityK8sAuth = async (knex: Knex) => {
} }
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex); const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const superAdminDAL = superAdminDALFactory(knex);
const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer = const orgEncryptionRingBuffer =
@@ -4,13 +4,14 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas"; import { SecretKeyEncoding, TableName, TOrgBots } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
const reencryptIdentityOidcAuth = async (knex: Knex) => { const reencryptIdentityOidcAuth = async (knex: Knex) => {
@@ -35,8 +36,11 @@ const reencryptIdentityOidcAuth = async (knex: Knex) => {
} }
initLogger(); initLogger();
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -4,16 +4,18 @@ import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas"; import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
const reencryptSamlConfig = async (knex: Knex) => { const reencryptSamlConfig = async (knex: Knex, kmsService: TKmsServiceFactory) => {
const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint"); const hasEncryptedEntrypointColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlEntryPoint");
const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer"); const hasEncryptedIssuerColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlIssuer");
const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate"); const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.SamlConfig, "encryptedSamlCertificate");
@@ -28,10 +30,6 @@ const reencryptSamlConfig = async (knex: Knex) => {
} }
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer = const orgEncryptionRingBuffer =
createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25); createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25);
@@ -159,7 +157,7 @@ const reencryptSamlConfig = async (knex: Knex) => {
} }
}; };
const reencryptLdapConfig = async (knex: Knex) => { const reencryptLdapConfig = async (knex: Knex, kmsService: TKmsServiceFactory) => {
const hasEncryptedLdapBindDNColum = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN"); const hasEncryptedLdapBindDNColum = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindDN");
const hasEncryptedLdapBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass"); const hasEncryptedLdapBindPassColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapBindPass");
const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate"); const hasEncryptedCertificateColumn = await knex.schema.hasColumn(TableName.LdapConfig, "encryptedLdapCaCertificate");
@@ -194,10 +192,6 @@ const reencryptLdapConfig = async (knex: Knex) => {
} }
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer = const orgEncryptionRingBuffer =
createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25); createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25);
@@ -323,7 +317,7 @@ const reencryptLdapConfig = async (knex: Knex) => {
} }
}; };
const reencryptOidcConfig = async (knex: Knex) => { const reencryptOidcConfig = async (knex: Knex, kmsService: TKmsServiceFactory) => {
const hasEncryptedOidcClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId"); const hasEncryptedOidcClientIdColumn = await knex.schema.hasColumn(TableName.OidcConfig, "encryptedOidcClientId");
const hasEncryptedOidcClientSecretColumn = await knex.schema.hasColumn( const hasEncryptedOidcClientSecretColumn = await knex.schema.hasColumn(
TableName.OidcConfig, TableName.OidcConfig,
@@ -354,10 +348,6 @@ const reencryptOidcConfig = async (knex: Knex) => {
} }
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const orgEncryptionRingBuffer = const orgEncryptionRingBuffer =
createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25); createCircularCache<Awaited<ReturnType<(typeof kmsService)["createCipherPairWithDataKey"]>>>(25);
@@ -462,9 +452,18 @@ const reencryptOidcConfig = async (knex: Knex) => {
}; };
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
await reencryptSamlConfig(knex); initLogger();
await reencryptLdapConfig(knex);
await reencryptOidcConfig(knex); const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex);
const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
await reencryptSamlConfig(knex, kmsService);
await reencryptLdapConfig(knex, kmsService);
await reencryptOidcConfig(knex, kmsService);
} }
const dropSamlConfigColumns = async (knex: Knex) => { const dropSamlConfigColumns = async (knex: Knex) => {
@@ -3,12 +3,13 @@ import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory"; import { inMemoryKeyStore } from "@app/keystore/memory";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger"; import { initLogger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { TableName } from "../schemas"; import { TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
// Note(daniel): We aren't dropping tables or columns in this migrations so we can easily rollback if needed. // Note(daniel): We aren't dropping tables or columns in this migrations so we can easily rollback if needed.
// In the future we need to drop the projectGatewayId on the dynamic secrets table, and drop the project_gateways table entirely. // In the future we need to drop the projectGatewayId on the dynamic secrets table, and drop the project_gateways table entirely.
@@ -40,8 +41,10 @@ export async function up(knex: Knex): Promise<void> {
); );
initLogger(); initLogger();
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -2,19 +2,23 @@ import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory"; import { inMemoryKeyStore } from "@app/keystore/memory";
import { selectAllTableCols } from "@app/lib/knex"; import { selectAllTableCols } from "@app/lib/knex";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { TableName } from "../schemas"; import { TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
export async function up(knex: Knex) { export async function up(knex: Knex) {
const existingSuperAdminsWithGithubConnection = await knex(TableName.SuperAdmin) const existingSuperAdminsWithGithubConnection = await knex(TableName.SuperAdmin)
.select(selectAllTableCols(TableName.SuperAdmin)) .select(selectAllTableCols(TableName.SuperAdmin))
.whereNotNull(`${TableName.SuperAdmin}.encryptedGitHubAppConnectionClientId`); .whereNotNull(`${TableName.SuperAdmin}.encryptedGitHubAppConnectionClientId`);
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -2,13 +2,14 @@ import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory"; import { inMemoryKeyStore } from "@app/keystore/memory";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { SecretKeyEncoding, TableName } from "../schemas"; import { SecretKeyEncoding, TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config"; import { getMigrationEnvConfig, getMigrationHsmConfig } from "./utils/env-config";
import { createCircularCache } from "./utils/ring-buffer"; import { createCircularCache } from "./utils/ring-buffer";
import { getMigrationEncryptionServices } from "./utils/services"; import { getMigrationEncryptionServices, getMigrationHsmService } from "./utils/services";
const BATCH_SIZE = 500; const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
@@ -25,8 +26,10 @@ export async function up(knex: Knex): Promise<void> {
}); });
if (!hasEncryptedCredentials) { if (!hasEncryptedCredentials) {
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -131,8 +134,11 @@ export async function down(knex: Knex): Promise<void> {
const hasEncryptedCredentials = await knex.schema.hasColumn(TableName.AuditLogStream, "encryptedCredentials"); const hasEncryptedCredentials = await knex.schema.hasColumn(TableName.AuditLogStream, "encryptedCredentials");
if (hasEncryptedCredentials) { if (hasEncryptedCredentials) {
const { hsmService } = await getMigrationHsmService({ envConfig: getMigrationHsmConfig() });
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const envConfig = await getMigrationEnvConfig(superAdminDAL, hsmService, kmsRootConfigDAL);
const keyStore = inMemoryKeyStore(); const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex }); const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
@@ -0,0 +1,49 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.PamAccount, "rotationEnabled"))) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.boolean("rotationEnabled").notNullable().defaultTo(false);
});
}
if (!(await knex.schema.hasColumn(TableName.PamAccount, "rotationIntervalSeconds"))) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.integer("rotationIntervalSeconds").nullable();
});
}
if (!(await knex.schema.hasColumn(TableName.PamAccount, "lastRotatedAt"))) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.timestamp("lastRotatedAt").nullable();
});
}
if (!(await knex.schema.hasColumn(TableName.PamResource, "encryptedRotationAccountCredentials"))) {
await knex.schema.alterTable(TableName.PamResource, (t) => {
t.binary("encryptedRotationAccountCredentials").nullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.PamResource, "encryptedRotationAccountCredentials")) {
await knex.schema.alterTable(TableName.PamResource, (t) => {
t.dropColumn("encryptedRotationAccountCredentials");
});
}
if (await knex.schema.hasColumn(TableName.PamAccount, "rotationEnabled")) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.dropColumn("rotationEnabled");
});
}
if (await knex.schema.hasColumn(TableName.PamAccount, "rotationIntervalSeconds")) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.dropColumn("rotationIntervalSeconds");
});
}
if (await knex.schema.hasColumn(TableName.PamAccount, "lastRotatedAt")) {
await knex.schema.alterTable(TableName.PamAccount, (t) => {
t.dropColumn("lastRotatedAt");
});
}
}
@@ -0,0 +1,68 @@
import { Knex } from "knex";
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
import { AccessScope, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasParentOrgId = await knex.schema.hasColumn(TableName.Organization, "parentOrgId");
if (!hasParentOrgId) {
await knex.schema.alterTable(TableName.Organization, async (t) => {
// the one just above the chain
t.uuid("parentOrgId");
t.foreign("parentOrgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
// this would root organization containing various informations like billing etc
t.uuid("rootOrgId");
t.foreign("rootOrgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
await dropConstraintIfExists(TableName.Organization, "organizations_slug_unique", knex);
t.unique(["rootOrgId", "parentOrgId", "slug"]);
});
// had to switch to raw for null not distinct
}
const hasIdentityOrgCol = await knex.schema.hasColumn(TableName.Identity, "orgId");
if (!hasIdentityOrgCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.uuid("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
await knex.raw(
`
UPDATE ?? AS identity
SET "orgId" = membership."scopeOrgId"
FROM ?? AS membership
WHERE
membership."actorIdentityId" = identity."id"
AND membership."scope" = ?
`,
[TableName.Identity, TableName.Membership, AccessScope.Organization]
);
await knex.raw(`DELETE FROM ?? WHERE "orgId" IS NULL`, [TableName.Identity]);
await knex.schema.alterTable(TableName.Identity, (t) => {
t.uuid("orgId").notNullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasParentOrgId = await knex.schema.hasColumn(TableName.Organization, "parentOrgId");
const hasRootOrgId = await knex.schema.hasColumn(TableName.Organization, "rootOrgId");
if (hasParentOrgId || hasRootOrgId) {
await knex.schema.alterTable(TableName.Organization, (t) => {
if (hasParentOrgId) t.dropColumn("parentOrgId");
if (hasRootOrgId) t.dropColumn("rootOrgId");
});
}
const hasIdentityOrgCol = await knex.schema.hasColumn(TableName.Identity, "orgId");
if (hasIdentityOrgCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.dropColumn("orgId");
});
}
}
+34 -7
View File
@@ -1,7 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { removeTrailingSlash } from "@app/lib/fn";
import { zpStr } from "@app/lib/zod"; import { zpStr } from "@app/lib/zod";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
const envSchema = z const envSchema = z
@@ -22,13 +25,17 @@ const envSchema = z
HSM_LIB_PATH: zpStr(z.string().optional()), HSM_LIB_PATH: zpStr(z.string().optional()),
HSM_PIN: zpStr(z.string().optional()), HSM_PIN: zpStr(z.string().optional()),
HSM_KEY_LABEL: zpStr(z.string().optional()), HSM_KEY_LABEL: zpStr(z.string().optional()),
HSM_SLOT: z.coerce.number().optional().default(0) HSM_SLOT: z.coerce.number().optional().default(0),
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
LICENSE_KEY: zpStr(z.string().optional()),
LICENSE_KEY_OFFLINE: zpStr(z.string().optional()),
INTERNAL_REGION: zpStr(z.enum(["us", "eu"]).optional()),
SITE_URL: zpStr(z.string().transform((val) => (val ? removeTrailingSlash(val) : val))).optional()
}) })
// To ensure that basic encryption is always possible. // 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) => ({ .transform((data) => ({
...data, ...data,
isHsmConfigured: isHsmConfigured:
@@ -37,7 +44,27 @@ const envSchema = z
export type TMigrationEnvConfig = z.infer<typeof envSchema>; export type TMigrationEnvConfig = z.infer<typeof envSchema>;
export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory) => { export const getMigrationHsmConfig = () => {
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
console.error("Invalid environment variables. Check the error below");
console.error(parsedEnv.error.issues);
process.exit(-1);
}
return {
isHsmConfigured: parsedEnv.data.isHsmConfigured,
HSM_PIN: parsedEnv.data.HSM_PIN,
HSM_SLOT: parsedEnv.data.HSM_SLOT,
HSM_LIB_PATH: parsedEnv.data.HSM_LIB_PATH,
HSM_KEY_LABEL: parsedEnv.data.HSM_KEY_LABEL
};
};
export const getMigrationEnvConfig = async (
superAdminDAL: TSuperAdminDALFactory,
hsmService: THsmServiceFactory,
kmsRootConfigDAL: TKmsRootConfigDALFactory
) => {
const parsedEnv = envSchema.safeParse(process.env); const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) { if (!parsedEnv.success) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@@ -53,7 +80,7 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory
let envCfg = Object.freeze(parsedEnv.data); let envCfg = Object.freeze(parsedEnv.data);
const fipsEnabled = await crypto.initialize(superAdminDAL, envCfg); const fipsEnabled = await crypto.initialize(superAdminDAL, hsmService, kmsRootConfigDAL, envCfg);
// Fix for 128-bit entropy encryption key expansion issue: // Fix for 128-bit entropy encryption key expansion issue:
// In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY. // In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY.
+67 -84
View File
@@ -1,28 +1,23 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns"; import { initializeHsmModule, isHsmActiveAndEnabled } from "@app/ee/services/hsm/hsm-fns";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { licenseDALFactory } from "@app/ee/services/license/license-dal";
import { licenseServiceFactory } from "@app/ee/services/license/license-service";
import { permissionDALFactory } from "@app/ee/services/permission/permission-dal";
import { permissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { folderCheckpointDALFactory } from "@app/services/folder-checkpoint/folder-checkpoint-dal"; import { BadRequestError } from "@app/lib/errors";
import { folderCheckpointResourcesDALFactory } from "@app/services/folder-checkpoint-resources/folder-checkpoint-resources-dal";
import { folderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
import { folderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service";
import { folderCommitChangesDALFactory } from "@app/services/folder-commit-changes/folder-commit-changes-dal";
import { folderTreeCheckpointDALFactory } from "@app/services/folder-tree-checkpoint/folder-tree-checkpoint-dal";
import { folderTreeCheckpointResourcesDALFactory } from "@app/services/folder-tree-checkpoint-resources/folder-tree-checkpoint-resources-dal";
import { identityDALFactory } from "@app/services/identity/identity-dal"; import { identityDALFactory } from "@app/services/identity/identity-dal";
import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { orgDALFactory } from "@app/services/org/org-dal"; import { orgDALFactory } from "@app/services/org/org-dal";
import { projectDALFactory } from "@app/services/project/project-dal"; import { projectDALFactory } from "@app/services/project/project-dal";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { roleDALFactory } from "@app/services/role/role-dal";
import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal";
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
import { secretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
import { userDALFactory } from "@app/services/user/user-dal"; import { userDALFactory } from "@app/services/user/user-dal";
import { TMigrationEnvConfig } from "./env-config"; import { TMigrationEnvConfig } from "./env-config";
@@ -33,8 +28,11 @@ type TDependencies = {
keyStore: TKeyStoreFactory; keyStore: TKeyStoreFactory;
}; };
export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => { type THsmServiceDependencies = {
// eslint-disable-next-line no-param-reassign envConfig: Pick<TMigrationEnvConfig, "HSM_PIN" | "HSM_SLOT" | "HSM_LIB_PATH" | "HSM_KEY_LABEL" | "isHsmConfigured">;
};
export const getMigrationHsmService = async ({ envConfig }: THsmServiceDependencies) => {
const hsmModule = initializeHsmModule(envConfig); const hsmModule = initializeHsmModule(envConfig);
hsmModule.initialize(); hsmModule.initialize();
@@ -43,67 +41,72 @@ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }
envConfig 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 hsmService.startService();
await kmsService.startService();
return { kmsService }; return { hsmService };
}; };
export const getMigrationPITServices = async ({ export const getMigrationEncryptionServices = async ({ envConfig, db, keyStore }: TDependencies) => {
db, // ----- DAL dependencies -----
keyStore, const orgDAL = orgDALFactory(db);
envConfig const licenseDAL = licenseDALFactory(db);
}: { const permissionDAL = permissionDALFactory(db);
db: Knex;
keyStore: TKeyStoreFactory;
envConfig: TMigrationEnvConfig;
}) => {
const projectDAL = projectDALFactory(db); const projectDAL = projectDALFactory(db);
const folderCommitDAL = folderCommitDALFactory(db); const roleDAL = roleDALFactory(db);
const folderCommitChangesDAL = folderCommitChangesDALFactory(db);
const folderCheckpointDAL = folderCheckpointDALFactory(db);
const folderTreeCheckpointDAL = folderTreeCheckpointDALFactory(db);
const userDAL = userDALFactory(db); const userDAL = userDALFactory(db);
const identityDAL = identityDALFactory(db); const identityDAL = identityDALFactory(db);
const folderDAL = secretFolderDALFactory(db); const serviceTokenDAL = serviceTokenDALFactory(db);
const folderVersionDAL = secretFolderVersionDALFactory(db);
const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db);
const folderCheckpointResourcesDAL = folderCheckpointResourcesDALFactory(db);
const secretV2BridgeDAL = secretV2BridgeDALFactory({ db, keyStore });
const folderTreeCheckpointResourcesDAL = folderTreeCheckpointResourcesDALFactory(db);
const secretTagDAL = secretTagDALFactory(db);
const orgDAL = orgDALFactory(db);
const kmsRootConfigDAL = kmsRootConfigDALFactory(db); const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const kmsDAL = kmskeyDALFactory(db); const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db); const internalKmsDAL = internalKmsDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const hsmModule = initializeHsmModule(envConfig); // ----- Service dependencies -----
hsmModule.initialize(); const permissionService = permissionServiceFactory({
permissionDAL,
serviceTokenDAL,
projectDAL,
keyStore,
roleDAL,
userDAL,
identityDAL
});
const hsmService = hsmServiceFactory({ const licenseService = licenseServiceFactory({
hsmModule: hsmModule.getModule(), permissionService,
orgDAL,
licenseDAL,
keyStore,
projectDAL,
envConfig envConfig
}); });
// ----- HSM startup -----
const { hsmService } = await getMigrationHsmService({ envConfig });
const hsmStatus = await isHsmActiveAndEnabled({
hsmService,
kmsRootConfigDAL,
licenseService
});
// if the encryption strategy is software - user needs to provide an encryption key
// if the encryption strategy is null AND the hsm is not configured - user needs to provide an encryption key
const needsEncryptionKey =
hsmStatus.rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.Software ||
(hsmStatus.rootKmsConfigEncryptionStrategy === null && !hsmStatus.isHsmConfigured);
if (needsEncryptionKey) {
if (!envConfig.ROOT_ENCRYPTION_KEY && !envConfig.ENCRYPTION_KEY) {
throw new BadRequestError({
message:
"Root KMS encryption strategy is set to software. Please set the ENCRYPTION_KEY environment variable and restart your deployment.\nYou can enable HSM encryption in the Server Console."
});
}
}
// ----- KMS startup -----
const kmsService = kmsServiceFactory({ const kmsService = kmsServiceFactory({
kmsRootConfigDAL, kmsRootConfigDAL,
keyStore, keyStore,
@@ -115,27 +118,7 @@ export const getMigrationPITServices = async ({
envConfig envConfig
}); });
await hsmService.startService(); await kmsService.startService(hsmStatus);
await kmsService.startService();
const folderCommitService = folderCommitServiceFactory({ return { kmsService, hsmService };
folderCommitDAL,
folderCommitChangesDAL,
folderCheckpointDAL,
folderTreeCheckpointDAL,
userDAL,
identityDAL,
folderDAL,
folderVersionDAL,
secretVersionV2BridgeDAL,
projectDAL,
folderCheckpointResourcesDAL,
secretV2BridgeDAL,
folderTreeCheckpointResourcesDAL,
kmsService,
secretTagDAL,
resourceMetadataDAL
});
return { folderCommitService };
}; };
+2 -1
View File
@@ -13,7 +13,8 @@ export const IdentitiesSchema = z.object({
authMethod: z.string().nullable().optional(), authMethod: z.string().nullable().optional(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
hasDeleteProtection: z.boolean().default(false) hasDeleteProtection: z.boolean().default(false),
orgId: z.string().uuid()
}); });
export type TIdentities = z.infer<typeof IdentitiesSchema>; export type TIdentities = z.infer<typeof IdentitiesSchema>;
+6
View File
@@ -316,6 +316,12 @@ export enum ActionProjectType {
Any = "any" Any = "any"
} }
export enum OrganizationActionScope {
ChildOrganization = "child-organization-only",
ParentOrganization = "parent-organization-only",
Any = "any"
}
export enum TemporaryPermissionMode { export enum TemporaryPermissionMode {
Relative = "relative" Relative = "relative"
} }
+3 -1
View File
@@ -38,7 +38,9 @@ export const OrganizationsSchema = z.object({
maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(), maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(),
maxSharedSecretViewLimit: z.number().nullable().optional(), maxSharedSecretViewLimit: z.number().nullable().optional(),
googleSsoAuthEnforced: z.boolean().default(false), googleSsoAuthEnforced: z.boolean().default(false),
googleSsoAuthLastUsed: z.date().nullable().optional() googleSsoAuthLastUsed: z.date().nullable().optional(),
parentOrgId: z.string().uuid().nullable().optional(),
rootOrgId: z.string().uuid().nullable().optional()
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
+4 -1
View File
@@ -18,7 +18,10 @@ export const PamAccountsSchema = z.object({
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
encryptedCredentials: zodBuffer, encryptedCredentials: zodBuffer,
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
rotationEnabled: z.boolean().default(false),
rotationIntervalSeconds: z.number().nullable().optional(),
lastRotatedAt: z.date().nullable().optional()
}); });
export type TPamAccounts = z.infer<typeof PamAccountsSchema>; export type TPamAccounts = z.infer<typeof PamAccountsSchema>;
+2 -1
View File
@@ -17,7 +17,8 @@ export const PamResourcesSchema = z.object({
resourceType: z.string(), resourceType: z.string(),
encryptedConnectionDetails: zodBuffer, encryptedConnectionDetails: zodBuffer,
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
encryptedRotationAccountCredentials: zodBuffer.nullable().optional()
}); });
export type TPamResources = z.infer<typeof PamResourcesSchema>; export type TPamResources = z.infer<typeof PamResourcesSchema>;
+19 -2
View File
@@ -1,7 +1,10 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { initEnvConfig } from "@app/lib/config/env"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { getHsmConfig, initEnvConfig } from "@app/lib/config/env";
import { initLogger, logger } from "@app/lib/logger"; import { initLogger, logger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { AuthMethod } from "../../services/auth/auth-type"; import { AuthMethod } from "../../services/auth/auth-type";
@@ -17,7 +20,21 @@ export async function seed(knex: Knex): Promise<void> {
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
await initEnvConfig(superAdminDAL, logger); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const hsmConfig = getHsmConfig(logger);
const hsmModule = initializeHsmModule(hsmConfig);
hsmModule.initialize();
const hsmService = hsmServiceFactory({
hsmModule: hsmModule.getModule(),
envConfig: hsmConfig
});
await hsmService.startService();
await initEnvConfig(hsmService, kmsRootConfigDAL, superAdminDAL, logger);
await knex(TableName.SuperAdmin).insert([ await knex(TableName.SuperAdmin).insert([
// eslint-disable-next-line // eslint-disable-next-line
+19 -2
View File
@@ -1,11 +1,14 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { initEnvConfig } from "@app/lib/config/env"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { getHsmConfig, initEnvConfig } from "@app/lib/config/env";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { initLogger, logger } from "@app/lib/logger"; import { initLogger, logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { AuthMethod } from "@app/services/auth/auth-type"; import { AuthMethod } from "@app/services/auth/auth-type";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal"; import { membershipUserDALFactory } from "@app/services/membership-user/membership-user-dal";
import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns"; import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns";
@@ -192,7 +195,21 @@ export async function seed(knex: Knex): Promise<void> {
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
await initEnvConfig(superAdminDAL, logger); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const hsmConfig = getHsmConfig(logger);
const hsmModule = initializeHsmModule(hsmConfig);
hsmModule.initialize();
const hsmService = hsmServiceFactory({
hsmModule: hsmModule.getModule(),
envConfig: hsmConfig
});
await hsmService.startService();
await initEnvConfig(hsmService, kmsRootConfigDAL, superAdminDAL, logger);
const [project] = await knex(TableName.Project) const [project] = await knex(TableName.Project)
.insert({ .insert({
+20 -3
View File
@@ -1,8 +1,11 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { initEnvConfig } from "@app/lib/config/env"; import { initializeHsmModule } from "@app/ee/services/hsm/hsm-fns";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { getHsmConfig, initEnvConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { initLogger, logger } from "@app/lib/logger"; import { initLogger, logger } from "@app/lib/logger";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { AccessScope, IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas"; import { AccessScope, IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
@@ -15,7 +18,20 @@ export async function seed(knex: Knex): Promise<void> {
initLogger(); initLogger();
const superAdminDAL = superAdminDALFactory(knex); const superAdminDAL = superAdminDALFactory(knex);
await initEnvConfig(superAdminDAL, logger); const kmsRootConfigDAL = kmsRootConfigDALFactory(knex);
const hsmConfig = getHsmConfig(logger);
const hsmModule = initializeHsmModule(hsmConfig);
hsmModule.initialize();
const hsmService = hsmServiceFactory({
hsmModule: hsmModule.getModule(),
envConfig: hsmConfig
});
await hsmService.startService();
await initEnvConfig(hsmService, kmsRootConfigDAL, superAdminDAL, logger);
// Inserts seed entries // Inserts seed entries
await knex(TableName.Identity).insert([ await knex(TableName.Identity).insert([
@@ -24,7 +40,8 @@ export async function seed(knex: Knex): Promise<void> {
// @ts-ignore // @ts-ignore
id: seedData1.machineIdentity.id, id: seedData1.machineIdentity.id,
name: seedData1.machineIdentity.name, name: seedData1.machineIdentity.name,
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH authMethod: IdentityAuthMethod.UNIVERSAL_AUTH,
orgId: seedData1.organization.id
} }
]); ]);
const identityUa = await knex(TableName.IdentityUniversalAuth) const identityUa = await knex(TableName.IdentityUniversalAuth)
+2
View File
@@ -48,12 +48,14 @@ import { registerSshCertRouter } from "./ssh-certificate-router";
import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router"; import { registerSshCertificateTemplateRouter } from "./ssh-certificate-template-router";
import { registerSshHostGroupRouter } from "./ssh-host-group-router"; import { registerSshHostGroupRouter } from "./ssh-host-group-router";
import { registerSshHostRouter } from "./ssh-host-router"; import { registerSshHostRouter } from "./ssh-host-router";
import { registerSubOrgRouter } from "./sub-org-router";
import { registerTrustedIpRouter } from "./trusted-ip-router"; import { registerTrustedIpRouter } from "./trusted-ip-router";
import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router"; import { registerUserAdditionalPrivilegeRouter } from "./user-additional-privilege-router";
export const registerV1EERoutes = async (server: FastifyZodProvider) => { export const registerV1EERoutes = async (server: FastifyZodProvider) => {
// org role starts with organization // org role starts with organization
await server.register(registerOrgRoleRouter, { prefix: "/organization" }); await server.register(registerOrgRoleRouter, { prefix: "/organization" });
await server.register(registerSubOrgRouter, { prefix: "/sub-organizations" });
await server.register(registerLicenseRouter, { prefix: "/organizations" }); await server.register(registerLicenseRouter, { prefix: "/organizations" });
// depreciated in favour of infisical workspace // depreciated in favour of infisical workspace
+1 -1
View File
@@ -58,7 +58,7 @@ export const registerLicenseRouter = async (server: FastifyZodProvider) => {
const plan = await server.services.license.getOrgPlan({ const plan = await server.services.license.getOrgPlan({
actorId: req.permission.id, actorId: req.permission.id,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId, actorOrgId: req.permission.rootOrgId,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
orgId: req.params.organizationId, orgId: req.params.organizationId,
refreshCache: req.query.refreshCache refreshCache: req.query.refreshCache
+34 -1
View File
@@ -3,12 +3,35 @@ import { z } from "zod";
import { AccessScope, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas"; import { AccessScope, OrgMembershipRole, OrgRolesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { OrgPermissionSchema } from "@app/ee/services/permission/org-permission"; import { OrgPermissionSchema, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { BadRequestError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas"; import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
const INVALID_SUBORG_PERMISSIONS = [
OrgPermissionSubjects.Sso,
OrgPermissionSubjects.Ldap,
OrgPermissionSubjects.Scim,
OrgPermissionSubjects.GithubOrgSync,
OrgPermissionSubjects.GithubOrgSyncManual,
OrgPermissionSubjects.Billing,
OrgPermissionSubjects.SubOrganization
];
const validateSubOrganizationSubjects = (permissions: unknown) => {
const invalidPermissionSubjects = (permissions as { subject: OrgPermissionSubjects }[])
.filter((el) => INVALID_SUBORG_PERMISSIONS.includes(el.subject))
.map((el) => el.subject);
if (invalidPermissionSubjects.length) {
const deduplication = Array.from(new Set(invalidPermissionSubjects));
throw new BadRequestError({
message: `Suborganization contains invalid permission subjects: ${deduplication.join(",")}`
});
}
};
export const registerOrgRoleRouter = async (server: FastifyZodProvider) => { export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
method: "POST", method: "POST",
@@ -37,6 +60,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const isSubOrganization = req.permission.rootOrgId !== req.permission.orgId;
if (isSubOrganization) {
validateSubOrganizationSubjects(req.body.permissions);
}
const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions)); const stringifiedPermissions = JSON.stringify(packRules(req.body.permissions));
const role = await server.services.role.createRole({ const role = await server.services.role.createRole({
permission: req.permission, permission: req.permission,
@@ -133,6 +161,11 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const isSubOrganization = req.permission.rootOrgId !== req.permission.orgId;
if (isSubOrganization && req.body.permissions) {
validateSubOrganizationSubjects(req.body.permissions);
}
const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined; const stringifiedPermissions = req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined;
const role = await server.services.role.updateRole({ const role = await server.services.role.updateRole({
permission: req.permission, permission: req.permission,
@@ -22,11 +22,15 @@ export const registerPamResourceEndpoints = <C extends TPamAccount>({
folderId?: C["folderId"]; folderId?: C["folderId"];
name: C["name"]; name: C["name"];
description?: C["description"]; description?: C["description"];
rotationEnabled: C["rotationEnabled"];
rotationIntervalSeconds?: C["rotationIntervalSeconds"];
}>; }>;
updateAccountSchema: z.ZodType<{ updateAccountSchema: z.ZodType<{
credentials?: C["credentials"]; credentials?: C["credentials"];
name?: C["name"]; name?: C["name"];
description?: C["description"]; description?: C["description"];
rotationEnabled?: C["rotationEnabled"];
rotationIntervalSeconds?: C["rotationIntervalSeconds"];
}>; }>;
accountResponseSchema: z.ZodTypeAny; accountResponseSchema: z.ZodTypeAny;
}) => { }) => {
@@ -60,7 +64,9 @@ export const registerPamResourceEndpoints = <C extends TPamAccount>({
resourceType, resourceType,
folderId: req.body.folderId, folderId: req.body.folderId,
name: req.body.name, name: req.body.name,
description: req.body.description description: req.body.description,
rotationEnabled: req.body.rotationEnabled,
rotationIntervalSeconds: req.body.rotationIntervalSeconds
} }
} }
}); });
@@ -108,7 +114,9 @@ export const registerPamResourceEndpoints = <C extends TPamAccount>({
resourceId: account.resourceId, resourceId: account.resourceId,
resourceType, resourceType,
name: req.body.name, name: req.body.name,
description: req.body.description description: req.body.description,
rotationEnabled: req.body.rotationEnabled,
rotationIntervalSeconds: req.body.rotationIntervalSeconds
} }
} }
}); });
@@ -1,7 +1,7 @@
import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums"; import { PamResource } from "@app/ee/services/pam-resource/pam-resource-enums";
import { import {
CreatePostgresResourceSchema, CreatePostgresResourceSchema,
PostgresResourceSchema, SanitizedPostgresResourceSchema,
UpdatePostgresResourceSchema UpdatePostgresResourceSchema
} from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
@@ -12,7 +12,7 @@ export const PAM_RESOURCE_REGISTER_ROUTER_MAP: Record<PamResource, (server: Fast
registerPamResourceEndpoints({ registerPamResourceEndpoints({
server, server,
resourceType: PamResource.Postgres, resourceType: PamResource.Postgres,
resourceResponseSchema: PostgresResourceSchema, resourceResponseSchema: SanitizedPostgresResourceSchema,
createResourceSchema: CreatePostgresResourceSchema, createResourceSchema: CreatePostgresResourceSchema,
updateResourceSchema: UpdatePostgresResourceSchema updateResourceSchema: UpdatePostgresResourceSchema
}); });
@@ -21,11 +21,13 @@ export const registerPamResourceEndpoints = <T extends TPamResource>({
connectionDetails: T["connectionDetails"]; connectionDetails: T["connectionDetails"];
gatewayId: T["gatewayId"]; gatewayId: T["gatewayId"];
name: T["name"]; name: T["name"];
rotationAccountCredentials?: T["rotationAccountCredentials"];
}>; }>;
updateResourceSchema: z.ZodType<{ updateResourceSchema: z.ZodType<{
connectionDetails?: T["connectionDetails"]; connectionDetails?: T["connectionDetails"];
gatewayId?: T["gatewayId"]; gatewayId?: T["gatewayId"];
name?: T["name"]; name?: T["name"];
rotationAccountCredentials?: T["rotationAccountCredentials"];
}>; }>;
resourceResponseSchema: z.ZodTypeAny; resourceResponseSchema: z.ZodTypeAny;
}) => { }) => {
@@ -3,14 +3,14 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { import {
PostgresResourceListItemSchema, PostgresResourceListItemSchema,
PostgresResourceSchema SanitizedPostgresResourceSchema
} from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas"; } from "@app/ee/services/pam-resource/postgres/postgres-resource-schemas";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
// Use z.union([...]) when more resources are added // Use z.union([...]) when more resources are added
const ResourceSchema = PostgresResourceSchema; const SanitizedResourceSchema = SanitizedPostgresResourceSchema;
const ResourceOptionsSchema = z.discriminatedUnion("resource", [PostgresResourceListItemSchema]); const ResourceOptionsSchema = z.discriminatedUnion("resource", [PostgresResourceListItemSchema]);
@@ -50,7 +50,7 @@ export const registerPamResourceRouter = async (server: FastifyZodProvider) => {
}), }),
response: { response: {
200: z.object({ 200: z.object({
resources: ResourceSchema.array() resources: SanitizedResourceSchema.array()
}) })
} }
}, },
+163
View File
@@ -0,0 +1,163 @@
import { z } from "zod";
import { OrganizationsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, SUB_ORGANIZATIONS } 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 { AuthMode } from "@app/services/auth/auth-type";
const sanitizedSubOrganizationSchema = OrganizationsSchema.pick({
id: true,
name: true,
slug: true,
createdAt: true,
updatedAt: true,
parentOrgId: true
});
export const registerSubOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "Create a sub organization",
security: [
{
bearerAuth: []
}
],
body: z.object({
name: slugSchema().describe(SUB_ORGANIZATIONS.CREATE.name)
}),
response: {
200: z.object({
organization: sanitizedSubOrganizationSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organization } = await server.services.subOrganization.createSubOrg({
name: req.body.name,
permissionActor: req.permission
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.CREATE_SUB_ORGANIZATION,
metadata: {
name: req.body.name,
organizationId: organization.id
}
}
});
return { organization };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "List of sub organizations",
security: [
{
bearerAuth: []
}
],
querystring: z.object({
limit: z.coerce.number().min(1).max(1000).default(25).describe(SUB_ORGANIZATIONS.LIST.limit),
offset: z.coerce.number().min(0).default(0).describe(SUB_ORGANIZATIONS.LIST.offset),
isAccessible: z
.enum(["true", "false"])
.optional()
.transform((value) => value === "true")
.describe(SUB_ORGANIZATIONS.LIST.isAccessible)
}),
response: {
200: z.object({
organizations: sanitizedSubOrganizationSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organizations } = await server.services.subOrganization.listSubOrgs({
permissionActor: req.permission,
data: {
limit: req.query.limit,
offset: req.query.offset,
isAccessible: req.query.isAccessible
}
});
return { organizations };
}
});
server.route({
method: "PATCH",
url: "/:subOrgId",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SubOrganizations],
description: "Update a sub organization",
security: [
{
bearerAuth: []
}
],
params: z.object({
subOrgId: z.string().trim().describe(SUB_ORGANIZATIONS.UPDATE.subOrgId)
}),
body: z.object({
name: slugSchema().describe(SUB_ORGANIZATIONS.UPDATE.name)
}),
response: {
200: z.object({
organization: sanitizedSubOrganizationSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { organization } = await server.services.subOrganization.updateSubOrg({
subOrgId: req.params.subOrgId,
name: req.body.name,
permissionActor: req.permission
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.UPDATE_SUB_ORGANIZATION,
metadata: {
name: req.body.name,
organizationId: organization.id
}
}
});
return { organization };
}
});
};
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { TAuditLogs } from "@app/db/schemas"; import { OrganizationActionScope, TAuditLogs } from "@app/db/schemas";
import { import {
decryptLogStream, decryptLogStream,
decryptLogStreamCredentials, decryptLogStreamCredentials,
@@ -45,13 +45,14 @@ export const auditLogStreamServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
@@ -94,13 +95,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
logStream.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
@@ -160,13 +162,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit Log Stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
logStream.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
@@ -185,14 +188,14 @@ export const auditLogStreamServiceFactory = ({
const logStream = await auditLogStreamDAL.findById(logStreamId); const logStream = await auditLogStreamDAL.findById(logStreamId);
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${logStreamId}' not found` }); if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${logStreamId}' not found` });
const { permission } = await permissionService.getOrgPermission({
const { permission } = await permissionService.getOrgPermission( scope: OrganizationActionScope.Any,
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
logStream.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -206,13 +209,14 @@ export const auditLogStreamServiceFactory = ({
}; };
const list = async (actor: OrgServiceActor) => { const list = async (actor: OrgServiceActor) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, scope: OrganizationActionScope.Any,
actor.id, actor: actor.type,
actor.orgId, actorId: actor.id,
actor.authMethod, orgId: actor.orgId,
actor.orgId actorAuthMethod: actor.authMethod,
); actorOrgId: actor.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { requestContext } from "@fastify/request-context"; import { requestContext } from "@fastify/request-context";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
@@ -47,13 +47,14 @@ export const auditLogServiceFactory = ({
); );
} else { } else {
// Organization-wide logs // Organization-wide logs
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionAuditLogsActions.Read, OrgPermissionAuditLogsActions.Read,
@@ -173,6 +173,9 @@ export enum EventType {
UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth", UPDATE_TOKEN_IDENTITY_TOKEN_AUTH = "update-token-identity-token-auth",
GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth", GET_TOKENS_IDENTITY_TOKEN_AUTH = "get-tokens-identity-token-auth",
CREATE_SUB_ORGANIZATION = "create-sub-organization",
UPDATE_SUB_ORGANIZATION = "update-sub-organization",
ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth", ADD_IDENTITY_TOKEN_AUTH = "add-identity-token-auth",
UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth", UPDATE_IDENTITY_TOKEN_AUTH = "update-identity-token-auth",
GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth", GET_IDENTITY_TOKEN_AUTH = "get-identity-token-auth",
@@ -524,6 +527,8 @@ export enum EventType {
PAM_ACCOUNT_CREATE = "pam-account-create", PAM_ACCOUNT_CREATE = "pam-account-create",
PAM_ACCOUNT_UPDATE = "pam-account-update", PAM_ACCOUNT_UPDATE = "pam-account-update",
PAM_ACCOUNT_DELETE = "pam-account-delete", PAM_ACCOUNT_DELETE = "pam-account-delete",
PAM_ACCOUNT_CREDENTIAL_ROTATION = "pam-account-credential-rotation",
PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED = "pam-account-credential-rotation-failed",
PAM_RESOURCE_LIST = "pam-resource-list", PAM_RESOURCE_LIST = "pam-resource-list",
PAM_RESOURCE_GET = "pam-resource-get", PAM_RESOURCE_GET = "pam-resource-get",
PAM_RESOURCE_CREATE = "pam-resource-create", PAM_RESOURCE_CREATE = "pam-resource-create",
@@ -616,6 +621,22 @@ interface GetSecretsEvent {
}; };
} }
interface CreateSubOrganizationEvent {
type: EventType.CREATE_SUB_ORGANIZATION;
metadata: {
name: string;
organizationId: string;
};
}
interface UpdateSubOrganizationEvent {
type: EventType.UPDATE_SUB_ORGANIZATION;
metadata: {
name: string;
organizationId: string;
};
}
type TSecretMetadata = { key: string; value: string }[]; type TSecretMetadata = { key: string; value: string }[];
interface GetSecretEvent { interface GetSecretEvent {
@@ -3896,6 +3917,8 @@ interface PamAccountCreateEvent {
folderId?: string | null; folderId?: string | null;
name: string; name: string;
description?: string | null; description?: string | null;
rotationEnabled: boolean;
rotationIntervalSeconds?: number | null;
}; };
} }
@@ -3907,6 +3930,8 @@ interface PamAccountUpdateEvent {
resourceType: string; resourceType: string;
name?: string; name?: string;
description?: string | null; description?: string | null;
rotationEnabled?: boolean;
rotationIntervalSeconds?: number | null;
}; };
} }
@@ -3920,6 +3945,27 @@ interface PamAccountDeleteEvent {
}; };
} }
interface PamAccountCredentialRotationEvent {
type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION;
metadata: {
accountName: string;
accountId: string;
resourceId: string;
resourceType: string;
};
}
interface PamAccountCredentialRotationFailedEvent {
type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED;
metadata: {
accountName: string;
accountId: string;
resourceId: string;
resourceType: string;
errorMessage: string;
};
}
interface PamResourceListEvent { interface PamResourceListEvent {
type: EventType.PAM_RESOURCE_LIST; type: EventType.PAM_RESOURCE_LIST;
metadata: { metadata: {
@@ -3964,6 +4010,8 @@ interface PamResourceDeleteEvent {
} }
export type Event = export type Event =
| CreateSubOrganizationEvent
| UpdateSubOrganizationEvent
| GetSecretsEvent | GetSecretsEvent
| GetSecretEvent | GetSecretEvent
| CreateSecretEvent | CreateSecretEvent
@@ -4319,6 +4367,8 @@ export type Event =
| PamAccountCreateEvent | PamAccountCreateEvent
| PamAccountUpdateEvent | PamAccountUpdateEvent
| PamAccountDeleteEvent | PamAccountDeleteEvent
| PamAccountCredentialRotationEvent
| PamAccountCredentialRotationFailedEvent
| PamResourceListEvent | PamResourceListEvent
| PamResourceGetEvent | PamResourceGetEvent
| PamResourceCreateEvent | PamResourceCreateEvent
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -134,13 +134,14 @@ export const dynamicSecretServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
gateway?.orgId ?? gatewayv2?.orgId, orgId: gateway?.orgId || gatewayv2?.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
@@ -297,13 +298,14 @@ export const dynamicSecretServiceFactory = ({
isGatewayV1 = false; isGatewayV1 = false;
} }
const { permission: orgPermission } = await permissionService.getOrgPermission( const { permission: orgPermission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: gateway?.orgId || gatewayv2?.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(orgPermission).throwUnlessCan( ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways, OrgPermissionGatewayActions.AttachGateways,
@@ -3,6 +3,7 @@ import { STSServiceException } from "@aws-sdk/client-sts";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { OrganizationActionScope } from "@app/db/schemas";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
@@ -51,13 +52,14 @@ export const externalKmsServiceFactory = ({
actorOrgId, actorOrgId,
actorAuthMethod actorAuthMethod
}: TCreateExternalKmsDTO) => { }: TCreateExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Kms);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -154,13 +156,14 @@ export const externalKmsServiceFactory = ({
actorAuthMethod actorAuthMethod
}: TUpdateExternalKmsDTO) => { }: TUpdateExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
const plan = await licenseService.getPlan(kmsDoc.orgId); const plan = await licenseService.getPlan(kmsDoc.orgId);
@@ -257,13 +260,14 @@ export const externalKmsServiceFactory = ({
const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => { const deleteById = async ({ actor, id: kmsId, actorId, actorOrgId, actorAuthMethod }: TDeleteExternalKmsDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
@@ -278,13 +282,14 @@ export const externalKmsServiceFactory = ({
}; };
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => { const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListExternalKmsDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId }); const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
@@ -294,13 +299,14 @@ export const externalKmsServiceFactory = ({
const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => { const findById = async ({ actor, actorId, actorOrgId, actorAuthMethod, id: kmsId }: TGetExternalKmsByIdDTO) => {
const kmsDoc = await kmsDAL.findById(kmsId); const kmsDoc = await kmsDAL.findById(kmsId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
@@ -342,13 +348,14 @@ export const externalKmsServiceFactory = ({
name: kmsName name: kmsName
}: TGetExternalKmsBySlugDTO) => { }: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId }); const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
kmsDoc.orgId, orgId: kmsDoc.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id }); const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
@@ -3,7 +3,7 @@ import net from "node:net";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { OrgMembershipRole, TRelays } from "@app/db/schemas"; import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore"; import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { DatabaseErrorCode } from "@app/lib/error-codes"; import { DatabaseErrorCode } from "@app/lib/error-codes";
@@ -73,13 +73,14 @@ export const gatewayV2ServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
actor: ActorType.IDENTITY,
actorId, actorId,
orgId, orgId,
actorAuthMethod, actorAuthMethod,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -258,13 +259,14 @@ export const gatewayV2ServiceFactory = ({
}; };
const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { const listGateways = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
@@ -815,13 +817,14 @@ export const gatewayV2ServiceFactory = ({
throw new NotFoundError({ message: `Gateway ${id} not found` }); throw new NotFoundError({ message: `Gateway ${id} not found` });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
gateway.orgId, orgId: gateway.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.DeleteGateways, OrgPermissionGatewayActions.DeleteGateways,
@@ -845,13 +848,14 @@ export const gatewayV2ServiceFactory = ({
}; };
const getPamSessionKey = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => { const getPamSessionKey = async ({ orgPermission }: { orgPermission: OrgServiceActor }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { z } from "zod"; import { z } from "zod";
import { OrganizationActionScope } from "@app/db/schemas";
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore"; import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
@@ -68,13 +69,14 @@ export const gatewayServiceFactory = ({
"Gateway handshake failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan." "Gateway handshake failed due to organization plan restrictions. Please upgrade your instance to Infisical's Enterprise plan."
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, actor: ActorType.IDENTITY,
actorId, actorId,
orgId, orgId,
actorAuthMethod, actorAuthMethod,
orgId actorOrgId: orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -480,13 +482,14 @@ export const gatewayServiceFactory = ({
}; };
const listGateways = async ({ orgPermission }: TListGatewaysDTO) => { const listGateways = async ({ orgPermission }: TListGatewaysDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -501,13 +504,14 @@ export const gatewayServiceFactory = ({
}; };
const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => { const getGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.ListGateways, OrgPermissionGatewayActions.ListGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -521,13 +525,14 @@ export const gatewayServiceFactory = ({
}; };
const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => { const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.EditGateways, OrgPermissionGatewayActions.EditGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -542,13 +547,14 @@ export const gatewayServiceFactory = ({
}; };
const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => { const deleteGatewayById = async ({ orgPermission, id }: TGetGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.DeleteGateways, OrgPermissionGatewayActions.DeleteGateways,
OrgPermissionSubjects.Gateway OrgPermissionSubjects.Gateway
@@ -6,7 +6,7 @@ import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
import { Octokit as OctokitRest } from "@octokit/rest"; import { Octokit as OctokitRest } from "@octokit/rest";
import RE2 from "re2"; import RE2 from "re2";
import { AccessScope, OrgMembershipRole } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole } from "@app/db/schemas";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
@@ -104,13 +104,14 @@ export const githubOrgSyncServiceFactory = ({
githubOrgAccessToken, githubOrgAccessToken,
isActive isActive
}: TCreateGithubOrgSyncDTO) => { }: TCreateGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, scope: OrganizationActionScope.ParentOrganization,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, actorId: orgPermission.id,
orgPermission.authMethod, orgId: orgPermission.orgId,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
const plan = await licenseService.getPlan(orgPermission.orgId); const plan = await licenseService.getPlan(orgPermission.orgId);
@@ -162,13 +163,14 @@ export const githubOrgSyncServiceFactory = ({
githubOrgAccessToken, githubOrgAccessToken,
isActive isActive
}: TUpdateGithubOrgSyncDTO) => { }: TUpdateGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, scope: OrganizationActionScope.ParentOrganization,
orgPermission.orgId, actorId: orgPermission.id,
orgPermission.authMethod, orgId: orgPermission.orgId,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
const plan = await licenseService.getPlan(orgPermission.orgId); const plan = await licenseService.getPlan(orgPermission.orgId);
@@ -226,13 +228,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => { const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actor: orgPermission.type,
orgPermission.id, actorId: orgPermission.id,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
@@ -256,13 +259,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => { const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actorId: orgPermission.id,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
@@ -422,13 +426,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const validateGithubToken = async ({ orgPermission, githubOrgAccessToken }: TValidateGithubTokenDTO) => { const validateGithubToken = async ({ orgPermission, githubOrgAccessToken }: TValidateGithubTokenDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, actorId: orgPermission.id,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorAuthMethod: orgPermission.authMethod,
orgPermission.orgId actorOrgId: orgPermission.orgId,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
@@ -509,13 +514,14 @@ export const githubOrgSyncServiceFactory = ({
}; };
const syncAllTeams = async ({ orgPermission }: TSyncAllTeamsDTO): Promise<TSyncResult> => { const syncAllTeams = async ({ orgPermission }: TSyncAllTeamsDTO): Promise<TSyncResult> => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
orgPermission.type, scope: OrganizationActionScope.ParentOrganization,
orgPermission.id, actor: orgPermission.type,
orgPermission.orgId, orgId: orgPermission.orgId,
orgPermission.authMethod, actorId: orgPermission.id,
orgPermission.orgId actorAuthMethod: orgPermission.authMethod,
); actorOrgId: orgPermission.orgId
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionActions.Edit, OrgPermissionActions.Edit,
+31 -24
View File
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import { AccessScope, OrgMembershipRole, TRoles } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipRole, TRoles } from "@app/db/schemas";
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal"; import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -73,13 +73,14 @@ export const groupServiceFactory = ({
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => { const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Create, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -167,13 +168,14 @@ export const groupServiceFactory = ({
}: TUpdateGroupDTO) => { }: TUpdateGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
@@ -270,13 +272,14 @@ export const groupServiceFactory = ({
const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => { const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Delete, OrgPermissionSubjects.Groups);
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -297,17 +300,18 @@ export const groupServiceFactory = ({
const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => { const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const group = await groupDAL.findById(id); const group = await groupDAL.findById(id);
if (!group) { if (!group || group.orgId !== actorOrgId) {
throw new NotFoundError({ throw new NotFoundError({
message: `Cannot find group with ID ${id}` message: `Cannot find group with ID ${id}`
}); });
@@ -330,13 +334,14 @@ export const groupServiceFactory = ({
}: TListGroupUsersDTO) => { }: TListGroupUsersDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Read, OrgPermissionSubjects.Groups);
const group = await groupDAL.findOne({ const group = await groupDAL.findOne({
@@ -365,13 +370,14 @@ export const groupServiceFactory = ({
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => { const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists // check if group with slug exists
@@ -451,13 +457,14 @@ export const groupServiceFactory = ({
}: TRemoveUserFromGroupDTO) => { }: TRemoveUserFromGroupDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" }); if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionGroupActions.Edit, OrgPermissionSubjects.Groups);
// check if group with slug exists // check if group with slug exists
+40 -2
View File
@@ -1,8 +1,14 @@
import * as pkcs11js from "pkcs11js"; import * as pkcs11js from "pkcs11js";
import { TEnvConfig } from "@app/lib/config/env"; import { TEnvConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { KMS_ROOT_CONFIG_UUID } from "@app/services/kms/kms-fns";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { THsmServiceFactory } from "./hsm-service";
import { HsmModule } from "./hsm-types"; import { HsmModule } from "./hsm-types";
export const initializeHsmModule = (envConfig: Pick<TEnvConfig, "isHsmConfigured" | "HSM_LIB_PATH">) => { export const initializeHsmModule = (envConfig: Pick<TEnvConfig, "isHsmConfigured" | "HSM_LIB_PATH">) => {
@@ -25,10 +31,9 @@ export const initializeHsmModule = (envConfig: Pick<TEnvConfig, "isHsmConfigured
logger.info("PKCS#11 module initialized"); logger.info("PKCS#11 module initialized");
} catch (error) { } catch (error) {
logger.error(error, "Failed to initialize PKCS#11 module");
if ((error as { message?: string })?.message === "CKR_CRYPTOKI_ALREADY_INITIALIZED") { if ((error as { message?: string })?.message === "CKR_CRYPTOKI_ALREADY_INITIALIZED") {
logger.info("Skipping HSM initialization because it's already initialized."); logger.info("Skipping HSM initialization because it's already initialized.");
isInitialized = true;
} else { } else {
logger.error(error, "Failed to initialize PKCS#11 module"); logger.error(error, "Failed to initialize PKCS#11 module");
throw error; throw error;
@@ -60,3 +65,36 @@ export const initializeHsmModule = (envConfig: Pick<TEnvConfig, "isHsmConfigured
getModule getModule
}; };
}; };
export const isHsmActiveAndEnabled = async ({
hsmService,
kmsRootConfigDAL,
licenseService
}: {
hsmService: Pick<THsmServiceFactory, "isActive">;
kmsRootConfigDAL: Pick<TKmsRootConfigDALFactory, "findById">;
licenseService?: Pick<TLicenseServiceFactory, "onPremFeatures">;
}) => {
const isHsmConfigured = await hsmService.isActive();
// null if the root kms config does not exist
let rootKmsConfigEncryptionStrategy: RootKeyEncryptionStrategy | null = null;
const rootKmsConfig = await kmsRootConfigDAL.findById(KMS_ROOT_CONFIG_UUID).catch(() => null);
rootKmsConfigEncryptionStrategy = (rootKmsConfig?.encryptionStrategy || null) as RootKeyEncryptionStrategy | null;
if (
rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.HSM &&
licenseService &&
!licenseService.onPremFeatures.hsm
) {
throw new BadRequestError({
message: "Your license does not include HSM integration. Please upgrade to the Enterprise plan to use HSM."
});
}
return {
rootKmsConfigEncryptionStrategy,
isHsmConfigured
};
};
+20 -3
View File
@@ -25,6 +25,8 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon
const AES_KEY_SIZE = 256; const AES_KEY_SIZE = 256;
const HMAC_KEY_SIZE = 256; const HMAC_KEY_SIZE = 256;
let pkcs11TestPassed = false;
const $withSession = async <T>(callbackWithSession: SessionCallback<T>): Promise<T> => { const $withSession = async <T>(callbackWithSession: SessionCallback<T>): Promise<T> => {
const RETRY_INTERVAL = 200; // 200ms between attempts const RETRY_INTERVAL = 200; // 200ms between attempts
const MAX_TIMEOUT = 90_000; // 90 seconds maximum total time const MAX_TIMEOUT = 90_000; // 90 seconds maximum total time
@@ -363,7 +365,9 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon
return false; return false;
} }
let pkcs11TestPassed = false; if (pkcs11TestPassed) {
return true;
}
try { try {
pkcs11TestPassed = await $withSession($testPkcs11Module); pkcs11TestPassed = await $withSession($testPkcs11Module);
@@ -371,7 +375,7 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon
logger.error(err, "HSM: Error testing PKCS#11 module"); logger.error(err, "HSM: Error testing PKCS#11 module");
} }
return envConfig.isHsmConfigured && isInitialized && pkcs11TestPassed; return pkcs11TestPassed;
}; };
const startService = async () => { const startService = async () => {
@@ -460,10 +464,23 @@ export const hsmServiceFactory = ({ hsmModule: { isInitialized, pkcs11 }, envCon
} }
}; };
const randomBytes = async (length: number) => {
if (!pkcs11 || !isInitialized) {
throw new Error("PKCS#11 module is not initialized");
}
const randomData = await $withSession((sessionHandle) =>
pkcs11.C_GenerateRandom(sessionHandle, Buffer.alloc(length))
);
return randomData;
};
return { return {
encrypt, encrypt,
startService, startService,
isActive, isActive,
decrypt decrypt,
randomBytes
}; };
}; };
+7
View File
@@ -1,5 +1,7 @@
import pkcs11js from "pkcs11js"; import pkcs11js from "pkcs11js";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
export type HsmModule = { export type HsmModule = {
pkcs11: pkcs11js.PKCS11; pkcs11: pkcs11js.PKCS11;
isInitialized: boolean; isInitialized: boolean;
@@ -9,3 +11,8 @@ export enum HsmKeyType {
AES = "AES", AES = "AES",
HMAC = "hmac" HMAC = "hmac"
} }
export type THsmStatus = {
rootKmsConfigEncryptionStrategy: RootKeyEncryptionStrategy | null;
isHsmConfigured: boolean;
};
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { import {
@@ -68,13 +69,14 @@ export const identityAuthTemplateServiceFactory = ({
templateFields: Record<string, unknown>; templateFields: Record<string, unknown>;
} & Omit<TOrgPermission, "orgId">) => { } & Omit<TOrgPermission, "orgId">) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates, OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -113,13 +115,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates, OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -227,13 +230,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates, OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -282,13 +286,14 @@ export const identityAuthTemplateServiceFactory = ({
throw new NotFoundError({ message: "Template not found" }); throw new NotFoundError({ message: "Template not found" });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
template.orgId, orgId: template.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -316,13 +321,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TListIdentityAuthTemplatesDTO) => { }: TListIdentityAuthTemplatesDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -352,13 +358,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TGetTemplatesByAuthMethodDTO) => { }: TGetTemplatesByAuthMethodDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates, OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -385,13 +392,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TFindTemplateUsagesDTO) => { }: TFindTemplateUsagesDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -415,13 +423,14 @@ export const identityAuthTemplateServiceFactory = ({
actorOrgId actorOrgId
}: TUnlinkTemplateUsageDTO) => { }: TUnlinkTemplateUsageDTO) => {
await $checkPlan(actorOrgId); await $checkPlan(actorOrgId);
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates, OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
OrgPermissionSubjects.MachineIdentityAuthTemplate OrgPermissionSubjects.MachineIdentityAuthTemplate
@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { OrganizationActionScope } from "@app/db/schemas";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal"; import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@@ -46,13 +47,14 @@ export const kmipOperationServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TKmipCreateDTO) => { }: TKmipCreateDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -78,13 +80,14 @@ export const kmipOperationServiceFactory = ({
}; };
const destroy = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDestroyDTO) => { const destroy = async ({ projectId, id, clientId, actor, actorId, actorOrgId, actorAuthMethod }: TKmipDestroyDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -131,13 +134,14 @@ export const kmipOperationServiceFactory = ({
}; };
const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => { const get = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -189,13 +193,14 @@ export const kmipOperationServiceFactory = ({
}; };
const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => { const activate = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipGetDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -226,13 +231,14 @@ export const kmipOperationServiceFactory = ({
}; };
const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => { const revoke = async ({ projectId, id, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipRevokeDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -287,13 +293,14 @@ export const kmipOperationServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TKmipGetAttributesDTO) => { }: TKmipGetAttributesDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -339,13 +346,14 @@ export const kmipOperationServiceFactory = ({
}; };
const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => { const locate = async ({ projectId, clientId, actor, actorId, actorAuthMethod, actorOrgId }: TKmipLocateDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -377,13 +385,14 @@ export const kmipOperationServiceFactory = ({
actorOrgId, actorOrgId,
kmipMetadata kmipMetadata
}: TKmipRegisterDTO) => { }: TKmipRegisterDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
+17 -8
View File
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidIp } from "@app/lib/ip"; import { isValidIp } from "@app/lib/ip";
@@ -401,13 +401,14 @@ export const kmipServiceFactory = ({
}; };
const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => { const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
const kmipConfig = await kmipOrgConfigDAL.findOne({ const kmipConfig = await kmipOrgConfigDAL.findOne({
@@ -566,7 +567,14 @@ export const kmipServiceFactory = ({
}; };
const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => { const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => {
await permissionService.getOrgPermission(actor, actorId, actorOrgId, actorAuthMethod, actorOrgId); await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
const kmipConfig = await kmipOrgConfigDAL.findOne({ const kmipConfig = await kmipOrgConfigDAL.findOne({
orgId: actorOrgId orgId: actorOrgId
@@ -759,13 +767,14 @@ export const kmipServiceFactory = ({
keyAlgorithm, keyAlgorithm,
hostnamesOrIps hostnamesOrIps
}: TRegisterServerDTO) => { }: TRegisterServerDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
@@ -1,7 +1,14 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import { AccessScope, OrgMembershipStatus, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas"; import {
AccessScope,
OrganizationActionScope,
OrgMembershipStatus,
TableName,
TLdapConfigsUpdate,
TUsers
} from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@@ -119,7 +126,14 @@ export const ldapConfigServiceFactory = ({
groupSearchFilter, groupSearchFilter,
caCert caCert
}: TCreateLdapCfgDTO) => { }: TCreateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -238,7 +252,14 @@ export const ldapConfigServiceFactory = ({
groupSearchFilter, groupSearchFilter,
caCert caCert
}: TUpdateLdapCfgDTO) => { }: TUpdateLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -316,7 +337,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetLdapCfgDTO) => { }: TGetLdapCfgDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
return getLdapCfg({ return getLdapCfg({
orgId orgId
@@ -649,7 +677,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetLdapGroupMapsDTO) => { }: TGetLdapGroupMapsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
const ldapConfig = await ldapConfigDAL.findOne({ const ldapConfig = await ldapConfigDAL.findOne({
@@ -678,7 +713,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TCreateLdapGroupMapDTO) => { }: TCreateLdapGroupMapDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -732,7 +774,14 @@ export const ldapConfigServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TDeleteLdapGroupMapDTO) => { }: TDeleteLdapGroupMapDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -771,7 +820,14 @@ export const ldapConfigServiceFactory = ({
caCert, caCert,
url url
}: TTestLdapConnectionDTO) => { }: TTestLdapConnectionDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId: actorOrgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
+28 -5
View File
@@ -10,6 +10,7 @@ export const licenseDALFactory = (db: TDbClient) => {
const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => { const countOfOrgMembers = async (orgId: string | null, tx?: Knex) => {
try { try {
const doc = await (tx || db.replicaNode())(TableName.Membership) const doc = await (tx || db.replicaNode())(TableName.Membership)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization }) .where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.andWhere((bd) => { .andWhere((bd) => {
if (orgId) { if (orgId) {
@@ -18,6 +19,7 @@ export const licenseDALFactory = (db: TDbClient) => {
}) })
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.whereNull(`${TableName.Organization}.rootOrgId`)
.count(); .count();
return Number(doc?.[0]?.count ?? 0); return Number(doc?.[0]?.count ?? 0);
} catch (error) { } catch (error) {
@@ -25,10 +27,31 @@ export const licenseDALFactory = (db: TDbClient) => {
} }
}; };
const countOfOrgIdentities = async (orgId: string | null, tx?: Knex) => {
try {
// count org identities
const identityDoc = await (tx || db.replicaNode())(TableName.Identity)
.join(TableName.Organization, `${TableName.Identity}.orgId`, `${TableName.Organization}.id`)
.where((bd) => {
if (orgId) {
void bd.where(`${TableName.Organization}.rootOrgId`, orgId).orWhere(`${TableName.Organization}.id`, orgId);
}
})
.count();
const identityCount = Number(identityDoc?.[0].count);
return identityCount;
} catch (error) {
throw new DatabaseError({ error, name: "Count of Org Users + Identities" });
}
};
const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => { const countOrgUsersAndIdentities = async (orgId: string | null, tx?: Knex) => {
try { try {
// count org users // count org users
const userDoc = await (tx || db.replicaNode())(TableName.Membership) const userDoc = await (tx || db.replicaNode())(TableName.Membership)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Membership}.scopeOrgId`)
.where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization }) .where({ status: OrgMembershipStatus.Accepted, scope: AccessScope.Organization })
.whereNotNull(`${TableName.Membership}.actorUserId`) .whereNotNull(`${TableName.Membership}.actorUserId`)
.andWhere((bd) => { .andWhere((bd) => {
@@ -38,17 +61,17 @@ export const licenseDALFactory = (db: TDbClient) => {
}) })
.join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`) .join(TableName.Users, `${TableName.Membership}.actorUserId`, `${TableName.Users}.id`)
.where(`${TableName.Users}.isGhost`, false) .where(`${TableName.Users}.isGhost`, false)
.whereNull(`${TableName.Organization}.rootOrgId`)
.count(); .count();
const userCount = Number(userDoc?.[0].count); const userCount = Number(userDoc?.[0].count);
// count org identities // count org identities
const identityDoc = await (tx || db.replicaNode())(TableName.Membership) const identityDoc = await (tx || db.replicaNode())(TableName.Identity)
.where({ scope: AccessScope.Organization }) .join(TableName.Organization, `${TableName.Identity}.orgId`, `${TableName.Organization}.id`)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where((bd) => { .where((bd) => {
if (orgId) { if (orgId) {
void bd.where(`${TableName.Membership}.scopeOrgId`, orgId); void bd.where(`${TableName.Organization}.rootOrgId`, orgId).orWhere(`${TableName.Organization}.id`, orgId);
} }
}) })
.count(); .count();
@@ -61,5 +84,5 @@ export const licenseDALFactory = (db: TDbClient) => {
} }
}; };
return { countOfOrgMembers, countOrgUsersAndIdentities }; return { countOfOrgMembers, countOrgUsersAndIdentities, countOfOrgIdentities };
}; };
@@ -28,6 +28,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
rbac: false, rbac: false,
githubOrgSync: false, githubOrgSync: false,
customRateLimits: false, customRateLimits: false,
subOrganization: false,
customAlerts: false, customAlerts: false,
secretAccessInsights: false, secretAccessInsights: false,
auditLogs: false, auditLogs: false,
@@ -9,12 +9,12 @@ import { AxiosError } from "axios";
import { CronJob } from "cron"; import { CronJob } from "cron";
import { Knex } from "knex"; import { Knex } from "knex";
import { OrganizationActionScope } from "@app/db/schemas";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env"; import { TEnvConfig } from "@app/lib/config/env";
import { verifyOfflineLicense } from "@app/lib/crypto"; import { verifyOfflineLicense } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TIdentityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -45,11 +45,14 @@ import {
} from "./license-types"; } from "./license-types";
type TLicenseServiceFactoryDep = { type TLicenseServiceFactoryDep = {
orgDAL: Pick<TOrgDALFactory, "findOrgById" | "countAllOrgMembers">; envConfig: Pick<
TEnvConfig,
"LICENSE_SERVER_URL" | "LICENSE_SERVER_KEY" | "LICENSE_KEY" | "LICENSE_KEY_OFFLINE" | "INTERNAL_REGION" | "SITE_URL"
>;
orgDAL: Pick<TOrgDALFactory, "findRootOrgDetails" | "countAllOrgMembers" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseDAL: TLicenseDALFactory; licenseDAL: TLicenseDALFactory;
keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">; keyStore: Pick<TKeyStoreFactory, "setItemWithExpiry" | "getItem" | "deleteItem">;
identityOrgMembershipDAL: TIdentityOrgDALFactory;
projectDAL: TProjectDALFactory; projectDAL: TProjectDALFactory;
}; };
@@ -66,27 +69,26 @@ export const licenseServiceFactory = ({
permissionService, permissionService,
licenseDAL, licenseDAL,
keyStore, keyStore,
identityOrgMembershipDAL, projectDAL,
projectDAL envConfig
}: TLicenseServiceFactoryDep) => { }: TLicenseServiceFactoryDep) => {
let isValidLicense = false; let isValidLicense = false;
let instanceType = InstanceType.OnPrem; let instanceType = InstanceType.OnPrem;
let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures(); let onPremFeatures: TFeatureSet = getDefaultOnPremFeatures();
let selfHostedLicense: TOfflineLicense | null = null; let selfHostedLicense: TOfflineLicense | null = null;
const appCfg = getConfig();
const licenseServerCloudApi = setupLicenseRequestWithStore( const licenseServerCloudApi = setupLicenseRequestWithStore(
appCfg.LICENSE_SERVER_URL || "", envConfig.LICENSE_SERVER_URL || "",
LICENSE_SERVER_CLOUD_LOGIN, LICENSE_SERVER_CLOUD_LOGIN,
appCfg.LICENSE_SERVER_KEY || "", envConfig.LICENSE_SERVER_KEY || "",
appCfg.INTERNAL_REGION envConfig.INTERNAL_REGION
); );
const licenseServerOnPremApi = setupLicenseRequestWithStore( const licenseServerOnPremApi = setupLicenseRequestWithStore(
appCfg.LICENSE_SERVER_URL || "", envConfig.LICENSE_SERVER_URL || "",
LICENSE_SERVER_ON_PREM_LOGIN, LICENSE_SERVER_ON_PREM_LOGIN,
appCfg.LICENSE_KEY || "", envConfig.LICENSE_KEY || "",
appCfg.INTERNAL_REGION envConfig.INTERNAL_REGION
); );
const syncLicenseKeyOnPremFeatures = async (shouldThrow: boolean = false) => { const syncLicenseKeyOnPremFeatures = async (shouldThrow: boolean = false) => {
@@ -120,7 +122,7 @@ export const licenseServiceFactory = ({
const init = async () => { const init = async () => {
try { try {
if (appCfg.LICENSE_SERVER_KEY) { if (envConfig.LICENSE_SERVER_KEY) {
const token = await licenseServerCloudApi.refreshLicense(); const token = await licenseServerCloudApi.refreshLicense();
if (token) instanceType = InstanceType.Cloud; if (token) instanceType = InstanceType.Cloud;
logger.info(`Instance type: ${InstanceType.Cloud}`); logger.info(`Instance type: ${InstanceType.Cloud}`);
@@ -128,7 +130,7 @@ export const licenseServiceFactory = ({
return; return;
} }
if (appCfg.LICENSE_KEY) { if (envConfig.LICENSE_KEY) {
const token = await licenseServerOnPremApi.refreshLicense(); const token = await licenseServerOnPremApi.refreshLicense();
if (token) { if (token) {
await syncLicenseKeyOnPremFeatures(true); await syncLicenseKeyOnPremFeatures(true);
@@ -139,10 +141,10 @@ export const licenseServiceFactory = ({
return; return;
} }
if (appCfg.LICENSE_KEY_OFFLINE) { if (envConfig.LICENSE_KEY_OFFLINE) {
let isValidOfflineLicense = true; let isValidOfflineLicense = true;
const contents: TOfflineLicenseContents = JSON.parse( const contents: TOfflineLicenseContents = JSON.parse(
Buffer.from(appCfg.LICENSE_KEY_OFFLINE, "base64").toString("utf8") Buffer.from(envConfig.LICENSE_KEY_OFFLINE, "base64").toString("utf8")
); );
const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature); const isVerified = await verifyOfflineLicense(JSON.stringify(contents.license), contents.signature);
@@ -181,7 +183,7 @@ export const licenseServiceFactory = ({
}; };
const initializeBackgroundSync = async () => { const initializeBackgroundSync = async () => {
if (appCfg.LICENSE_KEY) { if (envConfig.LICENSE_KEY) {
logger.info("Setting up background sync process for refresh onPremFeatures"); logger.info("Setting up background sync process for refresh onPremFeatures");
const job = new CronJob("*/10 * * * *", syncLicenseKeyOnPremFeatures); const job = new CronJob("*/10 * * * *", syncLicenseKeyOnPremFeatures);
job.start(); job.start();
@@ -199,22 +201,23 @@ export const licenseServiceFactory = ({
return JSON.parse(cachedPlan) as TFeatureSet; return JSON.parse(cachedPlan) as TFeatureSet;
} }
const org = await orgDAL.findOrgById(orgId); const org = await orgDAL.findRootOrgDetails(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const rootOrgId = org.id;
const { const {
data: { currentPlan } data: { currentPlan }
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>( } = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
`/api/license-server/v1/customers/${org.customerId}/cloud-plan` `/api/license-server/v1/customers/${org.customerId}/cloud-plan`
); );
const workspacesUsed = await projectDAL.countOfOrgProjects(orgId); const workspacesUsed = await projectDAL.countOfOrgProjects(rootOrgId);
currentPlan.workspacesUsed = workspacesUsed; currentPlan.workspacesUsed = workspacesUsed;
const membersUsed = await licenseDAL.countOfOrgMembers(orgId); const membersUsed = await licenseDAL.countOfOrgMembers(rootOrgId);
currentPlan.membersUsed = membersUsed; currentPlan.membersUsed = membersUsed;
const identityUsed = await licenseDAL.countOrgUsersAndIdentities(orgId); const identityUsed = await licenseDAL.countOrgUsersAndIdentities(rootOrgId);
currentPlan.identitiesUsed = identityUsed;
if (currentPlan.identityLimit && currentPlan.identityLimit !== identityUsed) { if (currentPlan?.identitiesUsed && currentPlan.identitiesUsed !== identityUsed) {
try { try {
await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, { await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, {
quantity: membersUsed, quantity: membersUsed,
@@ -227,6 +230,7 @@ export const licenseServiceFactory = ({
); );
} }
} }
currentPlan.identitiesUsed = identityUsed;
await keyStore.setItemWithExpiry( await keyStore.setItemWithExpiry(
FEATURE_CACHE_KEY(org.id), FEATURE_CACHE_KEY(org.id),
@@ -284,19 +288,20 @@ export const licenseServiceFactory = ({
}; };
const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => { const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => {
if (instanceType === InstanceType.Cloud) { const org = await orgDAL.findRootOrgDetails(orgId, tx);
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` }); if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const quantity = await licenseDAL.countOfOrgMembers(orgId, tx); const rootOrgId = org.id;
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId, tx); if (instanceType === InstanceType.Cloud) {
const quantity = await licenseDAL.countOfOrgMembers(rootOrgId, tx);
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(rootOrgId, tx);
if (org?.customerId) { if (org?.customerId) {
await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, { await licenseServerCloudApi.request.patch(`/api/license-server/v1/customers/${org.customerId}/cloud-plan`, {
quantity, quantity,
quantityIdentities quantityIdentities
}); });
} }
await keyStore.deleteItem(FEATURE_CACHE_KEY(orgId)); await keyStore.deleteItem(FEATURE_CACHE_KEY(rootOrgId));
} else if (instanceType === InstanceType.EnterpriseOnPrem) { } else if (instanceType === InstanceType.EnterpriseOnPrem) {
const usedSeats = await licenseDAL.countOfOrgMembers(null, tx); const usedSeats = await licenseDAL.countOfOrgMembers(null, tx);
const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx); const usedIdentitySeats = await licenseDAL.countOrgUsersAndIdentities(null, tx);
@@ -307,7 +312,7 @@ export const licenseServiceFactory = ({
usedIdentitySeats usedIdentitySeats
}); });
} }
await refreshPlan(orgId); await refreshPlan(rootOrgId);
}; };
// below all are api calls // below all are api calls
@@ -319,7 +324,14 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
billingCycle billingCycle
}: TOrgPlansTableDTO) => { }: TOrgPlansTableDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const { data } = await licenseServerCloudApi.request.get( const { data } = await licenseServerCloudApi.request.get(
`/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}` `/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
@@ -336,7 +348,14 @@ export const licenseServiceFactory = ({
projectId, projectId,
refreshCache refreshCache
}: TOrgPlanDTO) => { }: TOrgPlanDTO) => {
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
if (refreshCache) { if (refreshCache) {
await refreshPlan(orgId); await refreshPlan(orgId);
} }
@@ -352,13 +371,20 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
success_url success_url
}: TStartOrgTrialDTO) => { }: TStartOrgTrialDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -384,13 +410,20 @@ export const licenseServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TCreateOrgPortalSession) => { }: TCreateOrgPortalSession) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: "Organization not found" message: "Organization not found"
@@ -411,8 +444,8 @@ export const licenseServiceFactory = ({
} = await licenseServerCloudApi.request.post( } = await licenseServerCloudApi.request.post(
`/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`, `/api/license-server/v1/customers/${organization.customerId}/billing-details/payment-methods`,
{ {
success_url: `${appCfg.SITE_URL}/organization/billing`, success_url: `${envConfig.SITE_URL}/organization/billing`,
cancel_url: `${appCfg.SITE_URL}/organization/billing` cancel_url: `${envConfig.SITE_URL}/organization/billing`
} }
); );
@@ -425,7 +458,7 @@ export const licenseServiceFactory = ({
} = await licenseServerCloudApi.request.post( } = await licenseServerCloudApi.request.post(
`/api/license-server/v1/customers/${organization.customerId}/billing-details/billing-portal`, `/api/license-server/v1/customers/${organization.customerId}/billing-details/billing-portal`,
{ {
return_url: `${appCfg.SITE_URL}/organization/billing` return_url: `${envConfig.SITE_URL}/organization/billing`
} }
); );
@@ -433,10 +466,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingInfo = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -502,7 +542,7 @@ export const licenseServiceFactory = ({
const getUsageMetrics = async (orgId: string) => { const getUsageMetrics = async (orgId: string) => {
const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([ const [orgMembersUsed, identityUsed, projectCount] = await Promise.all([
orgDAL.countAllOrgMembers(orgId), orgDAL.countAllOrgMembers(orgId),
identityOrgMembershipDAL.countAllOrgIdentities({ scopeOrgId: orgId }), licenseDAL.countOfOrgIdentities(orgId),
projectDAL.countOfOrgProjects(orgId) projectDAL.countOfOrgProjects(orgId)
]); ]);
@@ -516,10 +556,17 @@ export const licenseServiceFactory = ({
// returns org current plan feature table // returns org current plan feature table
const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgPlanTable = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -553,10 +600,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => { const getOrgBillingDetails = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgBillInfoDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -578,13 +632,20 @@ export const licenseServiceFactory = ({
name, name,
email email
}: TUpdateOrgBillingDetailsDTO) => { }: TUpdateOrgBillingDetailsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -601,10 +662,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => { const getOrgPmtMethods = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPmtMethodsDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -628,13 +696,20 @@ export const licenseServiceFactory = ({
success_url, success_url,
cancel_url cancel_url
}: TAddOrgPmtMethodDTO) => { }: TAddOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -660,13 +735,20 @@ export const licenseServiceFactory = ({
orgId, orgId,
pmtMethodId pmtMethodId
}: TDelOrgPmtMethodDTO) => { }: TDelOrgPmtMethodDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -692,10 +774,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => { const getOrgTaxIds = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TGetOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -710,13 +799,20 @@ export const licenseServiceFactory = ({
}; };
const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => { const addOrgTaxId = async ({ actorId, actor, actorAuthMethod, actorOrgId, orgId, type, value }: TAddOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -734,13 +830,20 @@ export const licenseServiceFactory = ({
}; };
const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => { const delOrgTaxId = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId, taxId }: TDelOrgTaxIdDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionBillingActions.ManageBilling, OrgPermissionBillingActions.ManageBilling,
OrgPermissionSubjects.Billing OrgPermissionSubjects.Billing
); );
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -754,10 +857,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => { const getOrgTaxInvoices = async ({ actorId, actor, actorOrgId, actorAuthMethod, orgId }: TOrgInvoiceDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -771,10 +881,17 @@ export const licenseServiceFactory = ({
}; };
const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => { const getOrgLicenses = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgLicensesDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
actorId,
actor,
orgId,
actorOrgId,
actorAuthMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionBillingActions.Read, OrgPermissionSubjects.Billing);
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findById(orgId);
if (!organization) { if (!organization) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with ID '${orgId}' not found` message: `Organization with ID '${orgId}' not found`
@@ -819,7 +936,6 @@ export const licenseServiceFactory = ({
getLicenseId, getLicenseId,
invalidateGetPlan, invalidateGetPlan,
updateSubscriptionOrgMemberCount, updateSubscriptionOrgMemberCount,
refreshPlan,
getOrgPlan, getOrgPlan,
getOrgPlansTableByBillCycle, getOrgPlansTableByBillCycle,
startOrgTrial, startOrgTrial,
@@ -33,6 +33,7 @@ export type TFeatureSet = {
membersUsed: number; membersUsed: number;
identityLimit: null; identityLimit: null;
identitiesUsed: number; identitiesUsed: number;
subOrganization: false;
environmentLimit: null; environmentLimit: null;
environmentsUsed: 0; environmentsUsed: 0;
secretVersioning: true; secretVersioning: true;
@@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client"; import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
import { AccessScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas"; import { AccessScope, OrganizationActionScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs"; import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
@@ -118,13 +118,14 @@ export const oidcConfigServiceFactory = ({
} }
if (dto.type === "external") { if (dto.type === "external") {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.actor, actorId: dto.actorId,
dto.actorId, actor: dto.actor,
dto.organizationId, orgId: dto.organizationId,
dto.actorAuthMethod, actorOrgId: dto.actorOrgId,
dto.actorOrgId actorAuthMethod: dto.actorAuthMethod,
); scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
} }
@@ -508,13 +509,14 @@ export const oidcConfigServiceFactory = ({
"Failed to update OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." "Failed to update OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
org.id, actor,
orgId: org.id,
actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.ParentOrganization
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
if (org.googleSsoAuthEnforced && isActive) { if (org.googleSsoAuthEnforced && isActive) {
@@ -602,13 +604,14 @@ export const oidcConfigServiceFactory = ({
"Failed to create OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration." "Failed to create OIDC SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor,
actorId, actorId,
org.id, actor,
orgId: org.id,
actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId scope: OrganizationActionScope.ParentOrganization
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
if (org.googleSsoAuthEnforced && isActive) { if (org.googleSsoAuthEnforced && isActive) {
@@ -764,7 +767,14 @@ export const oidcConfigServiceFactory = ({
}; };
const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => { const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => {
await permissionService.getOrgPermission(ActorType.USER, actor.id, orgId, actor.authMethod, actor.orgId); await permissionService.getOrgPermission({
actor: ActorType.USER,
actorId: actor.id,
orgId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
scope: OrganizationActionScope.ParentOrganization
});
const oidcConfig = await oidcConfigDAL.findOne({ const oidcConfig = await oidcConfigDAL.findOne({
orgId, orgId,
@@ -18,7 +18,8 @@ export const pamAccountDALFactory = (db: TDbClient) => {
.select( .select(
// resource // resource
db.ref("name").withSchema(TableName.PamResource).as("resourceName"), db.ref("name").withSchema(TableName.PamResource).as("resourceName"),
db.ref("resourceType").withSchema(TableName.PamResource) db.ref("resourceType").withSchema(TableName.PamResource),
db.ref("encryptedRotationAccountCredentials").withSchema(TableName.PamResource)
); );
if (filter) { if (filter) {
@@ -28,16 +29,35 @@ export const pamAccountDALFactory = (db: TDbClient) => {
const accounts = await query; const accounts = await query;
return accounts.map(({ resourceId, resourceName, resourceType, ...account }) => ({ return accounts.map(
({ resourceId, resourceName, resourceType, encryptedRotationAccountCredentials, ...account }) => ({
...account, ...account,
resourceId, resourceId,
resource: { resource: {
id: resourceId, id: resourceId,
name: resourceName, name: resourceName,
resourceType resourceType,
encryptedRotationAccountCredentials
} }
})); })
);
}; };
return { ...orm, findWithResourceDetails }; const findAccountsDueForRotation = async (tx?: Knex) => {
const dbClient = tx || db.replicaNode();
const accounts = await dbClient(TableName.PamAccount)
.innerJoin(TableName.PamResource, `${TableName.PamAccount}.resourceId`, `${TableName.PamResource}.id`)
.whereNotNull(`${TableName.PamResource}.encryptedRotationAccountCredentials`)
.whereNotNull(`${TableName.PamAccount}.rotationIntervalSeconds`)
.where(`${TableName.PamAccount}.rotationEnabled`, true)
.whereRaw(
`COALESCE("${TableName.PamAccount}"."lastRotatedAt", "${TableName.PamAccount}"."createdAt") + "${TableName.PamAccount}"."rotationIntervalSeconds" * interval '1 second' < NOW()`
)
.select(selectAllTableCols(TableName.PamAccount));
return accounts;
};
return { ...orm, findWithResourceDetails, findAccountsDueForRotation };
}; };
@@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, TPamAccounts, TPamResources } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope, TPamAccounts, TPamResources } from "@app/db/schemas";
import { PAM_RESOURCE_FACTORY_MAP } from "@app/ee/services/pam-resource/pam-resource-factory"; import { PAM_RESOURCE_FACTORY_MAP } from "@app/ee/services/pam-resource/pam-resource-factory";
import { decryptResource, decryptResourceConnectionDetails } from "@app/ee/services/pam-resource/pam-resource-fns"; import { decryptResource, decryptResourceConnectionDetails } from "@app/ee/services/pam-resource/pam-resource-fns";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -11,12 +11,14 @@ import {
} from "@app/ee/services/permission/project-permission"; } from "@app/ee/services/permission/project-permission";
import { DatabaseErrorCode } from "@app/lib/error-codes"; import { DatabaseErrorCode } from "@app/lib/error-codes";
import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, DatabaseError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { EventType, TAuditLogServiceFactory } from "../audit-log/audit-log-types";
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { TPamFolderDALFactory } from "../pam-folder/pam-folder-dal"; import { TPamFolderDALFactory } from "../pam-folder/pam-folder-dal";
@@ -45,10 +47,12 @@ type TPamAccountServiceFactoryDep = {
"getPAMConnectionDetails" | "getPlatformConnectionDetailsByGatewayId" "getPAMConnectionDetails" | "getPlatformConnectionDetailsByGatewayId"
>; >;
userDAL: TUserDALFactory; userDAL: TUserDALFactory;
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
}; };
export type TPamAccountServiceFactory = ReturnType<typeof pamAccountServiceFactory>; export type TPamAccountServiceFactory = ReturnType<typeof pamAccountServiceFactory>;
const ROTATION_CONCURRENCY_LIMIT = 10;
export const pamAccountServiceFactory = ({ export const pamAccountServiceFactory = ({
pamResourceDAL, pamResourceDAL,
pamSessionDAL, pamSessionDAL,
@@ -59,10 +63,19 @@ export const pamAccountServiceFactory = ({
permissionService, permissionService,
licenseService, licenseService,
kmsService, kmsService,
gatewayV2Service gatewayV2Service,
auditLogService
}: TPamAccountServiceFactoryDep) => { }: TPamAccountServiceFactoryDep) => {
const create = async ( const create = async (
{ credentials, resourceId, name, description, folderId }: TCreateAccountDTO, {
credentials,
resourceId,
name,
description,
folderId,
rotationEnabled,
rotationIntervalSeconds
}: TCreateAccountDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId); const orgLicensePlan = await licenseService.getPlan(actor.orgId);
@@ -72,6 +85,12 @@ export const pamAccountServiceFactory = ({
}); });
} }
if (rotationEnabled && (rotationIntervalSeconds === undefined || rotationIntervalSeconds === null)) {
throw new BadRequestError({
message: "Rotation interval must be defined when rotation is enabled."
});
}
const resource = await pamResourceDAL.findById(resourceId); const resource = await pamResourceDAL.findById(resourceId);
if (!resource) throw new NotFoundError({ message: `Resource with ID '${resourceId}' not found` }); if (!resource) throw new NotFoundError({ message: `Resource with ID '${resourceId}' not found` });
@@ -84,6 +103,10 @@ export const pamAccountServiceFactory = ({
actionProjectType: ActionProjectType.PAM actionProjectType: ActionProjectType.PAM
}); });
if (!resource.encryptedRotationAccountCredentials && rotationEnabled) {
throw new NotFoundError({ message: "Rotation credentials are not configured for this account's resource" });
}
const accountPath = await getFullPamFolderPath({ const accountPath = await getFullPamFolderPath({
pamFolderDAL, pamFolderDAL,
folderId, folderId,
@@ -126,12 +149,19 @@ export const pamAccountServiceFactory = ({
encryptedCredentials, encryptedCredentials,
name, name,
description, description,
folderId folderId,
rotationEnabled,
rotationIntervalSeconds
}); });
return { return {
...(await decryptAccount(account, resource.projectId, kmsService)), ...(await decryptAccount(account, resource.projectId, kmsService)),
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } resource: {
id: resource.id,
name: resource.name,
resourceType: resource.resourceType,
rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials
}
}; };
} catch (err) { } catch (err) {
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) { if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
@@ -145,7 +175,7 @@ export const pamAccountServiceFactory = ({
}; };
const updateById = async ( const updateById = async (
{ accountId, credentials, description, name }: TUpdateAccountDTO, { accountId, credentials, description, name, rotationEnabled, rotationIntervalSeconds }: TUpdateAccountDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId); const orgLicensePlan = await licenseService.getPlan(actor.orgId);
@@ -195,6 +225,17 @@ export const pamAccountServiceFactory = ({
updateDoc.description = description; updateDoc.description = description;
} }
if (rotationEnabled !== undefined) {
if (!resource.encryptedRotationAccountCredentials && rotationEnabled) {
throw new NotFoundError({ message: "Rotation credentials are not configured for this account's resource" });
}
updateDoc.rotationEnabled = rotationEnabled;
}
if (rotationIntervalSeconds !== undefined) {
updateDoc.rotationIntervalSeconds = rotationIntervalSeconds;
}
if (credentials !== undefined) { if (credentials !== undefined) {
const connectionDetails = await decryptResourceConnectionDetails({ const connectionDetails = await decryptResourceConnectionDetails({
projectId: account.projectId, projectId: account.projectId,
@@ -211,7 +252,7 @@ export const pamAccountServiceFactory = ({
// Logic to prevent overwriting unedited censored values // Logic to prevent overwriting unedited censored values
const finalCredentials = { ...credentials }; const finalCredentials = { ...credentials };
if (credentials.password === "******") { if (credentials.password === "__INFISICAL_UNCHANGED__") {
const decryptedCredentials = await decryptAccountCredentials({ const decryptedCredentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials, encryptedCredentials: account.encryptedCredentials,
projectId: account.projectId, projectId: account.projectId,
@@ -239,7 +280,12 @@ export const pamAccountServiceFactory = ({
return { return {
...(await decryptAccount(updatedAccount, account.projectId, kmsService)), ...(await decryptAccount(updatedAccount, account.projectId, kmsService)),
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } resource: {
id: resource.id,
name: resource.name,
resourceType: resource.resourceType,
rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials
}
}; };
}; };
@@ -278,7 +324,12 @@ export const pamAccountServiceFactory = ({
return { return {
...(await decryptAccount(deletedAccount, account.projectId, kmsService)), ...(await decryptAccount(deletedAccount, account.projectId, kmsService)),
resource: { id: resource.id, name: resource.name, resourceType: resource.resourceType } resource: {
id: resource.id,
name: resource.name,
resourceType: resource.resourceType,
rotationCredentialsConfigured: !!resource.encryptedRotationAccountCredentials
}
}; };
}; };
@@ -300,7 +351,7 @@ export const pamAccountServiceFactory = ({
const decryptedAndPermittedAccounts: Array< const decryptedAndPermittedAccounts: Array<
TPamAccounts & { TPamAccounts & {
resource: Pick<TPamResources, "id" | "name" | "resourceType">; resource: Pick<TPamResources, "id" | "name" | "resourceType"> & { rotationCredentialsConfigured: boolean };
credentials: TPamAccountCredentials; credentials: TPamAccountCredentials;
} }
> = []; > = [];
@@ -330,7 +381,8 @@ export const pamAccountServiceFactory = ({
resource: { resource: {
id: account.resource.id, id: account.resource.id,
name: account.resource.name, name: account.resource.name,
resourceType: account.resource.resourceType resourceType: account.resource.resourceType,
rotationCredentialsConfigured: !!account.resource.encryptedRotationAccountCredentials
} }
}); });
} }
@@ -459,13 +511,14 @@ export const pamAccountServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -516,12 +569,116 @@ export const pamAccountServiceFactory = ({
}; };
}; };
const rotateAllDueAccounts = async () => {
const accounts = await pamAccountDAL.findAccountsDueForRotation();
for (let i = 0; i < accounts.length; i += ROTATION_CONCURRENCY_LIMIT) {
const batch = accounts.slice(i, i + ROTATION_CONCURRENCY_LIMIT);
const rotationPromises = batch.map(async (account) =>
pamAccountDAL.transaction(async (tx) => {
let logResourceType = "unknown";
try {
const resource = await pamResourceDAL.findById(account.resourceId, tx);
if (!resource || !resource.encryptedRotationAccountCredentials) return;
logResourceType = resource.resourceType;
const { connectionDetails, rotationAccountCredentials, gatewayId, resourceType } = await decryptResource(
resource,
account.projectId,
kmsService
);
if (!rotationAccountCredentials) return;
const accountCredentials = await decryptAccountCredentials({
encryptedCredentials: account.encryptedCredentials,
projectId: account.projectId,
kmsService
});
const factory = PAM_RESOURCE_FACTORY_MAP[resourceType as PamResource](
resourceType as PamResource,
connectionDetails,
gatewayId,
gatewayV2Service
);
const newCredentials = await factory.rotateAccountCredentials(
rotationAccountCredentials,
accountCredentials
);
const encryptedCredentials = await encryptAccountCredentials({
credentials: newCredentials,
projectId: account.projectId,
kmsService
});
await pamAccountDAL.updateById(
account.id,
{
encryptedCredentials,
lastRotatedAt: new Date()
},
tx
);
await auditLogService.createAuditLog({
projectId: account.projectId,
actor: {
type: ActorType.PLATFORM,
metadata: {}
},
event: {
type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION,
metadata: {
accountId: account.id,
accountName: account.name,
resourceId: resource.id,
resourceType: logResourceType
}
}
});
} catch (error) {
logger.error(error, `Failed to rotate credentials for account [accountId=${account.id}]`);
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
await auditLogService.createAuditLog({
projectId: account.projectId,
actor: {
type: ActorType.PLATFORM,
metadata: {}
},
event: {
type: EventType.PAM_ACCOUNT_CREDENTIAL_ROTATION_FAILED,
metadata: {
accountId: account.id,
accountName: account.name,
resourceId: account.resourceId,
resourceType: logResourceType,
errorMessage
}
}
});
throw error; // Rollback transaction
}
})
);
// eslint-disable-next-line no-await-in-loop
await Promise.all(rotationPromises);
}
};
return { return {
create, create,
updateById, updateById,
deleteById, deleteById,
list, list,
access, access,
getSessionCredentials getSessionCredentials,
rotateAllDueAccounts
}; };
}; };
@@ -1,7 +1,10 @@
import { TPamAccount } from "../pam-resource/pam-resource-types"; import { TPamAccount } from "../pam-resource/pam-resource-types";
// DTOs // DTOs
export type TCreateAccountDTO = Pick<TPamAccount, "name" | "description" | "credentials" | "folderId" | "resourceId">; export type TCreateAccountDTO = Pick<
TPamAccount,
"name" | "description" | "credentials" | "folderId" | "resourceId" | "rotationEnabled" | "rotationIntervalSeconds"
>;
export type TUpdateAccountDTO = Partial<Omit<TCreateAccountDTO, "folderId" | "resourceId">> & { export type TUpdateAccountDTO = Partial<Omit<TCreateAccountDTO, "folderId" | "resourceId">> & {
accountId: string; accountId: string;
@@ -2,6 +2,7 @@ import { TPamResources } from "@app/db/schemas";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types"; import { KmsDataKey } from "@app/services/kms/kms-types";
import { decryptAccountCredentials } from "../pam-account/pam-account-fns";
import { TPamResource, TPamResourceConnectionDetails } from "./pam-resource-types"; import { TPamResource, TPamResourceConnectionDetails } from "./pam-resource-types";
import { getPostgresResourceListItem } from "./postgres/postgres-resource-fns"; import { getPostgresResourceListItem } from "./postgres/postgres-resource-fns";
@@ -63,6 +64,13 @@ export const decryptResource = async (
encryptedConnectionDetails: resource.encryptedConnectionDetails, encryptedConnectionDetails: resource.encryptedConnectionDetails,
projectId, projectId,
kmsService kmsService
}),
rotationAccountCredentials: resource.encryptedRotationAccountCredentials
? await decryptAccountCredentials({
encryptedCredentials: resource.encryptedRotationAccountCredentials,
projectId,
kmsService
}) })
: null
} as TPamResource; } as TPamResource;
}; };
@@ -6,6 +6,7 @@ import { slugSchema } from "@app/server/lib/schemas";
// Resources // Resources
export const BasePamResourceSchema = PamResourcesSchema.omit({ export const BasePamResourceSchema = PamResourcesSchema.omit({
encryptedConnectionDetails: true, encryptedConnectionDetails: true,
encryptedRotationAccountCredentials: true,
resourceType: true resourceType: true
}); });
@@ -30,6 +31,8 @@ export const BasePamAccountSchemaWithResource = BasePamAccountSchema.extend({
id: true, id: true,
name: true, name: true,
resourceType: true resourceType: true
}).extend({
rotationCredentialsConfigured: z.boolean()
}) })
}); });
@@ -37,10 +40,14 @@ export const BaseCreatePamAccountSchema = z.object({
resourceId: z.string().uuid(), resourceId: z.string().uuid(),
folderId: z.string().uuid().optional(), folderId: z.string().uuid().optional(),
name: slugSchema({ field: "name" }), name: slugSchema({ field: "name" }),
description: z.string().max(512).nullable().optional() description: z.string().max(512).nullable().optional(),
rotationEnabled: z.boolean(),
rotationIntervalSeconds: z.number().min(3600).nullable().optional()
}); });
export const BaseUpdatePamAccountSchema = z.object({ export const BaseUpdatePamAccountSchema = z.object({
name: slugSchema({ field: "name" }).optional(), name: slugSchema({ field: "name" }).optional(),
description: z.string().max(512).nullable().optional() description: z.string().max(512).nullable().optional(),
rotationEnabled: z.boolean().optional(),
rotationIntervalSeconds: z.number().min(3600).nullable().optional()
}); });
@@ -10,10 +10,16 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service"; import { TGatewayV2ServiceFactory } from "../gateway-v2/gateway-v2-service";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { decryptAccountCredentials, encryptAccountCredentials } from "../pam-account/pam-account-fns";
import { TPamResourceDALFactory } from "./pam-resource-dal"; import { TPamResourceDALFactory } from "./pam-resource-dal";
import { PamResource } from "./pam-resource-enums"; import { PamResource } from "./pam-resource-enums";
import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory"; import { PAM_RESOURCE_FACTORY_MAP } from "./pam-resource-factory";
import { decryptResource, encryptResourceConnectionDetails, listResourceOptions } from "./pam-resource-fns"; import {
decryptResource,
decryptResourceConnectionDetails,
encryptResourceConnectionDetails,
listResourceOptions
} from "./pam-resource-fns";
import { TCreateResourceDTO, TUpdateResourceDTO } from "./pam-resource-types"; import { TCreateResourceDTO, TUpdateResourceDTO } from "./pam-resource-types";
type TPamResourceServiceFactoryDep = { type TPamResourceServiceFactoryDep = {
@@ -61,7 +67,7 @@ export const pamResourceServiceFactory = ({
}; };
const create = async ( const create = async (
{ resourceType, connectionDetails, gatewayId, name, projectId }: TCreateResourceDTO, { resourceType, connectionDetails, gatewayId, name, projectId, rotationAccountCredentials }: TCreateResourceDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId); const orgLicensePlan = await licenseService.getPlan(actor.orgId);
@@ -88,26 +94,42 @@ export const pamResourceServiceFactory = ({
gatewayId, gatewayId,
gatewayV2Service gatewayV2Service
); );
const validatedConnectionDetails = await factory.validateConnection();
const validatedConnectionDetails = await factory.validateConnection();
const encryptedConnectionDetails = await encryptResourceConnectionDetails({ const encryptedConnectionDetails = await encryptResourceConnectionDetails({
connectionDetails: validatedConnectionDetails, connectionDetails: validatedConnectionDetails,
projectId, projectId,
kmsService kmsService
}); });
let encryptedRotationAccountCredentials: Buffer | null = null;
if (rotationAccountCredentials) {
const validatedRotationAccountCredentials = await factory.validateAccountCredentials(rotationAccountCredentials);
encryptedRotationAccountCredentials = await encryptAccountCredentials({
credentials: validatedRotationAccountCredentials,
projectId,
kmsService
});
}
const resource = await pamResourceDAL.create({ const resource = await pamResourceDAL.create({
resourceType, resourceType,
encryptedConnectionDetails, encryptedConnectionDetails,
gatewayId, gatewayId,
name, name,
projectId projectId,
encryptedRotationAccountCredentials
}); });
return decryptResource(resource, projectId, kmsService); return decryptResource(resource, projectId, kmsService);
}; };
const updateById = async ({ connectionDetails, resourceId, name }: TUpdateResourceDTO, actor: OrgServiceActor) => { const updateById = async (
{ connectionDetails, resourceId, name, rotationAccountCredentials }: TUpdateResourceDTO,
actor: OrgServiceActor
) => {
const orgLicensePlan = await licenseService.getPlan(actor.orgId); const orgLicensePlan = await licenseService.getPlan(actor.orgId);
if (!orgLicensePlan.pam) { if (!orgLicensePlan.pam) {
throw new BadRequestError({ throw new BadRequestError({
@@ -151,6 +173,60 @@ export const pamResourceServiceFactory = ({
updateDoc.encryptedConnectionDetails = encryptedConnectionDetails; updateDoc.encryptedConnectionDetails = encryptedConnectionDetails;
} }
if (rotationAccountCredentials !== undefined) {
updateDoc.encryptedRotationAccountCredentials = null;
if (rotationAccountCredentials) {
const decryptedConnectionDetails =
connectionDetails ??
(await decryptResourceConnectionDetails({
encryptedConnectionDetails: resource.encryptedConnectionDetails,
projectId: resource.projectId,
kmsService
}));
const factory = PAM_RESOURCE_FACTORY_MAP[resource.resourceType as PamResource](
resource.resourceType as PamResource,
decryptedConnectionDetails,
resource.gatewayId,
gatewayV2Service
);
// Logic to prevent overwriting unedited censored values
const finalCredentials = { ...rotationAccountCredentials };
if (
resource.encryptedRotationAccountCredentials &&
rotationAccountCredentials.password === "__INFISICAL_UNCHANGED__"
) {
const decryptedCredentials = await decryptAccountCredentials({
encryptedCredentials: resource.encryptedRotationAccountCredentials,
projectId: resource.projectId,
kmsService
});
finalCredentials.password = decryptedCredentials.password;
}
try {
const validatedRotationAccountCredentials = await factory.validateAccountCredentials(finalCredentials);
updateDoc.encryptedRotationAccountCredentials = await encryptAccountCredentials({
credentials: validatedRotationAccountCredentials,
projectId: resource.projectId,
kmsService
});
} catch (err) {
if (err instanceof BadRequestError) {
throw new BadRequestError({
message: `Rotation Account Error: ${err.message}`
});
}
throw err;
}
}
}
// If nothing was updated, return the fetched resource // If nothing was updated, return the fetched resource
if (Object.keys(updateDoc).length === 0) { if (Object.keys(updateDoc).length === 0) {
return decryptResource(resource, resource.projectId, kmsService); return decryptResource(resource, resource.projectId, kmsService);
@@ -18,7 +18,7 @@ export type TPamAccountCredentials = TPostgresAccountCredentials;
// Resource DTOs // Resource DTOs
export type TCreateResourceDTO = Pick< export type TCreateResourceDTO = Pick<
TPamResource, TPamResource,
"name" | "connectionDetails" | "resourceType" | "gatewayId" | "projectId" "name" | "connectionDetails" | "resourceType" | "gatewayId" | "projectId" | "rotationAccountCredentials"
>; >;
export type TUpdateResourceDTO = Partial<Omit<TCreateResourceDTO, "resourceType" | "projectId">> & { export type TUpdateResourceDTO = Partial<Omit<TCreateResourceDTO, "resourceType" | "projectId">> & {
@@ -30,6 +30,10 @@ export type TPamResourceFactoryValidateConnection<T extends TPamResourceConnecti
export type TPamResourceFactoryValidateAccountCredentials<C extends TPamAccountCredentials> = ( export type TPamResourceFactoryValidateAccountCredentials<C extends TPamAccountCredentials> = (
credentials: C credentials: C
) => Promise<C>; ) => Promise<C>;
export type TPamResourceFactoryRotateAccountCredentials<C extends TPamAccountCredentials> = (
rotationAccountCredentials: C,
currentCredentials: C
) => Promise<C>;
export type TPamResourceFactory<T extends TPamResourceConnectionDetails, C extends TPamAccountCredentials> = ( export type TPamResourceFactory<T extends TPamResourceConnectionDetails, C extends TPamAccountCredentials> = (
resourceType: PamResource, resourceType: PamResource,
@@ -39,4 +43,5 @@ export type TPamResourceFactory<T extends TPamResourceConnectionDetails, C exten
) => { ) => {
validateConnection: TPamResourceFactoryValidateConnection<T>; validateConnection: TPamResourceFactoryValidateConnection<T>;
validateAccountCredentials: TPamResourceFactoryValidateAccountCredentials<C>; validateAccountCredentials: TPamResourceFactoryValidateAccountCredentials<C>;
rotateAccountCredentials: TPamResourceFactoryRotateAccountCredentials<C>;
}; };
@@ -15,13 +15,24 @@ import {
BaseSqlResourceConnectionDetailsSchema BaseSqlResourceConnectionDetailsSchema
} from "../shared/sql/sql-resource-schemas"; } from "../shared/sql/sql-resource-schemas";
// Resources
export const PostgresResourceConnectionDetailsSchema = BaseSqlResourceConnectionDetailsSchema; export const PostgresResourceConnectionDetailsSchema = BaseSqlResourceConnectionDetailsSchema;
export const PostgresAccountCredentialsSchema = BaseSqlAccountCredentialsSchema;
// Resources
const BasePostgresResourceSchema = BasePamResourceSchema.extend({ resourceType: z.literal(PamResource.Postgres) }); const BasePostgresResourceSchema = BasePamResourceSchema.extend({ resourceType: z.literal(PamResource.Postgres) });
export const PostgresResourceSchema = BasePostgresResourceSchema.extend({ export const PostgresResourceSchema = BasePostgresResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema connectionDetails: PostgresResourceConnectionDetailsSchema,
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
});
export const SanitizedPostgresResourceSchema = BasePostgresResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema,
rotationAccountCredentials: PostgresAccountCredentialsSchema.pick({
username: true
})
.nullable()
.optional()
}); });
export const PostgresResourceListItemSchema = z.object({ export const PostgresResourceListItemSchema = z.object({
@@ -30,16 +41,16 @@ export const PostgresResourceListItemSchema = z.object({
}); });
export const CreatePostgresResourceSchema = BaseCreatePamResourceSchema.extend({ export const CreatePostgresResourceSchema = BaseCreatePamResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema connectionDetails: PostgresResourceConnectionDetailsSchema,
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
}); });
export const UpdatePostgresResourceSchema = BaseUpdatePamResourceSchema.extend({ export const UpdatePostgresResourceSchema = BaseUpdatePamResourceSchema.extend({
connectionDetails: PostgresResourceConnectionDetailsSchema.optional() connectionDetails: PostgresResourceConnectionDetailsSchema.optional(),
rotationAccountCredentials: PostgresAccountCredentialsSchema.nullable().optional()
}); });
// Accounts // Accounts
export const PostgresAccountCredentialsSchema = BaseSqlAccountCredentialsSchema;
export const PostgresAccountSchema = BasePamAccountSchema.extend({ export const PostgresAccountSchema = BasePamAccountSchema.extend({
credentials: PostgresAccountCredentialsSchema credentials: PostgresAccountCredentialsSchema
}); });
@@ -6,9 +6,14 @@ import { TGatewayV2ServiceFactory } from "@app/ee/services/gateway-v2/gateway-v2
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { GatewayProxyProtocol } from "@app/lib/gateway"; import { GatewayProxyProtocol } from "@app/lib/gateway";
import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2"; import { withGatewayV2Proxy } from "@app/lib/gateway-v2/gateway-v2";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { PamResource } from "../../pam-resource-enums"; import { PamResource } from "../../pam-resource-enums";
import { TPamResourceFactory, TPamResourceFactoryValidateAccountCredentials } from "../../pam-resource-types"; import {
TPamResourceFactory,
TPamResourceFactoryRotateAccountCredentials,
TPamResourceFactoryValidateAccountCredentials
} from "../../pam-resource-types";
import { TSqlAccountCredentials, TSqlResourceConnectionDetails } from "./sql-resource-types"; import { TSqlAccountCredentials, TSqlResourceConnectionDetails } from "./sql-resource-types";
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
@@ -176,8 +181,66 @@ export const sqlResourceFactory: TPamResourceFactory<TSqlResourceConnectionDetai
} }
}; };
const rotateAccountCredentials: TPamResourceFactoryRotateAccountCredentials<TSqlAccountCredentials> = async (
rotationAccountCredentials,
currentCredentials
) => {
try {
const newPassword = alphaNumericNanoId(32);
await executeWithGateway(
{
connectionDetails,
gatewayId,
resourceType,
username: rotationAccountCredentials.username,
password: rotationAccountCredentials.password
},
gatewayV2Service,
async (client) => {
switch (resourceType) {
case PamResource.Postgres:
await client.raw(`ALTER USER ?? WITH PASSWORD '${newPassword}'`, [currentCredentials.username]);
break;
default:
throw new BadRequestError({
message: `Password rotation for ${resourceType as PamResource} is not supported.`
});
}
}
);
return { username: currentCredentials.username, password: newPassword };
} catch (error) {
if (error instanceof BadRequestError) {
if (error.message === `password authentication failed for user "${rotationAccountCredentials.username}"`) {
throw new BadRequestError({
message: "Management credentials invalid: Username or password incorrect"
});
}
if (error.message.includes("permission denied")) {
throw new BadRequestError({
message: `Management credentials lack permission to rotate password for user "${currentCredentials.username}"`
});
}
if (error.message === "Connection terminated unexpectedly") {
throw new BadRequestError({
message: "Connection terminated unexpectedly. Verify that host and port are correct"
});
}
}
throw new BadRequestError({
message: `Unable to rotate account credentials for ${resourceType}: ${(error as Error).message || String(error)}`
});
}
};
return { return {
validateConnection, validateConnection,
validateAccountCredentials validateAccountCredentials,
rotateAccountCredentials
}; };
}; };
@@ -16,6 +16,6 @@ export const BaseSqlResourceConnectionDetailsSchema = z.object({
// Accounts // Accounts
export const BaseSqlAccountCredentialsSchema = z.object({ export const BaseSqlAccountCredentialsSchema = z.object({
username: z.string().trim().min(1), username: z.string().trim().min(1).max(63),
password: z.string().trim().min(1) password: z.string().trim().min(1).max(256)
}); });
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
@@ -102,13 +102,14 @@ export const pamSessionServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionGatewayActions.CreateGateways, OrgPermissionGatewayActions.CreateGateways,
@@ -142,13 +143,14 @@ export const pamSessionServiceFactory = ({
const project = await projectDAL.findById(session.projectId); const project = await projectDAL.findById(session.projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` }); if (!project) throw new NotFoundError({ message: `Project with ID '${session.projectId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
project.orgId, orgId: project.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
if (actor.type === ActorType.IDENTITY) { if (actor.type === ActorType.IDENTITY) {
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -15,6 +15,11 @@ export enum OrgPermissionActions {
Delete = "delete" Delete = "delete"
} }
export enum OrgPermissionSubOrgActions {
Create = "create",
DirectAccess = "direct-access"
}
export enum OrgPermissionAppConnectionActions { export enum OrgPermissionAppConnectionActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -117,7 +122,8 @@ export enum OrgPermissionSubjects {
Kmip = "kmip", Kmip = "kmip",
Gateway = "gateway", Gateway = "gateway",
Relay = "relay", Relay = "relay",
SecretShare = "secret-share" SecretShare = "secret-share",
SubOrganization = "sub-organization"
} }
export type AppConnectionSubjectFields = { export type AppConnectionSubjectFields = {
@@ -128,6 +134,7 @@ export type OrgPermissionSet =
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace] | [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
| [OrgPermissionActions.Create, OrgPermissionSubjects.Project] | [OrgPermissionActions.Create, OrgPermissionSubjects.Project]
| [OrgPermissionActions, OrgPermissionSubjects.Role] | [OrgPermissionActions, OrgPermissionSubjects.Role]
| [OrgPermissionSubOrgActions, OrgPermissionSubjects.SubOrganization]
| [OrgPermissionActions, OrgPermissionSubjects.Member] | [OrgPermissionActions, OrgPermissionSubjects.Member]
| [OrgPermissionActions, OrgPermissionSubjects.Settings] | [OrgPermissionActions, OrgPermissionSubjects.Settings]
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount] | [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
@@ -185,6 +192,12 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."), subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.") action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
}), }),
z.object({
subject: z.literal(OrgPermissionSubjects.SubOrganization).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSubOrgActions).describe(
"Describe what action an entity can take."
)
}),
z.object({ z.object({
subject: z.literal(OrgPermissionSubjects.Member).describe("The entity this permission pertains to."), subject: z.literal(OrgPermissionSubjects.Member).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.") action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
@@ -308,6 +321,10 @@ const buildAdminPermission = () => {
// ws permissions // ws permissions
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Project); can(OrgPermissionActions.Create, OrgPermissionSubjects.Project);
can(OrgPermissionSubOrgActions.Create, OrgPermissionSubjects.SubOrganization);
can(OrgPermissionSubOrgActions.DirectAccess, OrgPermissionSubjects.SubOrganization);
// role permission // role permission
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role); can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
can(OrgPermissionActions.Create, OrgPermissionSubjects.Role); can(OrgPermissionActions.Create, OrgPermissionSubjects.Role);
@@ -19,6 +19,7 @@ interface TPermissionDataReturn extends TMemberships {
orgAuthEnforced?: boolean | null; orgAuthEnforced?: boolean | null;
orgGoogleSsoAuthEnforced?: boolean | null; orgGoogleSsoAuthEnforced?: boolean | null;
shouldUseNewPrivilegeSystem?: boolean | null; shouldUseNewPrivilegeSystem?: boolean | null;
rootOrgId?: string | null;
bypassOrgAuthEnabled?: boolean | null; bypassOrgAuthEnabled?: boolean | null;
roles: { roles: {
id: string; id: string;
@@ -273,7 +274,8 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization), db.ref("shouldUseNewPrivilegeSystem").withSchema(TableName.Organization),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"), db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"), db.ref("googleSsoAuthEnforced").withSchema(TableName.Organization).as("orgGoogleSsoAuthEnforced"),
db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled") db.ref("bypassOrgAuthEnabled").withSchema(TableName.Organization).as("bypassOrgAuthEnabled"),
db.ref("rootOrgId").withSchema(TableName.Organization).as("rootOrgId")
); );
const data = sqlNestRelationships({ const data = sqlNestRelationships({
@@ -283,6 +285,7 @@ export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
MembershipsSchema.extend({ MembershipsSchema.extend({
orgAuthEnforced: z.boolean().optional().nullable(), orgAuthEnforced: z.boolean().optional().nullable(),
shouldUseNewPrivilegeSystem: z.boolean().optional().nullable(), shouldUseNewPrivilegeSystem: z.boolean().optional().nullable(),
rootOrgId: z.string().optional().nullable(),
orgGoogleSsoAuthEnforced: z.boolean(), orgGoogleSsoAuthEnforced: z.boolean(),
bypassOrgAuthEnabled: z.boolean() bypassOrgAuthEnabled: z.boolean()
}).parse(el), }).parse(el),
@@ -2,7 +2,7 @@ import { MongoAbility } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js"; import { MongoQuery } from "@ucast/mongo2js";
import { Knex } from "knex"; import { Knex } from "knex";
import { ActionProjectType, TMemberships } from "@app/db/schemas"; import { ActionProjectType, OrganizationActionScope, TMemberships } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission"; import { OrgPermissionSet } from "./org-permission";
@@ -18,21 +18,6 @@ export type TBuildOrgPermissionDTO = {
role: string; role: string;
}[]; }[];
export type TGetUserProjectPermissionArg = {
userId: string;
projectId: string;
authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string;
};
export type TGetIdentityProjectPermissionArg = {
identityId: string;
projectId: string;
identityOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetServiceTokenProjectPermissionArg = { export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string; serviceTokenId: string;
projectId: string; projectId: string;
@@ -54,17 +39,12 @@ export type TGetOrgPermissionArg = {
actorId: string; actorId: string;
orgId: string; orgId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId?: string; actorOrgId: string;
scope: OrganizationActionScope;
}; };
export type TPermissionServiceFactory = { export type TPermissionServiceFactory = {
getOrgPermission: ( getOrgPermission: (arg: TGetOrgPermissionArg) => Promise<{
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>; permission: MongoAbility<OrgPermissionSet, MongoQuery>;
memberships: Array< memberships: Array<
TMemberships & { TMemberships & {
@@ -7,6 +7,7 @@ import { Knex } from "knex";
import { import {
AccessScope, AccessScope,
ActionProjectType, ActionProjectType,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
ProjectMembershipRole, ProjectMembershipRole,
ServiceTokenScopes ServiceTokenScopes
@@ -179,14 +180,15 @@ export const permissionServiceFactory = ({
// return minTtl; // return minTtl;
// }; // };
const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async ( const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async ({
type, actor,
id, actorId,
orgId, orgId,
authMethod, actorOrgId,
actorOrgId scope,
) => { actorAuthMethod
if (type !== ActorType.USER && type !== ActorType.IDENTITY) { }) => {
if (actor !== ActorType.USER && actor !== ActorType.IDENTITY) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid actor provided", message: "Invalid actor provided",
name: "Get org permission" name: "Get org permission"
@@ -202,11 +204,19 @@ export const permissionServiceFactory = ({
scope: AccessScope.Organization, scope: AccessScope.Organization,
orgId orgId
}, },
actorId: id, actorId,
actorType: type actorType: actor
}); });
if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" }); if (!permissionData?.length) throw new ForbiddenRequestError({ name: "You are not member of this organization" });
const rootOrgId = permissionData?.[0]?.rootOrgId;
const isChild = Boolean(rootOrgId);
if (scope === OrganizationActionScope.ParentOrganization && isChild) {
throw new ForbiddenRequestError({ message: `Child organization cannot do this operation` });
} else if (scope === OrganizationActionScope.ChildOrganization && !isChild) {
throw new ForbiddenRequestError({ message: `Parent organization cannot do this operation` });
}
const permissionFromRoles = permissionData.flatMap((membership) => { const permissionFromRoles = permissionData.flatMap((membership) => {
const activeRoles = membership?.roles const activeRoles = membership?.roles
.filter( .filter(
@@ -227,7 +237,7 @@ export const permissionServiceFactory = ({
permissionData.some((memberships) => memberships.roles.some((el) => role === (el.customRoleSlug || el.role))); permissionData.some((memberships) => memberships.roles.some((el) => role === (el.customRoleSlug || el.role)));
validateOrgSSO( validateOrgSSO(
authMethod, actorAuthMethod,
permissionData?.[0].orgAuthEnforced, permissionData?.[0].orgAuthEnforced,
Boolean(permissionData?.[0].orgGoogleSsoAuthEnforced), Boolean(permissionData?.[0].orgGoogleSsoAuthEnforced),
Boolean(permissionData?.[0].bypassOrgAuthEnabled), Boolean(permissionData?.[0].bypassOrgAuthEnabled),
@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { ProjectType, TProjectTemplates } from "@app/db/schemas"; import { OrganizationActionScope, ProjectType, TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -59,13 +59,14 @@ export const projectTemplateServiceFactory = ({
message: "Failed to access project templates due to plan restriction. Upgrade plan to access project templates." message: "Failed to access project templates due to plan restriction. Upgrade plan to access project templates."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -97,13 +98,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with Name "${name}"` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with Name "${name}"` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -125,13 +127,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
@@ -152,13 +155,14 @@ export const projectTemplateServiceFactory = ({
message: "Failed to create project template due to plan restriction. Upgrade plan to access project templates." message: "Failed to create project template due to plan restriction. Upgrade plan to access project templates."
}); });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
actor.orgId, orgId: actor.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
@@ -213,13 +217,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments) if (projectTemplate.type !== ProjectType.SecretManager && environments)
@@ -272,13 +277,14 @@ export const projectTemplateServiceFactory = ({
if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` }); if (!projectTemplate) throw new NotFoundError({ message: `Could not find project template with ID ${id}` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
actor.type, actor: actor.type,
actor.id, actorId: actor.id,
projectTemplate.orgId, orgId: projectTemplate.orgId,
actor.authMethod, actorAuthMethod: actor.authMethod,
actor.orgId actorOrgId: actor.orgId,
); scope: OrganizationActionScope.Any
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
@@ -2,7 +2,7 @@ import { z } from "zod";
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { OrgServiceActor } from "@app/lib/types"; import { ProjectServiceActor } from "@app/lib/types";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">; export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">;
@@ -31,7 +31,7 @@ export enum InfisicalProjectTemplate {
export type TProjectTemplateServiceFactory = { export type TProjectTemplateServiceFactory = {
listProjectTemplatesByOrg: ( listProjectTemplatesByOrg: (
actor: OrgServiceActor, actor: ProjectServiceActor,
type?: ProjectType type?: ProjectType
) => Promise< ) => Promise<
( (
@@ -85,7 +85,7 @@ export type TProjectTemplateServiceFactory = {
>; >;
createProjectTemplate: ( createProjectTemplate: (
arg: TCreateProjectTemplateDTO, arg: TCreateProjectTemplateDTO,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -109,7 +109,7 @@ export type TProjectTemplateServiceFactory = {
updateProjectTemplateById: ( updateProjectTemplateById: (
id: string, id: string,
{ roles, environments, ...params }: TUpdateProjectTemplateDTO, { roles, environments, ...params }: TUpdateProjectTemplateDTO,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -132,7 +132,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
deleteProjectTemplateById: ( deleteProjectTemplateById: (
id: string, id: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
roles: { roles: {
@@ -155,7 +155,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
findProjectTemplateById: ( findProjectTemplateById: (
id: string, id: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
packedRoles: TProjectTemplateRole[]; packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
@@ -179,7 +179,7 @@ export type TProjectTemplateServiceFactory = {
}>; }>;
findProjectTemplateByName: ( findProjectTemplateByName: (
name: string, name: string,
actor: OrgServiceActor actor: ProjectServiceActor
) => Promise<{ ) => Promise<{
packedRoles: TProjectTemplateRole[]; packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
+25 -21
View File
@@ -3,7 +3,7 @@ import { isIP } from "node:net";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { OrgMembershipRole, TRelays } from "@app/db/schemas"; import { OrganizationActionScope, OrgMembershipRole, TRelays } from "@app/db/schemas";
import { PgSqlLock } from "@app/keystore/keystore"; import { PgSqlLock } from "@app/keystore/keystore";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -126,8 +126,8 @@ export const relayServiceFactory = ({
// generate instance relay CA // generate instance relay CA
const instanceRelayCaSerialNumber = createSerialNumber(); const instanceRelayCaSerialNumber = createSerialNumber();
const instanceRelayCaIssuedAt = new Date();
const instanceRelayCaExpiration = new Date(new Date().setFullYear(2045)); const instanceRelayCaExpiration = new Date(new Date().setFullYear(2045));
const instanceRelayCaIssuedAt = new Date();
const instanceRelayCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]); const instanceRelayCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const instanceRelayCaSkObj = crypto.nativeCrypto.KeyObject.from(instanceRelayCaKeys.privateKey); const instanceRelayCaSkObj = crypto.nativeCrypto.KeyObject.from(instanceRelayCaKeys.privateKey);
const instanceRelayCaCert = await x509.X509CertificateGenerator.create({ const instanceRelayCaCert = await x509.X509CertificateGenerator.create({
@@ -972,13 +972,14 @@ export const relayServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityId, actor: ActorType.IDENTITY,
actorId: identityId,
orgId, orgId,
actorAuthMethod!, actorAuthMethod: actorAuthMethod!,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionRelayActions.CreateRelays, OrgPermissionRelayActions.CreateRelays,
@@ -1102,13 +1103,14 @@ export const relayServiceFactory = ({
}); });
} }
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
ActorType.IDENTITY, scope: OrganizationActionScope.Any,
identityId, actor: ActorType.IDENTITY,
actorId: identityId,
orgId, orgId,
actorAuthMethod!, actorAuthMethod: actorAuthMethod!,
orgId actorOrgId: orgId
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionRelayActions.CreateRelays, OrgPermissionRelayActions.CreateRelays,
OrgPermissionSubjects.Relay OrgPermissionSubjects.Relay
@@ -1155,13 +1157,14 @@ export const relayServiceFactory = ({
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
}) => { }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod: actorAuthMethod!,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.ListRelays, OrgPermissionSubjects.Relay);
@@ -1189,13 +1192,14 @@ export const relayServiceFactory = ({
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
}) => { }) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
actorOrgId, orgId: actorOrgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionRelayActions.DeleteRelays, OrgPermissionSubjects.Relay);
@@ -5,6 +5,7 @@ import RE2 from "re2";
import { import {
AccessScope, AccessScope,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
OrgMembershipStatus, OrgMembershipStatus,
TableName, TableName,
@@ -251,7 +252,14 @@ export const samlConfigServiceFactory = ({
authProvider, authProvider,
enableGroupSync enableGroupSync
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -317,7 +325,14 @@ export const samlConfigServiceFactory = ({
authProvider, authProvider,
enableGroupSync enableGroupSync
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (!plan.samlSSO) if (!plan.samlSSO)
@@ -393,7 +408,7 @@ export const samlConfigServiceFactory = ({
}); });
} }
} else if (dto.type === "orgSlug") { } else if (dto.type === "orgSlug") {
const org = await orgDAL.findOne({ slug: dto.orgSlug }); const org = await orgDAL.findOne({ slug: dto.orgSlug, rootOrgId: null });
if (!org) { if (!org) {
throw new NotFoundError({ throw new NotFoundError({
message: `Organization with slug '${dto.orgSlug}' not found` message: `Organization with slug '${dto.orgSlug}' not found`
@@ -424,13 +439,14 @@ export const samlConfigServiceFactory = ({
// when dto is type id means it's internally used // when dto is type id means it's internally used
if (dto.type === "org") { if (dto.type === "org") {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
dto.actor, scope: OrganizationActionScope.ParentOrganization,
dto.actorId, actor: dto.actor,
samlConfig.orgId, actorId: dto.actorId,
dto.actorAuthMethod, orgId: samlConfig.orgId,
dto.actorOrgId actorAuthMethod: dto.actorAuthMethod,
); actorOrgId: dto.actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Sso);
} }
const { decryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor } = await kmsService.createCipherPairWithDataKey({
@@ -37,7 +37,7 @@ export type TGetSamlCfgDTO =
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined; actorOrgId: string;
} }
| { | {
type: "orgSlug"; type: "orgSlug";
+22 -5
View File
@@ -4,6 +4,7 @@ import { scimPatch } from "scim-patch";
import { import {
AccessScope, AccessScope,
OrganizationActionScope,
OrgMembershipRole, OrgMembershipRole,
OrgMembershipStatus, OrgMembershipStatus,
TableName, TableName,
@@ -56,6 +57,7 @@ type TScimServiceFactoryDep = {
TOrgDALFactory, TOrgDALFactory,
| "createMembership" | "createMembership"
| "findById" | "findById"
| "find"
| "findMembership" | "findMembership"
| "findMembershipWithScimFilter" | "findMembershipWithScimFilter"
| "deleteMembershipById" | "deleteMembershipById"
@@ -125,7 +127,14 @@ export const scimServiceFactory = ({
description, description,
ttlDays ttlDays
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -160,7 +169,14 @@ export const scimServiceFactory = ({
actorAuthMethod, actorAuthMethod,
orgId orgId
}) => { }) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
@@ -183,13 +199,14 @@ export const scimServiceFactory = ({
let scimToken = await scimDAL.findById(scimTokenId); let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` }); if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.ParentOrganization,
actor, actor,
actorId, actorId,
scimToken.orgId, orgId: scimToken.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
const plan = await licenseService.getPlan(scimToken.orgId); const plan = await licenseService.getPlan(scimToken.orgId);
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import { WebhookEventMap } from "@octokit/webhooks-types"; import { WebhookEventMap } from "@octokit/webhooks-types";
import { ProbotOctokit } from "probot"; import { ProbotOctokit } from "probot";
import { OrganizationActionScope } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -49,7 +50,14 @@ export const secretScanningServiceFactory = ({
}: TInstallAppSessionDTO) => { }: TInstallAppSessionDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const sessionId = crypto.randomBytes(16).toString("hex"); const sessionId = crypto.randomBytes(16).toString("hex");
@@ -68,13 +76,14 @@ export const secretScanningServiceFactory = ({
const session = await gitAppInstallSessionDAL.findOne({ sessionId }); const session = await gitAppInstallSessionDAL.findOne({ sessionId });
if (!session) throw new NotFoundError({ message: "Session was not found" }); if (!session) throw new NotFoundError({ message: "Session was not found" });
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor, actor,
actorId, actorId,
session.orgId, orgId: session.orgId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
); });
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
const installatedApp = await gitAppOrgDAL.transaction(async (tx) => { const installatedApp = await gitAppOrgDAL.transaction(async (tx) => {
await gitAppInstallSessionDAL.deleteById(session.id, tx); await gitAppInstallSessionDAL.deleteById(session.id, tx);
@@ -117,7 +126,14 @@ export const secretScanningServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TGetOrgInstallStatusDTO) => { }: TGetOrgInstallStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const appInstallation = await gitAppOrgDAL.findOne({ orgId }); const appInstallation = await gitAppOrgDAL.findOne({ orgId });
@@ -125,7 +141,14 @@ export const secretScanningServiceFactory = ({
}; };
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => { const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const results = await secretScanningDAL.findByOrgId(orgId, filter); const results = await secretScanningDAL.findByOrgId(orgId, filter);
@@ -134,7 +157,14 @@ export const secretScanningServiceFactory = ({
}; };
const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => { const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] }); const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
@@ -150,7 +180,14 @@ export const secretScanningServiceFactory = ({
riskId, riskId,
status status
}: TUpdateRiskStatusDTO) => { }: TUpdateRiskStatusDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission({
scope: OrganizationActionScope.Any,
actor,
actorId,
orgId,
actorAuthMethod,
actorOrgId
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
const isRiskResolved = Boolean( const isRiskResolved = Boolean(
@@ -0,0 +1,160 @@
import { ForbiddenError } from "@casl/ability";
import { AccessScope, OrganizationActionScope, OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TMembershipDALFactory } from "@app/services/membership/membership-dal";
import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects, OrgPermissionSubOrgActions } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TCreateSubOrgDTO, TListSubOrgDTO, TUpdateSubOrgDTO } from "./sub-org-types";
type TSubOrgServiceFactoryDep = {
orgDAL: Pick<
TOrgDALFactory,
"findOne" | "create" | "transaction" | "listSubOrganizations" | "updateById" | "findById"
>;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
membershipDAL: Pick<TMembershipDALFactory, "create">;
membershipRoleDAL: Pick<TMembershipRoleDALFactory, "create">;
};
export type TSubOrgServiceFactory = ReturnType<typeof subOrgServiceFactory>;
export const subOrgServiceFactory = ({
orgDAL,
permissionService,
licenseService,
membershipDAL,
membershipRoleDAL
}: TSubOrgServiceFactoryDep) => {
const createSubOrg = async ({ name, permissionActor }: TCreateSubOrgDTO) => {
const { permission } = await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: permissionActor.orgId,
actorOrgId: permissionActor.orgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.ParentOrganization
});
ForbiddenError.from(permission).throwUnlessCan(
OrgPermissionSubOrgActions.Create,
OrgPermissionSubjects.SubOrganization
);
const orgLicensePlan = await licenseService.getPlan(permissionActor.rootOrgId);
if (!orgLicensePlan.subOrganization) {
throw new BadRequestError({
message: "Sub-organization creation failed. Please upgrade your instance to Infisical's Enterprise plan."
});
}
const existingSubOrg = await orgDAL.findOne({
parentOrgId: permissionActor.orgId,
name
});
if (existingSubOrg) {
throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` });
}
const organization = await orgDAL.transaction(async (tx) => {
const org = await orgDAL.create(
{ name, slug: name, rootOrgId: permissionActor.rootOrgId, parentOrgId: permissionActor.orgId },
tx
);
const membership = await membershipDAL.create(
{
scope: AccessScope.Organization,
[permissionActor.type === ActorType.IDENTITY ? "actorIdentityId" : "actorUserId"]: permissionActor.id,
scopeOrgId: org.id,
status: OrgMembershipStatus.Accepted,
isActive: true
},
tx
);
await membershipRoleDAL.create(
{
membershipId: membership.id,
role: OrgMembershipRole.Admin
},
tx
);
return org;
});
return {
organization
};
};
const listSubOrgs = async ({ permissionActor, data }: TListSubOrgDTO) => {
await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: permissionActor.rootOrgId,
actorOrgId: permissionActor.rootOrgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.Any
});
const organizations = await orgDAL.listSubOrganizations({
actorId: permissionActor.id,
actorType: permissionActor.type,
orgId: permissionActor.rootOrgId,
isAccessible: data?.isAccessible,
limit: data?.limit,
offset: data?.offset
});
return {
organizations
};
};
const updateSubOrg = async ({ subOrgId, name, permissionActor }: TUpdateSubOrgDTO) => {
const subOrg = await orgDAL.findOne({
rootOrgId: permissionActor.rootOrgId,
id: subOrgId
});
if (!subOrg) {
throw new BadRequestError({ message: "Sub-organization not found" });
}
const { permission } = await permissionService.getOrgPermission({
actorId: permissionActor.id,
actor: permissionActor.type,
orgId: subOrgId,
actorOrgId: subOrgId,
actorAuthMethod: permissionActor.authMethod,
scope: OrganizationActionScope.ChildOrganization
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
const existingSubOrg = await orgDAL.findOne({
parentOrgId: subOrg.parentOrgId,
slug: name
});
if (existingSubOrg && existingSubOrg.id !== subOrgId) {
throw new BadRequestError({ message: `Sub-organization with name ${name} already exists` });
}
const organization = await orgDAL.updateById(subOrgId, { name, slug: name });
return {
organization
};
};
return {
createSubOrg,
listSubOrgs,
updateSubOrg
};
};
@@ -0,0 +1,22 @@
import { OrgServiceActor } from "@app/lib/types";
export type TCreateSubOrgDTO = {
name: string;
permissionActor: OrgServiceActor;
};
export type TListSubOrgDTO = {
permissionActor: OrgServiceActor;
data: Partial<{
limit?: number;
offset?: number;
search?: string;
isAccessible?: boolean;
}>;
};
export type TUpdateSubOrgDTO = {
subOrgId: string;
name: string;
permissionActor: OrgServiceActor;
};
+16
View File
@@ -33,6 +33,7 @@ export enum ApiDocsTags {
LdapAuth = "LDAP Auth", LdapAuth = "LDAP Auth",
Groups = "Groups", Groups = "Groups",
Organizations = "Organizations", Organizations = "Organizations",
SubOrganizations = "Sub Organizations",
Projects = "Projects", Projects = "Projects",
ProjectUsers = "Project Users", ProjectUsers = "Project Users",
ProjectGroups = "Project Groups", ProjectGroups = "Project Groups",
@@ -717,6 +718,21 @@ export const ORGANIZATIONS = {
} }
} as const; } as const;
export const SUB_ORGANIZATIONS = {
CREATE: {
name: "The name of the sub organization to create."
},
UPDATE: {
name: "The name of the sub organization to update.",
subOrgId: "The id of the sub organization to update."
},
LIST: {
limit: "The number of sub organizations to return.",
offset: "The offset to start from. If you enter 10, it will start from the 10th sub organization.",
isAccessible: "Filter to only return sub organizations that the actor has access to."
}
} as const;
export const PROJECTS = { export const PROJECTS = {
CREATE: { CREATE: {
organizationSlug: "The slug of the organization to create the project in.", organizationSlug: "The slug of the organization to create the project in.",
+25 -7
View File
@@ -1,5 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { QueueWorkerProfile } from "@app/lib/types"; import { QueueWorkerProfile } from "@app/lib/types";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
@@ -8,6 +9,7 @@ import { BadRequestError } from "../errors";
import { removeTrailingSlash } from "../fn"; import { removeTrailingSlash } from "../fn";
import { CustomLogger } from "../logger/logger"; import { CustomLogger } from "../logger/logger";
import { zpStr } from "../zod"; import { zpStr } from "../zod";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
export const GITLAB_URL = "https://gitlab.com"; export const GITLAB_URL = "https://gitlab.com";
@@ -363,11 +365,6 @@ const envSchema = z
/* INTERNAL ----------------------------------------------------------------------------- */ /* INTERNAL ----------------------------------------------------------------------------- */
INTERNAL_REGION: zpStr(z.enum(["us", "eu"]).optional()) INTERNAL_REGION: zpStr(z.enum(["us", "eu"]).optional())
}) })
// To ensure that basic encryption is always possible.
.refine(
(data) => Boolean(data.ENCRYPTION_KEY) || Boolean(data.ROOT_ENCRYPTION_KEY),
"Either ENCRYPTION_KEY or ROOT_ENCRYPTION_KEY must be defined."
)
.refine( .refine(
(data) => Boolean(data.REDIS_URL) || Boolean(data.REDIS_SENTINEL_HOSTS) || Boolean(data.REDIS_CLUSTER_HOSTS), (data) => Boolean(data.REDIS_URL) || Boolean(data.REDIS_SENTINEL_HOSTS) || Boolean(data.REDIS_CLUSTER_HOSTS),
"Either REDIS_URL, REDIS_SENTINEL_HOSTS or REDIS_CLUSTER_HOSTS must be defined." "Either REDIS_URL, REDIS_SENTINEL_HOSTS or REDIS_CLUSTER_HOSTS must be defined."
@@ -453,7 +450,12 @@ export const getConfig = () => envCfg;
export const getOriginalConfig = () => originalEnvConfig; export const getOriginalConfig = () => originalEnvConfig;
// cannot import singleton logger directly as it needs config to load various transport // cannot import singleton logger directly as it needs config to load various transport
export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logger?: CustomLogger) => { export const initEnvConfig = async (
hsmService: THsmServiceFactory,
kmsRootConfigDAL: TKmsRootConfigDALFactory,
superAdminDAL?: TSuperAdminDALFactory,
logger?: CustomLogger
) => {
const parsedEnv = envSchema.safeParse(process.env); const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) { if (!parsedEnv.success) {
(logger ?? console).error("Invalid environment variables. Check the error below"); (logger ?? console).error("Invalid environment variables. Check the error below");
@@ -469,7 +471,7 @@ export const initEnvConfig = async (superAdminDAL?: TSuperAdminDALFactory, logge
} }
if (superAdminDAL) { if (superAdminDAL) {
const fipsEnabled = await crypto.initialize(superAdminDAL); const fipsEnabled = await crypto.initialize(superAdminDAL, hsmService, kmsRootConfigDAL);
if (fipsEnabled) { if (fipsEnabled) {
const newEnvCfg = { const newEnvCfg = {
@@ -532,6 +534,22 @@ export const getDatabaseCredentials = (logger?: CustomLogger) => {
}; };
}; };
export const getHsmConfig = (logger?: CustomLogger) => {
const parsedEnv = envSchema.safeParse(process.env);
if (!parsedEnv.success) {
(logger ?? console).error("Invalid environment variables. Check the error below");
(logger ?? console).error(parsedEnv.error.issues);
process.exit(-1);
}
return {
isHsmConfigured: parsedEnv.data.isHsmConfigured,
HSM_PIN: parsedEnv.data.HSM_PIN,
HSM_SLOT: parsedEnv.data.HSM_SLOT,
HSM_LIB_PATH: parsedEnv.data.HSM_LIB_PATH,
HSM_KEY_LABEL: parsedEnv.data.HSM_KEY_LABEL
};
};
// A list of environment variables that can be overwritten // A list of environment variables that can be overwritten
export const overwriteSchema: { export const overwriteSchema: {
[key: string]: { [key: string]: {
+49 -6
View File
@@ -9,7 +9,11 @@ import nacl from "tweetnacl";
import naclUtils from "tweetnacl-util"; import naclUtils from "tweetnacl-util";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas"; import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { isHsmActiveAndEnabled } from "@app/ee/services/hsm/hsm-fns";
import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service";
@@ -106,13 +110,31 @@ const cryptographyFactory = () => {
} }
}; };
const $setFipsModeEnabled = (enabled: boolean, envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">) => { const $setFipsModeEnabled = async (
enabled: boolean,
hsmService: THsmServiceFactory,
kmsRootConfigDAL: TKmsRootConfigDALFactory,
envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">
) => {
// If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key.
if (enabled) { if (enabled) {
crypto.setFips(true); crypto.setFips(true);
const appCfg = envCfg || getConfig(); const appCfg = envCfg || getConfig();
const hsmStatus = await isHsmActiveAndEnabled({
hsmService,
kmsRootConfigDAL
});
// if the encryption strategy is software - user needs to provide an encryption key
// if the encryption strategy is null AND the hsm is not configured - user needs to provide an encryption key
const needsEncryptionKey =
hsmStatus.rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.Software ||
(hsmStatus.rootKmsConfigEncryptionStrategy === null && !hsmStatus.isHsmConfigured);
// only perform encryption key validation if it's actually required.
if (needsEncryptionKey) {
if (appCfg.ENCRYPTION_KEY) { if (appCfg.ENCRYPTION_KEY) {
// we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key // we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key
@@ -137,18 +159,24 @@ const cryptographyFactory = () => {
}); });
} }
} }
}
$fipsEnabled = enabled; $fipsEnabled = enabled;
$isInitialized = true; $isInitialized = true;
}; };
const initialize = async (superAdminDAL: TSuperAdminDALFactory, envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">) => { const initialize = async (
superAdminDAL: TSuperAdminDALFactory,
hsmService: THsmServiceFactory,
kmsRootConfigDAL: TKmsRootConfigDALFactory,
envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">
) => {
if ($isInitialized) { if ($isInitialized) {
return isFipsModeEnabled(); return isFipsModeEnabled();
} }
if (process.env.FIPS_ENABLED !== "true") { if (process.env.FIPS_ENABLED !== "true") {
logger.info("Cryptography module initialized in normal operation mode."); logger.info("Cryptography module initialized in normal operation mode.");
$setFipsModeEnabled(false, envCfg); await $setFipsModeEnabled(false, hsmService, kmsRootConfigDAL, envCfg);
return false; return false;
} }
@@ -158,11 +186,11 @@ const cryptographyFactory = () => {
if (serverCfg) { if (serverCfg) {
if (serverCfg.fipsEnabled) { if (serverCfg.fipsEnabled) {
logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled."); logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled.");
$setFipsModeEnabled(true, envCfg); await $setFipsModeEnabled(true, hsmService, kmsRootConfigDAL, envCfg);
return true; return true;
} }
logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS."); logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS.");
$setFipsModeEnabled(false, envCfg); await $setFipsModeEnabled(false, hsmService, kmsRootConfigDAL, envCfg);
return false; return false;
} }
@@ -171,7 +199,7 @@ const cryptographyFactory = () => {
// TODO(daniel): check if it's an enterprise deployment // TODO(daniel): check if it's an enterprise deployment
// if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true. // if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true.
$setFipsModeEnabled(true, envCfg); await $setFipsModeEnabled(true, hsmService, kmsRootConfigDAL, envCfg);
return true; return true;
}; };
@@ -258,6 +286,13 @@ const cryptographyFactory = () => {
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY; const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
const encryptionKey = appCfg.ENCRYPTION_KEY; const encryptionKey = appCfg.ENCRYPTION_KEY;
// Sanity check
if (!rootEncryptionKey && !encryptionKey) {
throw new CryptographyError({
message: "Tried to encrypt with instance root encryption key, but no root encryption key is set."
});
}
if (rootEncryptionKey) { if (rootEncryptionKey) {
const { iv, tag, ciphertext } = encrypt({ const { iv, tag, ciphertext } = encrypt({
plaintext: data, plaintext: data,
@@ -303,6 +338,14 @@ const cryptographyFactory = () => {
// the or gate is used used in migration // the or gate is used used in migration
const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY; const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY; const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
// Sanity check
if (!rootEncryptionKey && !encryptionKey) {
throw new CryptographyError({
message: "Tried to decrypt with instance root encryption key, but no root encryption key is set."
});
}
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) { if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
const data = symmetric().decrypt({ const data = symmetric().decrypt({
key: rootEncryptionKey, key: rootEncryptionKey,
+10 -1
View File
@@ -5,7 +5,7 @@ export type TGenericPermission = {
actor: ActorType; actor: ActorType;
actorId: string; actorId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined; actorOrgId: string;
}; };
/** /**
@@ -78,6 +78,15 @@ export type OrgServiceActor = {
id: string; id: string;
authMethod: ActorAuthMethod; authMethod: ActorAuthMethod;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
};
export type ProjectServiceActor = {
type: ActorType;
id: string;
authMethod: ActorAuthMethod;
orgId: string;
}; };
export enum QueueWorkerProfile { export enum QueueWorkerProfile {
+19 -6
View File
@@ -9,14 +9,16 @@ import { keyValueStoreDALFactory } from "@app/keystore/key-value-store-dal";
import { runMigrations } from "./auto-start-migrations"; import { runMigrations } from "./auto-start-migrations";
import { initAuditLogDbConnection, initDbConnection } from "./db"; import { initAuditLogDbConnection, initDbConnection } from "./db";
import { hsmServiceFactory } from "./ee/services/hsm/hsm-service";
import { keyStoreFactory } from "./keystore/keystore"; import { keyStoreFactory } from "./keystore/keystore";
import { formatSmtpConfig, getDatabaseCredentials, initEnvConfig } from "./lib/config/env"; import { formatSmtpConfig, getDatabaseCredentials, getHsmConfig, initEnvConfig } from "./lib/config/env";
import { buildRedisFromConfig } from "./lib/config/redis"; import { buildRedisFromConfig } from "./lib/config/redis";
import { removeTemporaryBaseDirectory } from "./lib/files"; import { removeTemporaryBaseDirectory } from "./lib/files";
import { initLogger } from "./lib/logger"; import { initLogger } from "./lib/logger";
import { queueServiceFactory } from "./queue"; import { queueServiceFactory } from "./queue";
import { main } from "./server/app"; import { main } from "./server/app";
import { bootstrapCheck } from "./server/boot-strap-check"; import { bootstrapCheck } from "./server/boot-strap-check";
import { kmsRootConfigDALFactory } from "./services/kms/kms-root-config-dal";
import { smtpServiceFactory } from "./services/smtp/smtp-service"; import { smtpServiceFactory } from "./services/smtp/smtp-service";
import { superAdminDALFactory } from "./services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "./services/super-admin/super-admin-dal";
@@ -26,6 +28,18 @@ const run = async () => {
const logger = initLogger(); const logger = initLogger();
await removeTemporaryBaseDirectory(); await removeTemporaryBaseDirectory();
const hsmConfig = getHsmConfig(logger);
const hsmModule = initializeHsmModule(hsmConfig);
hsmModule.initialize();
const hsmService = hsmServiceFactory({
hsmModule: hsmModule.getModule(),
envConfig: hsmConfig
});
await hsmService.startService();
const databaseCredentials = getDatabaseCredentials(logger); const databaseCredentials = getDatabaseCredentials(logger);
const db = initDbConnection({ const db = initDbConnection({
@@ -35,7 +49,8 @@ const run = async () => {
}); });
const superAdminDAL = superAdminDALFactory(db); const superAdminDAL = superAdminDALFactory(db);
const envConfig = await initEnvConfig(superAdminDAL, logger); const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const envConfig = await initEnvConfig(hsmService, kmsRootConfigDAL, superAdminDAL, logger);
const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI const auditLogDb = envConfig.AUDIT_LOGS_DB_CONNECTION_URI
? initAuditLogDbConnection({ ? initAuditLogDbConnection({
@@ -59,14 +74,12 @@ const run = async () => {
const keyStore = keyStoreFactory(envConfig, keyValueStoreDAL); const keyStore = keyStoreFactory(envConfig, keyValueStoreDAL);
const redis = buildRedisFromConfig(envConfig); const redis = buildRedisFromConfig(envConfig);
const hsmModule = initializeHsmModule(envConfig);
hsmModule.initialize();
const server = await main({ const server = await main({
db, db,
auditLogDb, auditLogDb,
superAdminDAL, superAdminDAL,
hsmModule: hsmModule.getModule(), kmsRootConfigDAL,
hsmService,
smtp, smtp,
logger, logger,
queue, queue,
+8 -2
View File
@@ -77,7 +77,8 @@ export enum QueueName {
DailyReminders = "daily-reminders", DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration", SecretReminderMigration = "secret-reminder-migration",
UserNotification = "user-notification", UserNotification = "user-notification",
HealthAlert = "health-alert" HealthAlert = "health-alert",
PamAccountRotation = "pam-account-rotation"
} }
export enum QueueJobs { export enum QueueJobs {
@@ -126,7 +127,8 @@ export enum QueueJobs {
DailyReminders = "daily-reminders", DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration", SecretReminderMigration = "secret-reminder-migration",
UserNotification = "user-notification-job", UserNotification = "user-notification-job",
HealthAlert = "health-alert" HealthAlert = "health-alert",
PamAccountRotation = "pam-account-rotation"
} }
export type TQueueJobTypes = { export type TQueueJobTypes = {
@@ -357,6 +359,10 @@ export type TQueueJobTypes = {
name: QueueJobs.HealthAlert; name: QueueJobs.HealthAlert;
payload: undefined; payload: undefined;
}; };
[QueueName.PamAccountRotation]: {
name: QueueJobs.PamAccountRotation;
payload: undefined;
};
}; };
const SECRET_SCANNING_JOBS = [ const SECRET_SCANNING_JOBS = [
+10 -6
View File
@@ -15,12 +15,13 @@ import fastify from "fastify";
import { Cluster, Redis } from "ioredis"; import { Cluster, Redis } from "ioredis";
import { Knex } from "knex"; import { Knex } from "knex";
import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env"; import { getConfig, IS_PACKAGED, TEnvConfig } from "@app/lib/config/env";
import { CustomLogger } from "@app/lib/logger/logger"; import { CustomLogger } from "@app/lib/logger/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TQueueServiceFactory } from "@app/queue"; import { TQueueServiceFactory } from "@app/queue";
import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { TSmtpService } from "@app/services/smtp/smtp-service"; import { TSmtpService } from "@app/services/smtp/smtp-service";
import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
@@ -42,16 +43,16 @@ type TMain = {
logger?: CustomLogger; logger?: CustomLogger;
queue: TQueueServiceFactory; queue: TQueueServiceFactory;
keyStore: TKeyStoreFactory; keyStore: TKeyStoreFactory;
hsmModule: HsmModule;
redis: Redis | Cluster; redis: Redis | Cluster;
envConfig: TEnvConfig; envConfig: TEnvConfig;
superAdminDAL: TSuperAdminDALFactory; superAdminDAL: TSuperAdminDALFactory;
hsmService: THsmServiceFactory;
kmsRootConfigDAL: TKmsRootConfigDALFactory;
}; };
// Run the server! // Run the server!
export const main = async ({ export const main = async ({
db, db,
hsmModule,
auditLogDb, auditLogDb,
smtp, smtp,
logger, logger,
@@ -59,7 +60,9 @@ export const main = async ({
keyStore, keyStore,
redis, redis,
envConfig, envConfig,
superAdminDAL superAdminDAL,
hsmService,
kmsRootConfigDAL
}: TMain) => { }: TMain) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -148,9 +151,10 @@ export const main = async ({
db, db,
auditLogDb, auditLogDb,
keyStore, keyStore,
hsmModule, hsmService,
envConfig, envConfig,
superAdminDAL superAdminDAL,
kmsRootConfigDAL
}); });
await server.register(registerServeUI, { await server.register(registerServeUI, {
@@ -8,6 +8,7 @@ import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto"; import { crypto } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { slugSchema } from "@app/server/lib/schemas";
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type"; import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types"; import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@@ -20,6 +21,8 @@ export type TAuthMode =
tokenVersionId: string; // the session id of token used tokenVersionId: string; // the session id of token used
user: TUsers; user: TUsers;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: AuthMethod; authMethod: AuthMethod;
isMfaVerified?: boolean; isMfaVerified?: boolean;
token: AuthModeJwtTokenPayload; token: AuthModeJwtTokenPayload;
@@ -31,6 +34,8 @@ export type TAuthMode =
userId: string; userId: string;
user: TUsers; user: TUsers;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
token: string; token: string;
} }
| { | {
@@ -39,6 +44,8 @@ export type TAuthMode =
actor: ActorType.SERVICE; actor: ActorType.SERVICE;
serviceTokenId: string; serviceTokenId: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
token: string; token: string;
} }
@@ -48,6 +55,8 @@ export type TAuthMode =
identityId: string; identityId: string;
identityName: string; identityName: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
isInstanceAdmin?: boolean; isInstanceAdmin?: boolean;
token: TIdentityAccessTokenJwtPayload; token: TIdentityAccessTokenJwtPayload;
@@ -57,6 +66,8 @@ export type TAuthMode =
actor: ActorType.SCIM_CLIENT; actor: ActorType.SCIM_CLIENT;
scimTokenId: string; scimTokenId: string;
orgId: string; orgId: string;
rootOrgId: string;
parentOrgId: string;
authMethod: null; authMethod: null;
}; };
@@ -136,17 +147,26 @@ export const injectIdentity = fp(
if (!authMode) return; if (!authMode) return;
const subOrganizationSelector = req.headers?.["x-infisical-org"] as string | undefined;
if (subOrganizationSelector) {
await slugSchema().parseAsync(subOrganizationSelector);
}
switch (authMode) { switch (authMode) {
case AuthMode.JWT: { case AuthMode.JWT: {
const { user, tokenVersionId, orgId } = await server.services.authToken.fnValidateJwtIdentity(token); const { user, tokenVersionId, orgId, rootOrgId, parentOrgId } =
await server.services.authToken.fnValidateJwtIdentity(token, subOrganizationSelector);
requestContext.set("orgId", orgId); requestContext.set("orgId", orgId);
req.auth = { req.auth = {
authMode: AuthMode.JWT, authMode: AuthMode.JWT,
user, user,
userId: user.id, userId: user.id,
tokenVersionId, tokenVersionId,
actor, actor,
orgId: orgId as string, orgId,
rootOrgId,
parentOrgId,
authMethod: token.authMethod, authMethod: token.authMethod,
isMfaVerified: token.isMfaVerified, isMfaVerified: token.isMfaVerified,
token token
@@ -154,13 +174,19 @@ export const injectIdentity = fp(
break; break;
} }
case AuthMode.IDENTITY_ACCESS_TOKEN: { case AuthMode.IDENTITY_ACCESS_TOKEN: {
const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(token, req.realIp); const identity = await server.services.identityAccessToken.fnValidateIdentityAccessToken(
token,
subOrganizationSelector,
req.realIp
);
const serverCfg = await getServerCfg(); const serverCfg = await getServerCfg();
requestContext.set("orgId", identity.orgId); requestContext.set("orgId", identity.orgId);
req.auth = { req.auth = {
authMode: AuthMode.IDENTITY_ACCESS_TOKEN, authMode: AuthMode.IDENTITY_ACCESS_TOKEN,
actor, actor,
orgId: identity.orgId, orgId: identity.orgId,
rootOrgId: identity.rootOrgId,
parentOrgId: identity.parentOrgId,
identityId: identity.identityId, identityId: identity.identityId,
identityName: identity.name, identityName: identity.name,
authMethod: null, authMethod: null,
@@ -190,8 +216,14 @@ export const injectIdentity = fp(
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {
const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token); const serviceToken = await server.services.serviceToken.fnValidateServiceToken(token);
requestContext.set("orgId", serviceToken.orgId); requestContext.set("orgId", serviceToken.orgId);
if (subOrganizationSelector)
throw new BadRequestError({ message: `Service token doesn't support sub organization selector` });
req.auth = { req.auth = {
orgId: serviceToken.orgId, orgId: serviceToken.orgId,
rootOrgId: serviceToken.rootOrgId,
parentOrgId: serviceToken.parentOrgId,
authMode: AuthMode.SERVICE_TOKEN as const, authMode: AuthMode.SERVICE_TOKEN as const,
serviceToken, serviceToken,
serviceTokenId: serviceToken.id, serviceTokenId: serviceToken.id,
@@ -202,22 +234,27 @@ export const injectIdentity = fp(
break; break;
} }
case AuthMode.API_KEY: { case AuthMode.API_KEY: {
const user = await server.services.apiKey.fnValidateApiKey(token as string); throw new BadRequestError({
req.auth = { message: "API key authentication is not supported anymore. Please switch to identity authentication."
authMode: AuthMode.API_KEY as const, });
userId: user.id,
actor,
user,
orgId: "API_KEY", // We set the orgId to an arbitrary value, since we can't link an API key to a specific org. We have to deprecate API keys soon!
authMethod: null,
token: token as string
};
break;
} }
case AuthMode.SCIM_TOKEN: { case AuthMode.SCIM_TOKEN: {
const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token); const { orgId, scimTokenId } = await server.services.scim.fnValidateScimToken(token);
requestContext.set("orgId", orgId); requestContext.set("orgId", orgId);
req.auth = { authMode: AuthMode.SCIM_TOKEN, actor, scimTokenId, orgId, authMethod: null };
if (subOrganizationSelector)
throw new BadRequestError({ message: `SCIM token doesn't support sub organization selector` });
req.auth = {
authMode: AuthMode.SCIM_TOKEN,
actor,
scimTokenId,
orgId,
authMethod: null,
// scim cannot be done for sub organization
rootOrgId: orgId,
parentOrgId: orgId
};
break; break;
} }
default: default:
@@ -14,7 +14,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.USER, type: ActorType.USER,
id: req.auth.userId, id: req.auth.userId,
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY" orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null authMethod: req.auth.authMethod, // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
}; };
logger.info( logger.info(
@@ -25,7 +27,9 @@ export const injectPermission = fp(async (server) => {
type: ActorType.IDENTITY, type: ActorType.IDENTITY,
id: req.auth.identityId, id: req.auth.identityId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
authMethod: null authMethod: null,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId
}; };
logger.info( logger.info(
@@ -36,6 +40,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SERVICE, type: ActorType.SERVICE,
id: req.auth.serviceTokenId, id: req.auth.serviceTokenId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null authMethod: null
}; };
@@ -47,6 +53,8 @@ export const injectPermission = fp(async (server) => {
type: ActorType.SCIM_CLIENT, type: ActorType.SCIM_CLIENT,
id: req.auth.scimTokenId, id: req.auth.scimTokenId,
orgId: req.auth.orgId, orgId: req.auth.orgId,
rootOrgId: req.auth.rootOrgId,
parentOrgId: req.auth.parentOrgId,
authMethod: null authMethod: null
}; };
+72 -22
View File
@@ -46,8 +46,8 @@ import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/gi
import { groupDALFactory } from "@app/ee/services/group/group-dal"; import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service"; import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { hsmServiceFactory } from "@app/ee/services/hsm/hsm-service"; import { isHsmActiveAndEnabled } from "@app/ee/services/hsm/hsm-fns";
import { HsmModule } from "@app/ee/services/hsm/hsm-types"; import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal"; import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal";
import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service"; import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service";
import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-client-certificate-dal"; import { kmipClientCertificateDALFactory } from "@app/ee/services/kmip/kmip-client-certificate-dal";
@@ -131,12 +131,14 @@ import { sshHostLoginUserDALFactory } from "@app/ee/services/ssh-host/ssh-login-
import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal"; import { sshHostGroupDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-dal";
import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal"; import { sshHostGroupMembershipDALFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-membership-dal";
import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service"; import { sshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { subOrgServiceFactory } from "@app/ee/services/sub-org/sub-org-service";
import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"; import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal";
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service"; import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { keyValueStoreDALFactory } from "@app/keystore/key-value-store-dal"; import { keyValueStoreDALFactory } from "@app/keystore/key-value-store-dal";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig, TEnvConfig } from "@app/lib/config/env"; import { getConfig, TEnvConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TQueueServiceFactory } from "@app/queue"; import { TQueueServiceFactory } from "@app/queue";
import { readLimit } from "@app/server/config/rateLimiter"; import { readLimit } from "@app/server/config/rateLimiter";
@@ -235,8 +237,9 @@ import { integrationAuthDALFactory } from "@app/services/integration-auth/integr
import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service"; import { integrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal"; import { internalKmsDALFactory } from "@app/services/kms/internal-kms-dal";
import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal"; import { kmskeyDALFactory } from "@app/services/kms/kms-key-dal";
import { kmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal"; import { TKmsRootConfigDALFactory } from "@app/services/kms/kms-root-config-dal";
import { kmsServiceFactory } from "@app/services/kms/kms-service"; import { kmsServiceFactory } from "@app/services/kms/kms-service";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { membershipDALFactory } from "@app/services/membership/membership-dal"; import { membershipDALFactory } from "@app/services/membership/membership-dal";
import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { membershipRoleDALFactory } from "@app/services/membership/membership-role-dal";
import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal"; import { membershipGroupDALFactory } from "@app/services/membership-group/membership-group-dal";
@@ -254,11 +257,11 @@ import { userNotificationDALFactory } from "@app/services/notification/user-noti
import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal"; import { offlineUsageReportDALFactory } from "@app/services/offline-usage-report/offline-usage-report-dal";
import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service"; import { offlineUsageReportServiceFactory } from "@app/services/offline-usage-report/offline-usage-report-service";
import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal"; import { incidentContactDALFactory } from "@app/services/org/incident-contacts-dal";
import { orgBotDALFactory } from "@app/services/org/org-bot-dal";
import { orgDALFactory } from "@app/services/org/org-dal"; import { orgDALFactory } from "@app/services/org/org-dal";
import { orgServiceFactory } from "@app/services/org/org-service"; import { orgServiceFactory } from "@app/services/org/org-service";
import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; import { orgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { pamAccountRotationServiceFactory } from "@app/services/pam-account-rotation/pam-account-rotation-queue";
import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue"; import { dailyExpiringPkiItemAlertQueueServiceFactory } from "@app/services/pki-alert/expiring-pki-item-alert-queue";
import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal"; import { pkiAlertDALFactory } from "@app/services/pki-alert/pki-alert-dal";
import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
@@ -363,20 +366,22 @@ export const registerRoutes = async (
auditLogDb, auditLogDb,
superAdminDAL, superAdminDAL,
db, db,
hsmModule,
smtp: smtpService, smtp: smtpService,
queue: queueService, queue: queueService,
keyStore, keyStore,
envConfig envConfig,
hsmService,
kmsRootConfigDAL
}: { }: {
auditLogDb?: Knex; auditLogDb?: Knex;
superAdminDAL: TSuperAdminDALFactory; superAdminDAL: TSuperAdminDALFactory;
db: Knex; db: Knex;
hsmModule: HsmModule;
smtp: TSmtpService; smtp: TSmtpService;
queue: TQueueServiceFactory; queue: TQueueServiceFactory;
keyStore: TKeyStoreFactory; keyStore: TKeyStoreFactory;
envConfig: TEnvConfig; envConfig: TEnvConfig;
hsmService: THsmServiceFactory;
kmsRootConfigDAL: TKmsRootConfigDALFactory;
} }
) => { ) => {
const appCfg = getConfig(); const appCfg = getConfig();
@@ -391,7 +396,6 @@ export const registerRoutes = async (
const authTokenDAL = tokenDALFactory(db); const authTokenDAL = tokenDALFactory(db);
const orgDAL = orgDALFactory(db); const orgDAL = orgDALFactory(db);
const orgMembershipDAL = orgMembershipDALFactory(db); const orgMembershipDAL = orgMembershipDALFactory(db);
const orgBotDAL = orgBotDALFactory(db);
const incidentContactDAL = incidentContactDALFactory(db); const incidentContactDAL = incidentContactDALFactory(db);
const rateLimitDAL = rateLimitDALFactory(db); const rateLimitDAL = rateLimitDALFactory(db);
const apiKeyDAL = apiKeyDALFactory(db); const apiKeyDAL = apiKeyDALFactory(db);
@@ -508,7 +512,6 @@ export const registerRoutes = async (
const kmsDAL = kmskeyDALFactory(db); const kmsDAL = kmskeyDALFactory(db);
const internalKmsDAL = internalKmsDALFactory(db); const internalKmsDAL = internalKmsDALFactory(db);
const externalKmsDAL = externalKmsDALFactory(db); const externalKmsDAL = externalKmsDALFactory(db);
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
const slackIntegrationDAL = slackIntegrationDALFactory(db); const slackIntegrationDAL = slackIntegrationDALFactory(db);
const projectSlackConfigDAL = projectSlackConfigDALFactory(db); const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
@@ -568,11 +571,11 @@ export const registerRoutes = async (
orgDAL, orgDAL,
licenseDAL, licenseDAL,
keyStore, keyStore,
identityOrgMembershipDAL, projectDAL,
projectDAL envConfig
}); });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL }); const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, membershipUserDAL, orgDAL });
const membershipUserService = membershipUserServiceFactory({ const membershipUserService = membershipUserServiceFactory({
licenseService, licenseService,
@@ -592,6 +595,7 @@ export const registerRoutes = async (
}); });
const membershipIdentityService = membershipIdentityServiceFactory({ const membershipIdentityService = membershipIdentityServiceFactory({
identityDAL,
membershipIdentityDAL, membershipIdentityDAL,
membershipRoleDAL, membershipRoleDAL,
orgDAL, orgDAL,
@@ -623,11 +627,6 @@ export const registerRoutes = async (
permissionService permissionService
}); });
const hsmService = hsmServiceFactory({
hsmModule,
envConfig
});
const kmsService = kmsServiceFactory({ const kmsService = kmsServiceFactory({
kmsRootConfigDAL, kmsRootConfigDAL,
keyStore, keyStore,
@@ -900,7 +899,6 @@ export const registerRoutes = async (
smtpService, smtpService,
userDAL, userDAL,
groupDAL, groupDAL,
orgBotDAL,
oidcConfigDAL, oidcConfigDAL,
ldapConfigDAL, ldapConfigDAL,
loginService, loginService,
@@ -912,6 +910,15 @@ export const registerRoutes = async (
userGroupMembershipDAL, userGroupMembershipDAL,
additionalPrivilegeDAL additionalPrivilegeDAL
}); });
const subOrgService = subOrgServiceFactory({
licenseService,
membershipDAL,
membershipRoleDAL,
orgDAL,
permissionService
});
const signupService = authSignupServiceFactory({ const signupService = authSignupServiceFactory({
tokenService, tokenService,
smtpService, smtpService,
@@ -1594,10 +1601,12 @@ export const registerRoutes = async (
permissionService, permissionService,
projectDAL, projectDAL,
accessTokenQueue, accessTokenQueue,
smtpService smtpService,
orgDAL
}); });
const identityService = identityServiceFactory({ const identityService = identityServiceFactory({
additionalPrivilegeDAL,
permissionService, permissionService,
identityDAL, identityDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
@@ -1628,10 +1637,12 @@ export const registerRoutes = async (
identityAccessTokenDAL, identityAccessTokenDAL,
accessTokenQueue, accessTokenQueue,
identityDAL, identityDAL,
membershipIdentityDAL membershipIdentityDAL,
orgDAL
}); });
const identityTokenAuthService = identityTokenAuthServiceFactory({ const identityTokenAuthService = identityTokenAuthServiceFactory({
identityDAL,
identityTokenAuthDAL, identityTokenAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
@@ -1641,6 +1652,7 @@ export const registerRoutes = async (
}); });
const identityUaService = identityUaServiceFactory({ const identityUaService = identityUaServiceFactory({
identityDAL,
permissionService, permissionService,
identityAccessTokenDAL, identityAccessTokenDAL,
identityUaClientSecretDAL, identityUaClientSecretDAL,
@@ -1652,6 +1664,7 @@ export const registerRoutes = async (
}); });
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityDAL,
identityKubernetesAuthDAL, identityKubernetesAuthDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
@@ -1665,6 +1678,7 @@ export const registerRoutes = async (
membershipIdentityDAL membershipIdentityDAL
}); });
const identityGcpAuthService = identityGcpAuthServiceFactory({ const identityGcpAuthService = identityGcpAuthServiceFactory({
identityDAL,
identityGcpAuthDAL, identityGcpAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1674,6 +1688,7 @@ export const registerRoutes = async (
}); });
const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({ const identityAliCloudAuthService = identityAliCloudAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityAliCloudAuthDAL, identityAliCloudAuthDAL,
@@ -1683,6 +1698,7 @@ export const registerRoutes = async (
}); });
const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({ const identityTlsCertAuthService = identityTlsCertAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
identityTlsCertAuthDAL, identityTlsCertAuthDAL,
licenseService, licenseService,
@@ -1692,6 +1708,7 @@ export const registerRoutes = async (
}); });
const identityAwsAuthService = identityAwsAuthServiceFactory({ const identityAwsAuthService = identityAwsAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityAwsAuthDAL, identityAwsAuthDAL,
@@ -1701,6 +1718,7 @@ export const registerRoutes = async (
}); });
const identityAzureAuthService = identityAzureAuthServiceFactory({ const identityAzureAuthService = identityAzureAuthServiceFactory({
identityDAL,
identityAzureAuthDAL, identityAzureAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1710,6 +1728,7 @@ export const registerRoutes = async (
}); });
const identityOciAuthService = identityOciAuthServiceFactory({ const identityOciAuthService = identityOciAuthServiceFactory({
identityDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
orgDAL, orgDAL,
identityOciAuthDAL, identityOciAuthDAL,
@@ -1733,6 +1752,7 @@ export const registerRoutes = async (
}); });
const identityOidcAuthService = identityOidcAuthServiceFactory({ const identityOidcAuthService = identityOidcAuthServiceFactory({
identityDAL,
identityOidcAuthDAL, identityOidcAuthDAL,
orgDAL, orgDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1743,6 +1763,7 @@ export const registerRoutes = async (
}); });
const identityJwtAuthService = identityJwtAuthServiceFactory({ const identityJwtAuthService = identityJwtAuthServiceFactory({
identityDAL,
identityJwtAuthDAL, identityJwtAuthDAL,
orgDAL, orgDAL,
permissionService, permissionService,
@@ -2238,7 +2259,13 @@ export const registerRoutes = async (
pamSessionDAL, pamSessionDAL,
permissionService, permissionService,
projectDAL, projectDAL,
userDAL userDAL,
auditLogService
});
const pamAccountRotation = pamAccountRotationServiceFactory({
queueService,
pamAccountService
}); });
const pamSessionService = pamSessionServiceFactory({ const pamSessionService = pamSessionServiceFactory({
@@ -2272,16 +2299,38 @@ export const registerRoutes = async (
// Start HSM service if it's configured/enabled. // Start HSM service if it's configured/enabled.
await hsmService.startService(); await hsmService.startService();
const hsmStatus = await isHsmActiveAndEnabled({
hsmService,
kmsRootConfigDAL,
licenseService
});
// if the encryption strategy is software - user needs to provide an encryption key
// if the encryption strategy is null AND the hsm is not configured - user needs to provide an encryption key
const needsEncryptionKey =
hsmStatus.rootKmsConfigEncryptionStrategy === RootKeyEncryptionStrategy.Software ||
(hsmStatus.rootKmsConfigEncryptionStrategy === null && !hsmStatus.isHsmConfigured);
if (needsEncryptionKey) {
if (!envConfig.ROOT_ENCRYPTION_KEY && !envConfig.ENCRYPTION_KEY) {
throw new BadRequestError({
message:
"Root KMS encryption strategy is set to software. Please set the ENCRYPTION_KEY environment variable and restart your deployment.\nYou can enable HSM encryption in the Server Console."
});
}
}
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await telemetryQueue.startAggregatedEventsJob(); await telemetryQueue.startAggregatedEventsJob();
await dailyResourceCleanUp.init(); await dailyResourceCleanUp.init();
await healthAlert.init(); await healthAlert.init();
await pkiSyncCleanup.init(); await pkiSyncCleanup.init();
await pamAccountRotation.init();
await dailyReminderQueueService.startDailyRemindersJob(); await dailyReminderQueueService.startDailyRemindersJob();
await dailyReminderQueueService.startSecretReminderMigrationJob(); await dailyReminderQueueService.startSecretReminderMigrationJob();
await dailyExpiringPkiItemAlert.startSendingAlerts(); await dailyExpiringPkiItemAlert.startSendingAlerts();
await pkiSubscriberQueue.startDailyAutoRenewalJob(); await pkiSubscriberQueue.startDailyAutoRenewalJob();
await kmsService.startService(); await kmsService.startService(hsmStatus);
await microsoftTeamsService.start(); await microsoftTeamsService.start();
await dynamicSecretQueueService.init(); await dynamicSecretQueueService.init();
await eventBusService.init(); await eventBusService.init();
@@ -2296,6 +2345,7 @@ export const registerRoutes = async (
groupProject: groupProjectService, groupProject: groupProjectService,
permission: permissionService, permission: permissionService,
org: orgService, org: orgService,
subOrganization: subOrgService,
oidc: oidcService, oidc: oidcService,
apiKey: apiKeyService, apiKey: apiKeyService,
authToken: tokenService, authToken: tokenService,
@@ -94,6 +94,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
decodedToken.userId, decodedToken.userId,
decodedToken.organizationId, decodedToken.organizationId,
decodedToken.authMethod, decodedToken.authMethod,
decodedToken.organizationId,
decodedToken.organizationId decodedToken.organizationId
); );
if (org && org.userTokenExpiration) { if (org && org.userTokenExpiration) {
@@ -73,12 +73,12 @@ export const registerIdentityAliCloudAuthRouter = async (server: FastifyZodProvi
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAliCloudAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAliCloudAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAliCloudAuth.login(req.body); await server.services.identityAliCloudAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH, type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH,
metadata: { metadata: {
@@ -40,12 +40,12 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAwsAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAwsAuth.login(req.body); await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH, type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: { metadata: {
@@ -35,12 +35,12 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityAzureAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityAzureAuth, accessToken, identityAccessToken, identity } =
await server.services.identityAzureAuth.login(req.body); await server.services.identityAzureAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH, type: EventType.LOGIN_IDENTITY_AZURE_AUTH,
metadata: { metadata: {
@@ -35,12 +35,12 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityGcpAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityGcpAuth, accessToken, identityAccessToken, identity } =
await server.services.identityGcpAuth.login(req.body); await server.services.identityGcpAuth.login(req.body);
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_GCP_AUTH, type: EventType.LOGIN_IDENTITY_GCP_AUTH,
metadata: { metadata: {
@@ -111,7 +111,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityJwtAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityJwtAuth, accessToken, identityAccessToken, identity } =
await server.services.identityJwtAuth.login({ await server.services.identityJwtAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
jwt: req.body.jwt jwt: req.body.jwt
@@ -119,7 +119,7 @@ export const registerIdentityJwtAuthRouter = async (server: FastifyZodProvider)
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_JWT_AUTH, type: EventType.LOGIN_IDENTITY_JWT_AUTH,
metadata: { metadata: {
@@ -56,7 +56,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
} }
}, },
handler: async (req) => { handler: async (req) => {
const { identityKubernetesAuth, accessToken, identityAccessToken, identityMembershipOrg } = const { identityKubernetesAuth, accessToken, identityAccessToken, identity } =
await server.services.identityKubernetesAuth.login({ await server.services.identityKubernetesAuth.login({
identityId: req.body.identityId, identityId: req.body.identityId,
jwt: req.body.jwt jwt: req.body.jwt
@@ -64,7 +64,7 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH, type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH,
metadata: { metadata: {
@@ -162,13 +162,13 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
const { identityId, user } = req.passportMachineIdentity; const { identityId, user } = req.passportMachineIdentity;
const { accessToken, identityLdapAuth, identityMembershipOrg } = await server.services.identityLdapAuth.login({ const { accessToken, identityLdapAuth, identity } = await server.services.identityLdapAuth.login({
identityId identityId
}); });
await server.services.auditLog.createAuditLog({ await server.services.auditLog.createAuditLog({
...req.auditLogInfo, ...req.auditLogInfo,
orgId: identityMembershipOrg.scopeOrgId, orgId: identity.orgId,
event: { event: {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH, type: EventType.LOGIN_IDENTITY_LDAP_AUTH,
metadata: { metadata: {

Some files were not shown because too many files have changed in this diff Show More