Merge branch 'main' into feat/point-in-time-revamp

This commit is contained in:
carlosmonastyrski
2025-05-20 10:37:41 -03:00
parent c01a98ccf1
commit 1748052cb0
731 changed files with 29323 additions and 10098 deletions
+12
View File
@@ -28,3 +28,15 @@ frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow
docs/cli/commands/user.mdx:generic-api-key:51 docs/cli/commands/user.mdx:generic-api-key:51
frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76 frontend/src/pages/secret-manager/OverviewPage/components/SecretOverviewTableRow/SecretOverviewTableRow.tsx:generic-api-key:76
docs/integrations/app-connections/hashicorp-vault.mdx:generic-api-key:188 docs/integrations/app-connections/hashicorp-vault.mdx:generic-api-key:188
cli/detect/config/gitleaks.toml:gcp-api-key:567
cli/detect/config/gitleaks.toml:gcp-api-key:569
cli/detect/config/gitleaks.toml:gcp-api-key:570
cli/detect/config/gitleaks.toml:gcp-api-key:572
cli/detect/config/gitleaks.toml:gcp-api-key:574
cli/detect/config/gitleaks.toml:gcp-api-key:575
cli/detect/config/gitleaks.toml:gcp-api-key:576
cli/detect/config/gitleaks.toml:gcp-api-key:577
cli/detect/config/gitleaks.toml:gcp-api-key:578
cli/detect/config/gitleaks.toml:gcp-api-key:579
cli/detect/config/gitleaks.toml:gcp-api-key:581
cli/detect/config/gitleaks.toml:gcp-api-key:582
+3 -2
View File
@@ -133,8 +133,8 @@ RUN apt-get update && apt-get install -y \
RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini RUN printf "[FreeTDS]\nDescription = FreeTDS Driver\nDriver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so\nSetup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so\nFileUsage = 1\n" > /etc/odbcinst.ini
# Install Infisical CLI # Install Infisical CLI
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \ RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
&& apt-get update && apt-get install -y infisical=0.31.1 \ && apt-get update && apt-get install -y infisical=0.41.2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
@@ -171,6 +171,7 @@ ENV NODE_ENV production
ENV STANDALONE_BUILD true ENV STANDALONE_BUILD true
ENV STANDALONE_MODE true ENV STANDALONE_MODE true
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
ENV NODE_OPTIONS="--max-old-space-size=1024"
WORKDIR /backend WORKDIR /backend
+3 -2
View File
@@ -127,8 +127,8 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install Infisical CLI # Install Infisical CLI
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash \ RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
&& apt-get update && apt-get install -y infisical=0.31.1 \ && apt-get update && apt-get install -y infisical=0.41.2 \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR / WORKDIR /
@@ -168,6 +168,7 @@ ENV HTTPS_ENABLED false
ENV NODE_ENV production ENV NODE_ENV production
ENV STANDALONE_BUILD true ENV STANDALONE_BUILD true
ENV STANDALONE_MODE true ENV STANDALONE_MODE true
ENV NODE_OPTIONS="--max-old-space-size=1024"
WORKDIR /backend WORKDIR /backend
+2 -2
View File
@@ -54,8 +54,8 @@ COPY --from=build /app .
# Install Infisical CLI # Install Infisical CLI
RUN apt-get install -y curl bash && \ RUN apt-get install -y curl bash && \
curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \ curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
apt-get update && apt-get install -y infisical=0.8.1 git apt-get update && apt-get install -y infisical=0.41.2 git
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \ HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js CMD node healthcheck.js
+2 -2
View File
@@ -55,9 +55,9 @@ RUN mkdir -p /etc/softhsm2/tokens && \
# ? App setup # ? App setup
# Install Infisical CLI # Install Infisical CLI
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \ RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
apt-get update && \ apt-get update && \
apt-get install -y infisical=0.8.1 apt-get install -y infisical=0.41.2
WORKDIR /app WORKDIR /app
+2 -2
View File
@@ -64,9 +64,9 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
# ? App setup # ? App setup
# Install Infisical CLI # Install Infisical CLI
RUN curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash && \ RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash && \
apt-get update && \ apt-get update && \
apt-get install -y infisical=0.8.1 apt-get install -y infisical=0.41.2
WORKDIR /app WORKDIR /app
+25
View File
@@ -1,4 +1,8 @@
import RE2 from "re2";
import { TKeyStoreFactory } from "@app/keystore/keystore"; import { TKeyStoreFactory } from "@app/keystore/keystore";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { Lock } from "@app/lib/red-lock"; import { Lock } from "@app/lib/red-lock";
export const mockKeyStore = (): TKeyStoreFactory => { export const mockKeyStore = (): TKeyStoreFactory => {
@@ -18,6 +22,27 @@ export const mockKeyStore = (): TKeyStoreFactory => {
delete store[key]; delete store[key];
return 1; return 1;
}, },
deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => {
const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
let totalDeleted = 0;
const keys = Object.keys(store);
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
for (const key of batch) {
if (regex.test(key)) {
delete store[key];
totalDeleted += 1;
}
}
// eslint-disable-next-line no-await-in-loop
await delayMs(Math.max(0, applyJitter(delay, jitter)));
}
return totalDeleted;
},
getItem: async (key) => { getItem: async (key) => {
const value = store[key]; const value = store[key];
if (typeof value === "string") { if (typeof value === "string") {
+1989 -314
View File
File diff suppressed because it is too large Load Diff
+6 -4
View File
@@ -38,8 +38,8 @@
"build:frontend": "npm run build --prefix ../frontend", "build:frontend": "npm run build --prefix ../frontend",
"start": "node --enable-source-maps dist/main.mjs", "start": "node --enable-source-maps dist/main.mjs",
"type:check": "tsc --noEmit", "type:check": "tsc --noEmit",
"lint:fix": "eslint --fix --ext js,ts ./src", "lint:fix": "node --max-old-space-size=8192 ./node_modules/.bin/eslint --fix --ext js,ts ./src",
"lint": "eslint 'src/**/*.ts'", "lint": "node --max-old-space-size=8192 ./node_modules/.bin/eslint 'src/**/*.ts'",
"test:unit": "vitest run -c vitest.unit.config.ts", "test:unit": "vitest run -c vitest.unit.config.ts",
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1", "test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1", "test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
@@ -152,7 +152,8 @@
"@infisical/quic": "^1.0.8", "@infisical/quic": "^1.0.8",
"@node-saml/passport-saml": "^5.0.1", "@node-saml/passport-saml": "^5.0.1",
"@octokit/auth-app": "^7.1.1", "@octokit/auth-app": "^7.1.1",
"@octokit/plugin-paginate-graphql": "^5.2.4", "@octokit/core": "^5.2.1",
"@octokit/plugin-paginate-graphql": "^4.0.1",
"@octokit/plugin-retry": "^5.0.5", "@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2", "@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1", "@octokit/webhooks-types": "^7.3.1",
@@ -208,6 +209,7 @@
"mysql2": "^3.9.8", "mysql2": "^3.9.8",
"nanoid": "^3.3.8", "nanoid": "^3.3.8",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",
"oci-sdk": "^2.108.0",
"odbc": "^2.4.9", "odbc": "^2.4.9",
"openid-client": "^5.6.5", "openid-client": "^5.6.5",
"ora": "^7.0.1", "ora": "^7.0.1",
@@ -240,6 +242,6 @@
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"zod": "^3.22.4", "zod": "^3.22.4",
"zod-to-json-schema": "^3.22.4" "zod-to-json-schema": "^3.24.5"
} }
} }
+17 -1
View File
@@ -67,6 +67,9 @@ import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-a
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service"; import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service"; import { TIdentityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service"; import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { TIdentityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
import { TIdentityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service";
import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service"; import { TIdentityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service"; import { TIdentityTokenAuthServiceFactory } from "@app/services/identity-token-auth/identity-token-auth-service";
@@ -79,6 +82,7 @@ import { TOrgServiceFactory } from "@app/services/org/org-service";
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service"; import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service"; import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service"; import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
import { TPkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { TProjectServiceFactory } from "@app/services/project/project-service"; import { TProjectServiceFactory } from "@app/services/project/project-service";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service"; import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service"; import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
@@ -147,6 +151,13 @@ declare module "fastify" {
providerAuthToken: string; providerAuthToken: string;
externalProviderAccessToken?: string; externalProviderAccessToken?: string;
}; };
passportMachineIdentity: {
identityId: string;
user: {
uid: string;
mail?: string;
};
};
kmipUser: { kmipUser: {
projectId: string; projectId: string;
clientId: string; clientId: string;
@@ -154,7 +165,9 @@ declare module "fastify" {
}; };
auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">; auditLogInfo: Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>; ssoConfig: Awaited<ReturnType<TSamlConfigServiceFactory["getSaml"]>>;
ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>>; ldapConfig: Awaited<ReturnType<TLdapConfigServiceFactory["getLdapCfg"]>> & {
allowedFields?: TAllowedFields[];
};
} }
interface FastifyInstance { interface FastifyInstance {
@@ -198,8 +211,10 @@ declare module "fastify" {
identityGcpAuth: TIdentityGcpAuthServiceFactory; identityGcpAuth: TIdentityGcpAuthServiceFactory;
identityAwsAuth: TIdentityAwsAuthServiceFactory; identityAwsAuth: TIdentityAwsAuthServiceFactory;
identityAzureAuth: TIdentityAzureAuthServiceFactory; identityAzureAuth: TIdentityAzureAuthServiceFactory;
identityOciAuth: TIdentityOciAuthServiceFactory;
identityOidcAuth: TIdentityOidcAuthServiceFactory; identityOidcAuth: TIdentityOidcAuthServiceFactory;
identityJwtAuth: TIdentityJwtAuthServiceFactory; identityJwtAuth: TIdentityJwtAuthServiceFactory;
identityLdapAuth: TIdentityLdapAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory; accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory; accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory; secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
@@ -221,6 +236,7 @@ declare module "fastify" {
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory; certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
certificateEst: TCertificateEstServiceFactory; certificateEst: TCertificateEstServiceFactory;
pkiCollection: TPkiCollectionServiceFactory; pkiCollection: TPkiCollectionServiceFactory;
pkiSubscriber: TPkiSubscriberServiceFactory;
secretScanning: TSecretScanningServiceFactory; secretScanning: TSecretScanningServiceFactory;
license: TLicenseServiceFactory; license: TLicenseServiceFactory;
trustedIp: TTrustedIpServiceFactory; trustedIp: TTrustedIpServiceFactory;
+26
View File
@@ -137,6 +137,9 @@ import {
TIdentityMetadata, TIdentityMetadata,
TIdentityMetadataInsert, TIdentityMetadataInsert,
TIdentityMetadataUpdate, TIdentityMetadataUpdate,
TIdentityOciAuths,
TIdentityOciAuthsInsert,
TIdentityOciAuthsUpdate,
TIdentityOidcAuths, TIdentityOidcAuths,
TIdentityOidcAuthsInsert, TIdentityOidcAuthsInsert,
TIdentityOidcAuthsUpdate, TIdentityOidcAuthsUpdate,
@@ -227,6 +230,9 @@ import {
TPkiCollections, TPkiCollections,
TPkiCollectionsInsert, TPkiCollectionsInsert,
TPkiCollectionsUpdate, TPkiCollectionsUpdate,
TPkiSubscribers,
TPkiSubscribersInsert,
TPkiSubscribersUpdate,
TProjectBots, TProjectBots,
TProjectBotsInsert, TProjectBotsInsert,
TProjectBotsUpdate, TProjectBotsUpdate,
@@ -450,6 +456,11 @@ import {
TWorkflowIntegrationsInsert, TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate TWorkflowIntegrationsUpdate
} from "@app/db/schemas"; } from "@app/db/schemas";
import {
TIdentityLdapAuths,
TIdentityLdapAuthsInsert,
TIdentityLdapAuthsUpdate
} from "@app/db/schemas/identity-ldap-auths";
import { import {
TMicrosoftTeamsIntegrations, TMicrosoftTeamsIntegrations,
TMicrosoftTeamsIntegrationsInsert, TMicrosoftTeamsIntegrationsInsert,
@@ -577,6 +588,11 @@ declare module "knex/types/tables" {
TPkiCollectionItemsInsert, TPkiCollectionItemsInsert,
TPkiCollectionItemsUpdate TPkiCollectionItemsUpdate
>; >;
[TableName.PkiSubscriber]: KnexOriginal.CompositeTableType<
TPkiSubscribers,
TPkiSubscribersInsert,
TPkiSubscribersUpdate
>;
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType< [TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
TUserGroupMembership, TUserGroupMembership,
TUserGroupMembershipInsert, TUserGroupMembershipInsert,
@@ -743,6 +759,11 @@ declare module "knex/types/tables" {
TIdentityAzureAuthsInsert, TIdentityAzureAuthsInsert,
TIdentityAzureAuthsUpdate TIdentityAzureAuthsUpdate
>; >;
[TableName.IdentityOciAuth]: KnexOriginal.CompositeTableType<
TIdentityOciAuths,
TIdentityOciAuthsInsert,
TIdentityOciAuthsUpdate
>;
[TableName.IdentityOidcAuth]: KnexOriginal.CompositeTableType< [TableName.IdentityOidcAuth]: KnexOriginal.CompositeTableType<
TIdentityOidcAuths, TIdentityOidcAuths,
TIdentityOidcAuthsInsert, TIdentityOidcAuthsInsert,
@@ -753,6 +774,11 @@ declare module "knex/types/tables" {
TIdentityJwtAuthsInsert, TIdentityJwtAuthsInsert,
TIdentityJwtAuthsUpdate TIdentityJwtAuthsUpdate
>; >;
[TableName.IdentityLdapAuth]: KnexOriginal.CompositeTableType<
TIdentityLdapAuths,
TIdentityLdapAuthsInsert,
TIdentityLdapAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType< [TableName.IdentityUaClientSecret]: KnexOriginal.CompositeTableType<
TIdentityUaClientSecrets, TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert, TIdentityUaClientSecretsInsert,
@@ -0,0 +1,33 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.CertificateBody, "encryptedCertificateChain"))) {
await knex.schema.alterTable(TableName.CertificateBody, (t) => {
t.binary("encryptedCertificateChain").nullable();
});
}
if (!(await knex.schema.hasTable(TableName.CertificateSecret))) {
await knex.schema.createTable(TableName.CertificateSecret, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.uuid("certId").notNullable().unique();
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
t.binary("encryptedPrivateKey").notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.CertificateSecret)) {
await knex.schema.dropTable(TableName.CertificateSecret);
}
if (await knex.schema.hasColumn(TableName.CertificateBody, "encryptedCertificateChain")) {
await knex.schema.alterTable(TableName.CertificateBody, (t) => {
t.dropColumn("encryptedCertificateChain");
});
}
}
@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.SshHostLoginUserMapping, "groupId"))) {
await knex.schema.alterTable(TableName.SshHostLoginUserMapping, (t) => {
t.uuid("groupId").nullable();
t.foreign("groupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
t.unique(["sshHostLoginUserId", "groupId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SshHostLoginUserMapping, "groupId")) {
await knex.schema.alterTable(TableName.SshHostLoginUserMapping, (t) => {
t.dropUnique(["sshHostLoginUserId", "groupId"]);
t.dropColumn("groupId");
});
}
}
@@ -0,0 +1,22 @@
import { Knex } from "knex";
import { ProjectType, TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasColumn(TableName.ProjectTemplates, "type"))) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
// defaulting to sm for migration to set existing, new ones will always be specified on creation
t.string("type").defaultTo(ProjectType.SecretManager).notNullable();
t.jsonb("environments").nullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.ProjectTemplates, "type")) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.dropColumn("type");
// not reverting nullable environments
});
}
}
@@ -0,0 +1,39 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityLdapAuth))) {
await knex.schema.createTable(TableName.IdentityLdapAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.binary("encryptedBindDN").notNullable();
t.binary("encryptedBindPass").notNullable();
t.binary("encryptedLdapCaCertificate").nullable();
t.string("url").notNullable();
t.string("searchBase").notNullable();
t.string("searchFilter").notNullable();
t.jsonb("allowedFields").nullable();
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityLdapAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityLdapAuth);
}
@@ -0,0 +1,46 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.PkiSubscriber))) {
await knex.schema.createTable(TableName.PkiSubscriber, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true);
t.string("projectId").notNullable();
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.uuid("caId").nullable();
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("SET NULL");
t.string("name").notNullable();
t.string("commonName").notNullable();
t.specificType("subjectAlternativeNames", "text[]").notNullable();
t.string("ttl").notNullable();
t.specificType("keyUsages", "text[]").notNullable();
t.specificType("extendedKeyUsages", "text[]").notNullable();
t.string("status").notNullable(); // active / disabled
t.unique(["projectId", "name"]);
});
await createOnUpdateTrigger(knex, TableName.PkiSubscriber);
}
const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId");
if (!hasSubscriberCol) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.uuid("pkiSubscriberId").nullable();
t.foreign("pkiSubscriberId").references("id").inTable(TableName.PkiSubscriber).onDelete("SET NULL");
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasSubscriberCol = await knex.schema.hasColumn(TableName.Certificate, "pkiSubscriberId");
if (hasSubscriberCol) {
await knex.schema.alterTable(TableName.Certificate, (t) => {
t.dropColumn("pkiSubscriberId");
});
}
await knex.schema.dropTableIfExists(TableName.PkiSubscriber);
await dropOnUpdateTrigger(knex, TableName.PkiSubscriber);
}
@@ -0,0 +1,30 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityOciAuth))) {
await knex.schema.createTable(TableName.IdentityOciAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.string("type").notNullable();
t.string("tenancyOcid").notNullable();
t.string("allowedUsernames").nullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityOciAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityOciAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityOciAuth);
}
@@ -0,0 +1,25 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId");
if (!hasGatewayIdColumn) {
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
table.uuid("gatewayId").nullable();
table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL");
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasGatewayIdColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "gatewayId");
if (hasGatewayIdColumn) {
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
table.dropForeign("gatewayId");
table.dropColumn("gatewayId");
});
}
}
@@ -0,0 +1,110 @@
import { Knex } from "knex";
import { inMemoryKeyStore } from "@app/keystore/memory";
import { selectAllTableCols } from "@app/lib/knex";
import { initLogger } from "@app/lib/logger";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TableName } from "../schemas";
import { getMigrationEnvConfig } from "./utils/env-config";
import { getMigrationEncryptionServices } from "./utils/services";
// 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.
const BATCH_SIZE = 500;
export async function up(knex: Knex): Promise<void> {
// eslint-disable-next-line no-param-reassign
knex.replicaNode = () => {
return knex;
};
if (!(await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId"))) {
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
table.uuid("gatewayId").nullable();
table.foreign("gatewayId").references("id").inTable(TableName.Gateway).onDelete("SET NULL");
table.index("gatewayId");
});
const existingDynamicSecretsWithProjectGatewayId = await knex(TableName.DynamicSecret)
.select(selectAllTableCols(TableName.DynamicSecret))
.whereNotNull(`${TableName.DynamicSecret}.projectGatewayId`)
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.id`, `${TableName.DynamicSecret}.projectGatewayId`)
.whereNotNull(`${TableName.ProjectGateway}.gatewayId`)
.select(
knex.ref("projectId").withSchema(TableName.ProjectGateway).as("projectId"),
knex.ref("gatewayId").withSchema(TableName.ProjectGateway).as("projectGatewayGatewayId")
);
initLogger();
const envConfig = getMigrationEnvConfig();
const keyStore = inMemoryKeyStore();
const { kmsService } = await getMigrationEncryptionServices({ envConfig, keyStore, db: knex });
const updatedDynamicSecrets = await Promise.all(
existingDynamicSecretsWithProjectGatewayId.map(async (existingDynamicSecret) => {
if (!existingDynamicSecret.projectGatewayGatewayId) {
const result = {
...existingDynamicSecret,
gatewayId: null
};
const { projectId, projectGatewayGatewayId, ...rest } = result;
return rest;
}
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: existingDynamicSecret.projectId
});
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: existingDynamicSecret.projectId
});
let decryptedStoredInput = JSON.parse(
secretManagerDecryptor({ cipherTextBlob: Buffer.from(existingDynamicSecret.encryptedInput) }).toString()
) as object;
// We're not removing the existing projectGatewayId from the input so we can easily rollback without having to re-encrypt the input
decryptedStoredInput = {
...decryptedStoredInput,
gatewayId: existingDynamicSecret.projectGatewayGatewayId
};
const encryptedInput = secretManagerEncryptor({
plainText: Buffer.from(JSON.stringify(decryptedStoredInput))
}).cipherTextBlob;
const result = {
...existingDynamicSecret,
encryptedInput,
gatewayId: existingDynamicSecret.projectGatewayGatewayId
};
const { projectId, projectGatewayGatewayId, ...rest } = result;
return rest;
})
);
for (let i = 0; i < updatedDynamicSecrets.length; i += BATCH_SIZE) {
// eslint-disable-next-line no-await-in-loop
await knex(TableName.DynamicSecret)
.insert(updatedDynamicSecrets.slice(i, i + BATCH_SIZE))
.onConflict("id")
.merge();
}
}
}
export async function down(knex: Knex): Promise<void> {
// no re-encryption needed as we keep the old projectGatewayId in the input
if (await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId")) {
await knex.schema.alterTable(TableName.DynamicSecret, (table) => {
table.dropForeign("gatewayId");
table.dropColumn("gatewayId");
});
}
}
@@ -0,0 +1,53 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const columns = await knex.table(TableName.Organization).columnInfo();
await knex.schema.alterTable(TableName.Organization, (t) => {
if (!columns.secretsProductEnabled) {
t.boolean("secretsProductEnabled").defaultTo(true);
}
if (!columns.pkiProductEnabled) {
t.boolean("pkiProductEnabled").defaultTo(true);
}
if (!columns.kmsProductEnabled) {
t.boolean("kmsProductEnabled").defaultTo(true);
}
if (!columns.sshProductEnabled) {
t.boolean("sshProductEnabled").defaultTo(true);
}
if (!columns.scannerProductEnabled) {
t.boolean("scannerProductEnabled").defaultTo(true);
}
if (!columns.shareSecretsProductEnabled) {
t.boolean("shareSecretsProductEnabled").defaultTo(true);
}
});
}
export async function down(knex: Knex): Promise<void> {
const columns = await knex.table(TableName.Organization).columnInfo();
await knex.schema.alterTable(TableName.Organization, (t) => {
if (columns.secretsProductEnabled) {
t.dropColumn("secretsProductEnabled");
}
if (columns.pkiProductEnabled) {
t.dropColumn("pkiProductEnabled");
}
if (columns.kmsProductEnabled) {
t.dropColumn("kmsProductEnabled");
}
if (columns.sshProductEnabled) {
t.dropColumn("sshProductEnabled");
}
if (columns.scannerProductEnabled) {
t.dropColumn("scannerProductEnabled");
}
if (columns.shareSecretsProductEnabled) {
t.dropColumn("shareSecretsProductEnabled");
}
});
}
@@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing");
if (!hasSecretSharingColumn) {
await knex.schema.table(TableName.Project, (table) => {
table.boolean("secretSharing").notNullable().defaultTo(true);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasSecretSharingColumn = await knex.schema.hasColumn(TableName.Project, "secretSharing");
if (hasSecretSharingColumn) {
await knex.schema.table(TableName.Project, (table) => {
table.dropColumn("secretSharing");
});
}
}
@@ -0,0 +1,35 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasLifetimeColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretLifetime");
const hasViewLimitColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretViewLimit");
if (!hasLifetimeColumn || !hasViewLimitColumn) {
await knex.schema.alterTable(TableName.Organization, (t) => {
if (!hasLifetimeColumn) {
t.integer("maxSharedSecretLifetime").nullable().defaultTo(2592000); // 30 days in seconds
}
if (!hasViewLimitColumn) {
t.integer("maxSharedSecretViewLimit").nullable();
}
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasLifetimeColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretLifetime");
const hasViewLimitColumn = await knex.schema.hasColumn(TableName.Organization, "maxSharedSecretViewLimit");
if (hasLifetimeColumn || hasViewLimitColumn) {
await knex.schema.alterTable(TableName.Organization, (t) => {
if (hasLifetimeColumn) {
t.dropColumn("maxSharedSecretLifetime");
}
if (hasViewLimitColumn) {
t.dropColumn("maxSharedSecretViewLimit");
}
});
}
}
@@ -0,0 +1,43 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
const hasAuthorizedEmails = await knex.schema.hasColumn(TableName.SecretSharing, "authorizedEmails");
if (!hasEncryptedSalt || !hasAuthorizedEmails) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
// These two columns are only needed when secrets are shared with a specific list of emails
if (!hasEncryptedSalt) {
t.binary("encryptedSalt").nullable();
}
if (!hasAuthorizedEmails) {
t.json("authorizedEmails").nullable();
}
});
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.SecretSharing)) {
const hasEncryptedSalt = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSalt");
const hasAuthorizedEmails = await knex.schema.hasColumn(TableName.SecretSharing, "authorizedEmails");
if (hasEncryptedSalt || hasAuthorizedEmails) {
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
if (hasEncryptedSalt) {
t.dropColumn("encryptedSalt");
}
if (hasAuthorizedEmails) {
t.dropColumn("authorizedEmails");
}
});
}
}
}
+2 -1
View File
@@ -14,7 +14,8 @@ export const CertificateBodiesSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
certId: z.string().uuid(), certId: z.string().uuid(),
encryptedCertificate: zodBuffer encryptedCertificate: zodBuffer,
encryptedCertificateChain: zodBuffer.nullable().optional()
}); });
export type TCertificateBodies = z.infer<typeof CertificateBodiesSchema>; export type TCertificateBodies = z.infer<typeof CertificateBodiesSchema>;
@@ -5,6 +5,8 @@
import { z } from "zod"; import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const CertificateSecretsSchema = z.object({ export const CertificateSecretsSchema = z.object({
@@ -12,8 +14,7 @@ export const CertificateSecretsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
certId: z.string().uuid(), certId: z.string().uuid(),
pk: z.string(), encryptedPrivateKey: zodBuffer
sk: z.string()
}); });
export type TCertificateSecrets = z.infer<typeof CertificateSecretsSchema>; export type TCertificateSecrets = z.infer<typeof CertificateSecretsSchema>;
+2 -1
View File
@@ -24,7 +24,8 @@ export const CertificatesSchema = z.object({
caCertId: z.string().uuid(), caCertId: z.string().uuid(),
certificateTemplateId: z.string().uuid().nullable().optional(), certificateTemplateId: z.string().uuid().nullable().optional(),
keyUsages: z.string().array().nullable().optional(), keyUsages: z.string().array().nullable().optional(),
extendedKeyUsages: z.string().array().nullable().optional() extendedKeyUsages: z.string().array().nullable().optional(),
pkiSubscriberId: z.string().uuid().nullable().optional()
}); });
export type TCertificates = z.infer<typeof CertificatesSchema>; export type TCertificates = z.infer<typeof CertificatesSchema>;
+2 -1
View File
@@ -27,7 +27,8 @@ export const DynamicSecretsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
encryptedInput: zodBuffer, encryptedInput: zodBuffer,
projectGatewayId: z.string().uuid().nullable().optional() projectGatewayId: z.string().uuid().nullable().optional(),
gatewayId: z.string().uuid().nullable().optional()
}); });
export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>; export type TDynamicSecrets = z.infer<typeof DynamicSecretsSchema>;
@@ -29,7 +29,8 @@ export const IdentityKubernetesAuthsSchema = z.object({
allowedNames: z.string(), allowedNames: z.string(),
allowedAudience: z.string(), allowedAudience: z.string(),
encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(), encryptedKubernetesTokenReviewerJwt: zodBuffer.nullable().optional(),
encryptedKubernetesCaCertificate: zodBuffer.nullable().optional() encryptedKubernetesCaCertificate: zodBuffer.nullable().optional(),
gatewayId: z.string().uuid().nullable().optional()
}); });
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>; export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
@@ -0,0 +1,32 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const IdentityLdapAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
identityId: z.string().uuid(),
encryptedBindDN: zodBuffer,
encryptedBindPass: zodBuffer,
encryptedLdapCaCertificate: zodBuffer.nullable().optional(),
url: z.string(),
searchBase: z.string(),
searchFilter: z.string(),
allowedFields: z.unknown().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
export type TIdentityLdapAuthsInsert = Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>;
export type TIdentityLdapAuthsUpdate = Partial<Omit<z.input<typeof IdentityLdapAuthsSchema>, TImmutableDBKeys>>;
@@ -0,0 +1,26 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const IdentityOciAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
createdAt: z.date(),
updatedAt: z.date(),
identityId: z.string().uuid(),
type: z.string(),
tenancyOcid: z.string(),
allowedUsernames: z.string().nullable().optional()
});
export type TIdentityOciAuths = z.infer<typeof IdentityOciAuthsSchema>;
export type TIdentityOciAuthsInsert = Omit<z.input<typeof IdentityOciAuthsSchema>, TImmutableDBKeys>;
export type TIdentityOciAuthsUpdate = Partial<Omit<z.input<typeof IdentityOciAuthsSchema>, TImmutableDBKeys>>;
+2
View File
@@ -43,6 +43,7 @@ export * from "./identity-gcp-auths";
export * from "./identity-jwt-auths"; export * from "./identity-jwt-auths";
export * from "./identity-kubernetes-auths"; export * from "./identity-kubernetes-auths";
export * from "./identity-metadata"; export * from "./identity-metadata";
export * from "./identity-oci-auths";
export * from "./identity-oidc-auths"; export * from "./identity-oidc-auths";
export * from "./identity-org-memberships"; export * from "./identity-org-memberships";
export * from "./identity-project-additional-privilege"; export * from "./identity-project-additional-privilege";
@@ -75,6 +76,7 @@ export * from "./organizations";
export * from "./pki-alerts"; export * from "./pki-alerts";
export * from "./pki-collection-items"; export * from "./pki-collection-items";
export * from "./pki-collections"; export * from "./pki-collections";
export * from "./pki-subscribers";
export * from "./project-bots"; export * from "./project-bots";
export * from "./project-environments"; export * from "./project-environments";
export * from "./project-gateways"; export * from "./project-gateways";
+12 -2
View File
@@ -21,6 +21,7 @@ export enum TableName {
CertificateBody = "certificate_bodies", CertificateBody = "certificate_bodies",
CertificateSecret = "certificate_secrets", CertificateSecret = "certificate_secrets",
CertificateTemplate = "certificate_templates", CertificateTemplate = "certificate_templates",
PkiSubscriber = "pki_subscribers",
PkiAlert = "pki_alerts", PkiAlert = "pki_alerts",
PkiCollection = "pki_collections", PkiCollection = "pki_collections",
PkiCollectionItem = "pki_collection_items", PkiCollectionItem = "pki_collection_items",
@@ -78,8 +79,10 @@ export enum TableName {
IdentityAzureAuth = "identity_azure_auths", IdentityAzureAuth = "identity_azure_auths",
IdentityUaClientSecret = "identity_ua_client_secrets", IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsAuth = "identity_aws_auths", IdentityAwsAuth = "identity_aws_auths",
IdentityOciAuth = "identity_oci_auths",
IdentityOidcAuth = "identity_oidc_auths", IdentityOidcAuth = "identity_oidc_auths",
IdentityJwtAuth = "identity_jwt_auths", IdentityJwtAuth = "identity_jwt_auths",
IdentityLdapAuth = "identity_ldap_auths",
IdentityOrgMembership = "identity_org_memberships", IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships", IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role", IdentityProjectMembershipRole = "identity_project_membership_role",
@@ -191,11 +194,16 @@ export enum OrgMembershipStatus {
} }
export enum ProjectMembershipRole { export enum ProjectMembershipRole {
// general
Admin = "admin", Admin = "admin",
Member = "member", Member = "member",
Custom = "custom", Custom = "custom",
Viewer = "viewer", Viewer = "viewer",
NoAccess = "no-access" NoAccess = "no-access",
// ssh
SshHostBootstrapper = "ssh-host-bootstrapper",
// kms
KmsCryptographicOperator = "cryptographic-operator"
} }
export enum SecretEncryptionAlgo { export enum SecretEncryptionAlgo {
@@ -232,8 +240,10 @@ export enum IdentityAuthMethod {
GCP_AUTH = "gcp-auth", GCP_AUTH = "gcp-auth",
AWS_AUTH = "aws-auth", AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth", AZURE_AUTH = "azure-auth",
OCI_AUTH = "oci-auth",
OIDC_AUTH = "oidc-auth", OIDC_AUTH = "oidc-auth",
JWT_AUTH = "jwt-auth" JWT_AUTH = "jwt-auth",
LDAP_AUTH = "ldap-auth"
} }
export enum ProjectType { export enum ProjectType {
+9 -1
View File
@@ -28,7 +28,15 @@ export const OrganizationsSchema = z.object({
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(), privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
privilegeUpgradeInitiatedAt: z.date().nullable().optional(), privilegeUpgradeInitiatedAt: z.date().nullable().optional(),
bypassOrgAuthEnabled: z.boolean().default(false), bypassOrgAuthEnabled: z.boolean().default(false),
userTokenExpiration: z.string().nullable().optional() userTokenExpiration: z.string().nullable().optional(),
secretsProductEnabled: z.boolean().default(true).nullable().optional(),
pkiProductEnabled: z.boolean().default(true).nullable().optional(),
kmsProductEnabled: z.boolean().default(true).nullable().optional(),
sshProductEnabled: z.boolean().default(true).nullable().optional(),
scannerProductEnabled: z.boolean().default(true).nullable().optional(),
shareSecretsProductEnabled: z.boolean().default(true).nullable().optional(),
maxSharedSecretLifetime: z.number().default(2592000).nullable().optional(),
maxSharedSecretViewLimit: z.number().nullable().optional()
}); });
export type TOrganizations = z.infer<typeof OrganizationsSchema>; export type TOrganizations = z.infer<typeof OrganizationsSchema>;
+27
View File
@@ -0,0 +1,27 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const PkiSubscribersSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
projectId: z.string(),
caId: z.string().uuid().nullable().optional(),
name: z.string(),
commonName: z.string(),
subjectAlternativeNames: z.string().array(),
ttl: z.string(),
keyUsages: z.string().array(),
extendedKeyUsages: z.string().array(),
status: z.string()
});
export type TPkiSubscribers = z.infer<typeof PkiSubscribersSchema>;
export type TPkiSubscribersInsert = Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>;
export type TPkiSubscribersUpdate = Partial<Omit<z.input<typeof PkiSubscribersSchema>, TImmutableDBKeys>>;
+3 -2
View File
@@ -12,10 +12,11 @@ export const ProjectTemplatesSchema = z.object({
name: z.string(), name: z.string(),
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
roles: z.unknown(), roles: z.unknown(),
environments: z.unknown(), environments: z.unknown().nullable().optional(),
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date() updatedAt: z.date(),
type: z.string().default("secret-manager")
}); });
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>; export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;
+2 -1
View File
@@ -27,7 +27,8 @@ export const ProjectsSchema = z.object({
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
type: z.string(), type: z.string(),
enforceCapitalization: z.boolean().default(false), enforceCapitalization: z.boolean().default(false),
hasDeleteProtection: z.boolean().default(true).nullable().optional() hasDeleteProtection: z.boolean().default(false).nullable().optional(),
secretSharing: z.boolean().default(true)
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;
+3 -1
View File
@@ -27,7 +27,9 @@ export const SecretSharingSchema = z.object({
password: z.string().nullable().optional(), password: z.string().nullable().optional(),
encryptedSecret: zodBuffer.nullable().optional(), encryptedSecret: zodBuffer.nullable().optional(),
identifier: z.string().nullable().optional(), identifier: z.string().nullable().optional(),
type: z.string().default("share") type: z.string().default("share"),
encryptedSalt: zodBuffer.nullable().optional(),
authorizedEmails: z.unknown().nullable().optional()
}); });
export type TSecretSharing = z.infer<typeof SecretSharingSchema>; export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
@@ -12,7 +12,8 @@ export const SshHostLoginUserMappingsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
sshHostLoginUserId: z.string().uuid(), sshHostLoginUserId: z.string().uuid(),
userId: z.string().uuid().nullable().optional() userId: z.string().uuid().nullable().optional(),
groupId: z.string().uuid().nullable().optional()
}); });
export type TSshHostLoginUserMappings = z.infer<typeof SshHostLoginUserMappingsSchema>; export type TSshHostLoginUserMappings = z.infer<typeof SshHostLoginUserMappingsSchema>;
@@ -2,6 +2,7 @@ import { z } from "zod";
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas"; import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types"; import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
import { writeLimit } 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";
@@ -18,6 +19,9 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
server.route({ server.route({
url: "/", url: "/",
method: "POST", method: "POST",
config: {
rateLimit: writeLimit
},
schema: { schema: {
body: z.object({ body: z.object({
permissions: z.any().array(), permissions: z.any().array(),
+6 -17
View File
@@ -121,14 +121,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
identity: z.object({ identity: z.object({
name: z.string(), name: z.string(),
id: z.string() id: z.string()
}), })
projects: z
.object({
name: z.string(),
id: z.string(),
slug: z.string()
})
.array()
}).array() }).array()
}) })
} }
@@ -158,17 +151,15 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
identity: z.object({ identity: z.object({
name: z.string(), name: z.string(),
id: z.string() id: z.string()
}), })
projectGatewayId: z.string()
}).array() }).array()
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]), onRequest: verifyAuth([AuthMode.IDENTITY_ACCESS_TOKEN, AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const gateways = await server.services.gateway.getProjectGateways({ const gateways = await server.services.gateway.listGateways({
projectId: req.params.projectId, orgPermission: req.permission
projectPermission: req.permission
}); });
return { gateways }; return { gateways };
} }
@@ -216,8 +207,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
id: z.string() id: z.string()
}), }),
body: z.object({ body: z.object({
name: slugSchema({ field: "name" }).optional(), name: slugSchema({ field: "name" }).optional()
projectIds: z.string().array().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -230,8 +220,7 @@ export const registerGatewayRouter = async (server: FastifyZodProvider) => {
const gateway = await server.services.gateway.updateGatewayById({ const gateway = await server.services.gateway.updateGatewayById({
orgPermission: req.permission, orgPermission: req.permission,
id: req.params.id, id: req.params.id,
name: req.body.name, name: req.body.name
projectIds: req.body.projectIds
}); });
return { gateway }; return { gateway };
} }
+3
View File
@@ -98,6 +98,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/login", url: "/login",
method: "POST", method: "POST",
config: {
rateLimit: writeLimit
},
schema: { schema: {
body: z.object({ body: z.object({
organizationSlug: z.string().trim() organizationSlug: z.string().trim()
@@ -1,9 +1,8 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } 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 { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns"; import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs"; import { ApiDocsTags, ProjectTemplates } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -35,6 +34,7 @@ const SanitizedProjectTemplateSchema = ProjectTemplatesSchema.extend({
position: z.number().min(1) position: z.number().min(1)
}) })
.array() .array()
.nullable()
}); });
const ProjectTemplateRolesSchema = z const ProjectTemplateRolesSchema = z
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
hide: false, hide: false,
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.", description: "List project templates for the current organization.",
querystring: z.object({
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
}),
response: { response: {
200: z.object({ 200: z.object({
projectTemplates: SanitizedProjectTemplateSchema.array() projectTemplates: SanitizedProjectTemplateSchema.array()
@@ -112,7 +115,8 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission); const { type } = req.query;
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission, type);
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name)); const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
@@ -184,6 +188,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "Create a project template.", description: "Create a project template.",
body: z.object({ body: z.object({
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
name: slugSchema({ field: "name" }) name: slugSchema({ field: "name" })
.refine((val) => !isInfisicalProjectTemplate(val), { .refine((val) => !isInfisicalProjectTemplate(val), {
message: `The requested project template name is reserved.` message: `The requested project template name is reserved.`
@@ -191,9 +196,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
.describe(ProjectTemplates.CREATE.name), .describe(ProjectTemplates.CREATE.name),
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description), description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles), roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
environments: ProjectTemplateEnvironmentsSchema.default(ProjectTemplateDefaultEnvironments).describe( environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
ProjectTemplates.CREATE.environments
)
}), }),
response: { response: {
200: z.object({ 200: z.object({
+9
View File
@@ -166,6 +166,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/redirect/saml2/organizations/:orgSlug", url: "/redirect/saml2/organizations/:orgSlug",
method: "GET", method: "GET",
config: {
rateLimit: readLimit
},
schema: { schema: {
params: z.object({ params: z.object({
orgSlug: z.string().trim() orgSlug: z.string().trim()
@@ -192,6 +195,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/redirect/saml2/:samlConfigId", url: "/redirect/saml2/:samlConfigId",
method: "GET", method: "GET",
config: {
rateLimit: readLimit
},
schema: { schema: {
params: z.object({ params: z.object({
samlConfigId: z.string().trim() samlConfigId: z.string().trim()
@@ -218,6 +224,9 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/saml2/:samlConfigId", url: "/saml2/:samlConfigId",
method: "POST", method: "POST",
config: {
rateLimit: writeLimit
},
schema: { schema: {
params: z.object({ params: z.object({
samlConfigId: z.string().trim() samlConfigId: z.string().trim()
+3
View File
@@ -196,6 +196,9 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
server.route({ server.route({
url: "/Users", url: "/Users",
method: "POST", method: "POST",
config: {
rateLimit: writeLimit
},
schema: { schema: {
body: z.object({ body: z.object({
schemas: z.array(z.string()), schemas: z.array(z.string()),
@@ -1,11 +1,11 @@
import { z } from "zod"; import { z } from "zod";
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas"; import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
import { canUseSecretScanning } from "@app/ee/services/secret-scanning/secret-scanning-fns";
import { import {
SecretScanningResolvedStatus, SecretScanningResolvedStatus,
SecretScanningRiskStatus SecretScanningRiskStatus
} from "@app/ee/services/secret-scanning/secret-scanning-types"; } from "@app/ee/services/secret-scanning/secret-scanning-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { OrderByDirection } from "@app/lib/types"; import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -23,14 +23,14 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
body: z.object({ organizationId: z.string().trim() }), body: z.object({ organizationId: z.string().trim() }),
response: { response: {
200: z.object({ 200: z.object({
sessionId: z.string() sessionId: z.string(),
gitAppSlug: z.string()
}) })
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const appCfg = getConfig(); if (!canUseSecretScanning(req.auth.orgId)) {
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
throw new BadRequestError({ throw new BadRequestError({
message: "Secret scanning is temporarily unavailable." message: "Secret scanning is temporarily unavailable."
}); });
@@ -97,7 +97,7 @@ export const registerSshCertificateTemplateRouter = async (server: FastifyZodPro
allowCustomKeyIds: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowCustomKeyIds) allowCustomKeyIds: z.boolean().describe(SSH_CERTIFICATE_TEMPLATES.CREATE.allowCustomKeyIds)
}) })
.refine((data) => ms(data.maxTTL) >= ms(data.ttl), { .refine((data) => ms(data.maxTTL) >= ms(data.ttl), {
message: "Max TLL must be greater than or equal to TTL", message: "Max TTL must be greater than or equal to TTL",
path: ["maxTTL"] path: ["maxTTL"]
}), }),
response: { response: {
+1 -1
View File
@@ -73,7 +73,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const host = await server.services.sshHost.getSshHost({ const host = await server.services.sshHost.getSshHostById({
sshHostId: req.params.sshHostId, sshHostId: req.params.sshHostId,
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@@ -19,9 +19,10 @@ import { TProjectPermission } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types"; import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types"; import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { TAllowedFields } from "@app/services/identity-ldap-auth/identity-ldap-auth-types";
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types"; import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums"; import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
import { import {
@@ -119,44 +120,66 @@ export enum EventType {
CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth", CREATE_TOKEN_IDENTITY_TOKEN_AUTH = "create-token-identity-token-auth",
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",
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",
REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth", REVOKE_IDENTITY_TOKEN_AUTH = "revoke-identity-token-auth",
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth", LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth", ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth", UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth", GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth", REVOKE_IDENTITY_KUBERNETES_AUTH = "revoke-identity-kubernetes-auth",
LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth", LOGIN_IDENTITY_OIDC_AUTH = "login-identity-oidc-auth",
ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth", ADD_IDENTITY_OIDC_AUTH = "add-identity-oidc-auth",
UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth", UPDATE_IDENTITY_OIDC_AUTH = "update-identity-oidc-auth",
GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth", GET_IDENTITY_OIDC_AUTH = "get-identity-oidc-auth",
REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth", REVOKE_IDENTITY_OIDC_AUTH = "revoke-identity-oidc-auth",
LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth", LOGIN_IDENTITY_JWT_AUTH = "login-identity-jwt-auth",
ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth", ADD_IDENTITY_JWT_AUTH = "add-identity-jwt-auth",
UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth", UPDATE_IDENTITY_JWT_AUTH = "update-identity-jwt-auth",
GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth", GET_IDENTITY_JWT_AUTH = "get-identity-jwt-auth",
REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth", REVOKE_IDENTITY_JWT_AUTH = "revoke-identity-jwt-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret", CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret", REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret", GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id", GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET_BY_ID = "get-identity-universal-auth-client-secret-by-id",
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth", LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth", ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth", UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth", REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth", GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth", LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth", ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth", UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth", REVOKE_IDENTITY_AWS_AUTH = "revoke-identity-aws-auth",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth", GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
LOGIN_IDENTITY_OCI_AUTH = "login-identity-oci-auth",
ADD_IDENTITY_OCI_AUTH = "add-identity-oci-auth",
UPDATE_IDENTITY_OCI_AUTH = "update-identity-oci-auth",
REVOKE_IDENTITY_OCI_AUTH = "revoke-identity-oci-auth",
GET_IDENTITY_OCI_AUTH = "get-identity-oci-auth",
LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth", LOGIN_IDENTITY_AZURE_AUTH = "login-identity-azure-auth",
ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth", ADD_IDENTITY_AZURE_AUTH = "add-identity-azure-auth",
UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth", UPDATE_IDENTITY_AZURE_AUTH = "update-identity-azure-auth",
GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth", GET_IDENTITY_AZURE_AUTH = "get-identity-azure-auth",
REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth", REVOKE_IDENTITY_AZURE_AUTH = "revoke-identity-azure-auth",
LOGIN_IDENTITY_LDAP_AUTH = "login-identity-ldap-auth",
ADD_IDENTITY_LDAP_AUTH = "add-identity-ldap-auth",
UPDATE_IDENTITY_LDAP_AUTH = "update-identity-ldap-auth",
GET_IDENTITY_LDAP_AUTH = "get-identity-ldap-auth",
REVOKE_IDENTITY_LDAP_AUTH = "revoke-identity-ldap-auth",
CREATE_ENVIRONMENT = "create-environment", CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
@@ -224,6 +247,8 @@ export enum EventType {
DELETE_CERT = "delete-cert", DELETE_CERT = "delete-cert",
REVOKE_CERT = "revoke-cert", REVOKE_CERT = "revoke-cert",
GET_CERT_BODY = "get-cert-body", GET_CERT_BODY = "get-cert-body",
GET_CERT_PRIVATE_KEY = "get-cert-private-key",
GET_CERT_BUNDLE = "get-cert-bundle",
CREATE_PKI_ALERT = "create-pki-alert", CREATE_PKI_ALERT = "create-pki-alert",
GET_PKI_ALERT = "get-pki-alert", GET_PKI_ALERT = "get-pki-alert",
UPDATE_PKI_ALERT = "update-pki-alert", UPDATE_PKI_ALERT = "update-pki-alert",
@@ -235,6 +260,13 @@ export enum EventType {
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items", GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item", ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item", DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
CREATE_PKI_SUBSCRIBER = "create-pki-subscriber",
UPDATE_PKI_SUBSCRIBER = "update-pki-subscriber",
DELETE_PKI_SUBSCRIBER = "delete-pki-subscriber",
GET_PKI_SUBSCRIBER = "get-pki-subscriber",
ISSUE_PKI_SUBSCRIBER_CERT = "issue-pki-subscriber-cert",
SIGN_PKI_SUBSCRIBER_CERT = "sign-pki-subscriber-cert",
LIST_PKI_SUBSCRIBER_CERTS = "list-pki-subscriber-certs",
CREATE_KMS = "create-kms", CREATE_KMS = "create-kms",
UPDATE_KMS = "update-kms", UPDATE_KMS = "update-kms",
DELETE_KMS = "delete-kms", DELETE_KMS = "delete-kms",
@@ -991,6 +1023,55 @@ interface GetIdentityAwsAuthEvent {
}; };
} }
interface LoginIdentityOciAuthEvent {
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
metadata: {
identityId: string;
identityOciAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityOciAuthEvent {
type: EventType.ADD_IDENTITY_OCI_AUTH;
metadata: {
identityId: string;
tenancyOcid: string;
allowedUsernames: string | null;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface DeleteIdentityOciAuthEvent {
type: EventType.REVOKE_IDENTITY_OCI_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityOciAuthEvent {
type: EventType.UPDATE_IDENTITY_OCI_AUTH;
metadata: {
identityId: string;
tenancyOcid?: string;
allowedUsernames: string | null;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityOciAuthEvent {
type: EventType.GET_IDENTITY_OCI_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityAzureAuthEvent { interface LoginIdentityAzureAuthEvent {
type: EventType.LOGIN_IDENTITY_AZURE_AUTH; type: EventType.LOGIN_IDENTITY_AZURE_AUTH;
metadata: { metadata: {
@@ -1040,6 +1121,55 @@ interface GetIdentityAzureAuthEvent {
}; };
} }
interface LoginIdentityLdapAuthEvent {
type: EventType.LOGIN_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
ldapUsername: string;
ldapEmail?: string;
};
}
interface AddIdentityLdapAuthEvent {
type: EventType.ADD_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url: string;
};
}
interface UpdateIdentityLdapAuthEvent {
type: EventType.UPDATE_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
allowedFields?: TAllowedFields[];
url?: string;
};
}
interface GetIdentityLdapAuthEvent {
type: EventType.GET_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
};
}
interface RevokeIdentityLdapAuthEvent {
type: EventType.REVOKE_IDENTITY_LDAP_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityOidcAuthEvent { interface LoginIdentityOidcAuthEvent {
type: EventType.LOGIN_IDENTITY_OIDC_AUTH; type: EventType.LOGIN_IDENTITY_OIDC_AUTH;
metadata: { metadata: {
@@ -1798,6 +1928,24 @@ interface GetCertBody {
}; };
} }
interface GetCertPrivateKey {
type: EventType.GET_CERT_PRIVATE_KEY;
metadata: {
certId: string;
cn: string;
serialNumber: string;
};
}
interface GetCertBundle {
type: EventType.GET_CERT_BUNDLE;
metadata: {
certId: string;
cn: string;
serialNumber: string;
};
}
interface CreatePkiAlert { interface CreatePkiAlert {
type: EventType.CREATE_PKI_ALERT; type: EventType.CREATE_PKI_ALERT;
metadata: { metadata: {
@@ -1887,6 +2035,77 @@ interface DeletePkiCollectionItem {
}; };
} }
interface CreatePkiSubscriber {
type: EventType.CREATE_PKI_SUBSCRIBER;
metadata: {
pkiSubscriberId: string;
caId?: string;
name: string;
commonName: string;
ttl: string;
subjectAlternativeNames: string[];
keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
};
}
interface UpdatePkiSubscriber {
type: EventType.UPDATE_PKI_SUBSCRIBER;
metadata: {
pkiSubscriberId: string;
caId?: string;
name?: string;
commonName?: string;
ttl?: string;
subjectAlternativeNames?: string[];
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
};
}
interface DeletePkiSubscriber {
type: EventType.DELETE_PKI_SUBSCRIBER;
metadata: {
pkiSubscriberId: string;
name: string;
};
}
interface GetPkiSubscriber {
type: EventType.GET_PKI_SUBSCRIBER;
metadata: {
pkiSubscriberId: string;
name: string;
};
}
interface IssuePkiSubscriberCert {
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT;
metadata: {
subscriberId: string;
name: string;
serialNumber: string;
};
}
interface SignPkiSubscriberCert {
type: EventType.SIGN_PKI_SUBSCRIBER_CERT;
metadata: {
subscriberId: string;
name: string;
serialNumber: string;
};
}
interface ListPkiSubscriberCerts {
type: EventType.LIST_PKI_SUBSCRIBER_CERTS;
metadata: {
subscriberId: string;
name: string;
projectId: string;
};
}
interface CreateKmsEvent { interface CreateKmsEvent {
type: EventType.CREATE_KMS; type: EventType.CREATE_KMS;
metadata: { metadata: {
@@ -2823,6 +3042,11 @@ export type Event =
| UpdateIdentityAwsAuthEvent | UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent | GetIdentityAwsAuthEvent
| DeleteIdentityAwsAuthEvent | DeleteIdentityAwsAuthEvent
| LoginIdentityOciAuthEvent
| AddIdentityOciAuthEvent
| UpdateIdentityOciAuthEvent
| GetIdentityOciAuthEvent
| DeleteIdentityOciAuthEvent
| LoginIdentityAzureAuthEvent | LoginIdentityAzureAuthEvent
| AddIdentityAzureAuthEvent | AddIdentityAzureAuthEvent
| DeleteIdentityAzureAuthEvent | DeleteIdentityAzureAuthEvent
@@ -2838,6 +3062,11 @@ export type Event =
| UpdateIdentityJwtAuthEvent | UpdateIdentityJwtAuthEvent
| GetIdentityJwtAuthEvent | GetIdentityJwtAuthEvent
| DeleteIdentityJwtAuthEvent | DeleteIdentityJwtAuthEvent
| LoginIdentityLdapAuthEvent
| AddIdentityLdapAuthEvent
| UpdateIdentityLdapAuthEvent
| GetIdentityLdapAuthEvent
| RevokeIdentityLdapAuthEvent
| CreateEnvironmentEvent | CreateEnvironmentEvent
| GetEnvironmentEvent | GetEnvironmentEvent
| UpdateEnvironmentEvent | UpdateEnvironmentEvent
@@ -2897,6 +3126,8 @@ export type Event =
| DeleteCert | DeleteCert
| RevokeCert | RevokeCert
| GetCertBody | GetCertBody
| GetCertPrivateKey
| GetCertBundle
| CreatePkiAlert | CreatePkiAlert
| GetPkiAlert | GetPkiAlert
| UpdatePkiAlert | UpdatePkiAlert
@@ -2908,6 +3139,13 @@ export type Event =
| GetPkiCollectionItems | GetPkiCollectionItems
| AddPkiCollectionItem | AddPkiCollectionItem
| DeletePkiCollectionItem | DeletePkiCollectionItem
| CreatePkiSubscriber
| UpdatePkiSubscriber
| DeletePkiSubscriber
| GetPkiSubscriber
| IssuePkiSubscriberCert
| SignPkiSubscriberCert
| ListPkiSubscriberCerts
| CreateKmsEvent | CreateKmsEvent
| UpdateKmsEvent | UpdateKmsEvent
| DeleteKmsEvent | DeleteKmsEvent
@@ -24,8 +24,16 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
if (net.isIPv4(el)) { if (net.isIPv4(el)) {
exclusiveIps.push(el); exclusiveIps.push(el);
} else { } else {
const resolvedIps = await dns.resolve4(el); try {
exclusiveIps.push(...resolvedIps); const resolvedIps = await dns.resolve4(el);
exclusiveIps.push(...resolvedIps);
} catch (error) {
// only try lookup if not found
if ((error as { code: string })?.code !== "ENOTFOUND") throw error;
const resolvedIps = (await dns.lookup(el, { all: true, family: 4 })).map(({ address }) => address);
exclusiveIps.push(...resolvedIps);
}
} }
} }
} }
@@ -38,8 +46,16 @@ export const verifyHostInputValidity = async (host: string, isGateway = false) =
if (normalizedHost === "localhost" || normalizedHost === "host.docker.internal") { if (normalizedHost === "localhost" || normalizedHost === "host.docker.internal") {
throw new BadRequestError({ message: "Invalid db host" }); throw new BadRequestError({ message: "Invalid db host" });
} }
const resolvedIps = await dns.resolve4(host); try {
inputHostIps.push(...resolvedIps); const resolvedIps = await dns.resolve4(host);
inputHostIps.push(...resolvedIps);
} catch (error) {
// only try lookup if not found
if ((error as { code: string })?.code !== "ENOTFOUND") throw error;
const resolvedIps = (await dns.lookup(host, { all: true, family: 4 })).map(({ address }) => address);
inputHostIps.push(...resolvedIps);
}
} }
if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) { if (!isGateway && !(appCfg.DYNAMIC_SECRET_ALLOW_INTERNAL_IP || appCfg.ALLOW_INTERNAL_IP_CONNECTIONS)) {
@@ -17,7 +17,8 @@ import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-fold
import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal"; import { TDynamicSecretLeaseDALFactory } from "../dynamic-secret-lease/dynamic-secret-lease-dal";
import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue"; import { TDynamicSecretLeaseQueueServiceFactory } from "../dynamic-secret-lease/dynamic-secret-lease-queue";
import { TProjectGatewayDALFactory } from "../gateway/project-gateway-dal"; import { TGatewayDALFactory } from "../gateway/gateway-dal";
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TDynamicSecretDALFactory } from "./dynamic-secret-dal"; import { TDynamicSecretDALFactory } from "./dynamic-secret-dal";
import { import {
DynamicSecretStatus, DynamicSecretStatus,
@@ -44,9 +45,9 @@ type TDynamicSecretServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">; folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findBySecretPathMultiEnv">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">; projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
projectGatewayDAL: Pick<TProjectGatewayDALFactory, "findOne">; gatewayDAL: Pick<TGatewayDALFactory, "findOne" | "find">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">; resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
}; };
@@ -62,7 +63,7 @@ export const dynamicSecretServiceFactory = ({
dynamicSecretQueueService, dynamicSecretQueueService,
projectDAL, projectDAL,
kmsService, kmsService,
projectGatewayDAL, gatewayDAL,
resourceMetadataDAL resourceMetadataDAL
}: TDynamicSecretServiceFactoryDep) => { }: TDynamicSecretServiceFactoryDep) => {
const create = async ({ const create = async ({
@@ -117,15 +118,31 @@ export const dynamicSecretServiceFactory = ({
const inputs = await selectedProvider.validateProviderInputs(provider.inputs); const inputs = await selectedProvider.validateProviderInputs(provider.inputs);
let selectedGatewayId: string | null = null; let selectedGatewayId: string | null = null;
if (inputs && typeof inputs === "object" && "projectGatewayId" in inputs && inputs.projectGatewayId) { if (inputs && typeof inputs === "object" && "gatewayId" in inputs && inputs.gatewayId) {
const projectGatewayId = inputs.projectGatewayId as string; const gatewayId = inputs.gatewayId as string;
const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
if (!projectGateway)
if (!gateway) {
throw new NotFoundError({ throw new NotFoundError({
message: `Project gateway with ${projectGatewayId} not found` message: `Gateway with ID ${gatewayId} not found`
}); });
selectedGatewayId = projectGateway.id; }
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor,
actorId,
gateway.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
);
selectedGatewayId = gateway.id;
} }
const isConnected = await selectedProvider.validateConnection(provider.inputs); const isConnected = await selectedProvider.validateConnection(provider.inputs);
@@ -146,7 +163,7 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
folderId: folder.id, folderId: folder.id,
name, name,
projectGatewayId: selectedGatewayId gatewayId: selectedGatewayId
}, },
tx tx
); );
@@ -255,20 +272,30 @@ export const dynamicSecretServiceFactory = ({
const updatedInput = await selectedProvider.validateProviderInputs(newInput); const updatedInput = await selectedProvider.validateProviderInputs(newInput);
let selectedGatewayId: string | null = null; let selectedGatewayId: string | null = null;
if ( if (updatedInput && typeof updatedInput === "object" && "gatewayId" in updatedInput && updatedInput?.gatewayId) {
updatedInput && const gatewayId = updatedInput.gatewayId as string;
typeof updatedInput === "object" &&
"projectGatewayId" in updatedInput &&
updatedInput?.projectGatewayId
) {
const projectGatewayId = updatedInput.projectGatewayId as string;
const projectGateway = await projectGatewayDAL.findOne({ id: projectGatewayId, projectId }); const [gateway] = await gatewayDAL.find({ id: gatewayId, orgId: actorOrgId });
if (!projectGateway) if (!gateway) {
throw new NotFoundError({ throw new NotFoundError({
message: `Project gateway with ${projectGatewayId} not found` message: `Gateway with ID ${gatewayId} not found`
}); });
selectedGatewayId = projectGateway.id; }
const { permission: orgPermission } = await permissionService.getOrgPermission(
actor,
actorId,
gateway.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(orgPermission).throwUnlessCan(
OrgPermissionGatewayActions.AttachGateways,
OrgPermissionSubjects.Gateway
);
selectedGatewayId = gateway.id;
} }
const isConnected = await selectedProvider.validateConnection(newInput); const isConnected = await selectedProvider.validateConnection(newInput);
@@ -284,7 +311,7 @@ export const dynamicSecretServiceFactory = ({
defaultTTL, defaultTTL,
name: newName ?? name, name: newName ?? name,
status: null, status: null,
projectGatewayId: selectedGatewayId gatewayId: selectedGatewayId
}, },
tx tx
); );
@@ -18,7 +18,7 @@ import { SqlDatabaseProvider } from "./sql-database";
import { TotpProvider } from "./totp"; import { TotpProvider } from "./totp";
type TBuildDynamicSecretProviderDTO = { type TBuildDynamicSecretProviderDTO = {
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTls">; gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
}; };
export const buildDynamicSecretProviders = ({ export const buildDynamicSecretProviders = ({
@@ -137,7 +137,7 @@ export const DynamicSecretSqlDBSchema = z.object({
revocationStatement: z.string().trim(), revocationStatement: z.string().trim(),
renewStatement: z.string().trim().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional(), ca: z.string().optional(),
projectGatewayId: z.string().nullable().optional() gatewayId: z.string().nullable().optional()
}); });
export const DynamicSecretCassandraSchema = z.object({ export const DynamicSecretCassandraSchema = z.object({
@@ -112,14 +112,14 @@ const generateUsername = (provider: SqlProviders) => {
}; };
type TSqlDatabaseProviderDTO = { type TSqlDatabaseProviderDTO = {
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTls">; gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
}; };
export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => { export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => { const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs); const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.projectGatewayId)); const [hostIp] = await verifyHostInputValidity(providerInputs.host, Boolean(providerInputs.gatewayId));
validateHandlebarTemplate("SQL creation", providerInputs.creationStatement, { validateHandlebarTemplate("SQL creation", providerInputs.creationStatement, {
allowedExpressions: (val) => ["username", "password", "expiration", "database"].includes(val) allowedExpressions: (val) => ["username", "password", "expiration", "database"].includes(val)
}); });
@@ -168,7 +168,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>, providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>,
gatewayCallback: (host: string, port: number) => Promise<void> gatewayCallback: (host: string, port: number) => Promise<void>
) => { ) => {
const relayDetails = await gatewayService.fnGetGatewayClientTls(providerInputs.projectGatewayId as string); const relayDetails = await gatewayService.fnGetGatewayClientTlsByGatewayId(providerInputs.gatewayId as string);
const [relayHost, relayPort] = relayDetails.relayAddress.split(":"); const [relayHost, relayPort] = relayDetails.relayAddress.split(":");
await withGatewayProxy( await withGatewayProxy(
async (port) => { async (port) => {
@@ -202,7 +202,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await db.destroy(); await db.destroy();
}; };
if (providerInputs.projectGatewayId) { if (providerInputs.gatewayId) {
await gatewayProxyWrapper(providerInputs, gatewayCallback); await gatewayProxyWrapper(providerInputs, gatewayCallback);
} else { } else {
await gatewayCallback(); await gatewayCallback();
@@ -238,7 +238,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await db.destroy(); await db.destroy();
} }
}; };
if (providerInputs.projectGatewayId) { if (providerInputs.gatewayId) {
await gatewayProxyWrapper(providerInputs, gatewayCallback); await gatewayProxyWrapper(providerInputs, gatewayCallback);
} else { } else {
await gatewayCallback(); await gatewayCallback();
@@ -265,7 +265,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await db.destroy(); await db.destroy();
} }
}; };
if (providerInputs.projectGatewayId) { if (providerInputs.gatewayId) {
await gatewayProxyWrapper(providerInputs, gatewayCallback); await gatewayProxyWrapper(providerInputs, gatewayCallback);
} else { } else {
await gatewayCallback(); await gatewayCallback();
@@ -301,7 +301,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
await db.destroy(); await db.destroy();
} }
}; };
if (providerInputs.projectGatewayId) { if (providerInputs.gatewayId) {
await gatewayProxyWrapper(providerInputs, gatewayCallback); await gatewayProxyWrapper(providerInputs, gatewayCallback);
} else { } else {
await gatewayCallback(); await gatewayCallback();
+24 -59
View File
@@ -1,37 +1,34 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas"; import { GatewaysSchema, TableName, TGateways } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
buildFindFilter,
ormify,
selectAllTableCols,
sqlNestRelationships,
TFindFilter,
TFindOpt
} from "@app/lib/knex";
export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>; export type TGatewayDALFactory = ReturnType<typeof gatewayDALFactory>;
export const gatewayDALFactory = (db: TDbClient) => { export const gatewayDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.Gateway); const orm = ormify(db, TableName.Gateway);
const find = async (filter: TFindFilter<TGateways>, { offset, limit, sort, tx }: TFindOpt<TGateways> = {}) => { const find = async (
filter: TFindFilter<TGateways> & { orgId?: string },
{ offset, limit, sort, tx }: TFindOpt<TGateways> = {}
) => {
try { try {
const query = (tx || db)(TableName.Gateway) const query = (tx || db)(TableName.Gateway)
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter(filter)) .where(buildFindFilter(filter, TableName.Gateway, ["orgId"]))
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`) .join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
.leftJoin(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`) .join(
.leftJoin(TableName.Project, `${TableName.Project}.id`, `${TableName.ProjectGateway}.projectId`) TableName.IdentityOrgMembership,
`${TableName.IdentityOrgMembership}.identityId`,
`${TableName.Gateway}.identityId`
)
.select(selectAllTableCols(TableName.Gateway)) .select(selectAllTableCols(TableName.Gateway))
.select( .select(db.ref("orgId").withSchema(TableName.IdentityOrgMembership).as("identityOrgId"))
db.ref("name").withSchema(TableName.Identity).as("identityName"), .select(db.ref("name").withSchema(TableName.Identity).as("identityName"));
db.ref("name").withSchema(TableName.Project).as("projectName"),
db.ref("slug").withSchema(TableName.Project).as("projectSlug"), if (filter.orgId) {
db.ref("id").withSchema(TableName.Project).as("projectId") void query.where(`${TableName.IdentityOrgMembership}.orgId`, filter.orgId);
); }
if (limit) void query.limit(limit); if (limit) void query.limit(limit);
if (offset) void query.offset(offset); if (offset) void query.offset(offset);
if (sort) { if (sort) {
@@ -39,48 +36,16 @@ export const gatewayDALFactory = (db: TDbClient) => {
} }
const docs = await query; const docs = await query;
return sqlNestRelationships({
data: docs, return docs.map((el) => ({
key: "id", ...GatewaysSchema.parse(el),
parentMapper: (data) => ({ orgId: el.identityOrgId as string, // todo(daniel): figure out why typescript is not inferring this as a string
...GatewaysSchema.parse(data), identity: { id: el.identityId, name: el.identityName }
identity: { id: data.identityId, name: data.identityName } }));
}),
childrenMapper: [
{
key: "projectId",
label: "projects" as const,
mapper: ({ projectId, projectName, projectSlug }) => ({
id: projectId,
name: projectName,
slug: projectSlug
})
}
]
});
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` }); throw new DatabaseError({ error, name: `${TableName.Gateway}: Find` });
} }
}; };
const findByProjectId = async (projectId: string, tx?: Knex) => { return { ...orm, find };
try {
const query = (tx || db)(TableName.Gateway)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Gateway}.identityId`)
.join(TableName.ProjectGateway, `${TableName.ProjectGateway}.gatewayId`, `${TableName.Gateway}.id`)
.select(selectAllTableCols(TableName.Gateway))
.select(
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("id").withSchema(TableName.ProjectGateway).as("projectGatewayId")
)
.where({ [`${TableName.ProjectGateway}.projectId` as "projectId"]: projectId });
const docs = await query;
return docs.map((el) => ({ ...el, identity: { id: el.identityId, name: el.identityName } }));
} catch (error) {
throw new DatabaseError({ error, name: `${TableName.Gateway}: Find by project id` });
}
};
return { ...orm, find, findByProjectId };
}; };
@@ -4,7 +4,6 @@ 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 { ActionProjectType } 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 { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -27,17 +26,14 @@ import { TGatewayDALFactory } from "./gateway-dal";
import { import {
TExchangeAllocatedRelayAddressDTO, TExchangeAllocatedRelayAddressDTO,
TGetGatewayByIdDTO, TGetGatewayByIdDTO,
TGetProjectGatewayByIdDTO,
THeartBeatDTO, THeartBeatDTO,
TListGatewaysDTO, TListGatewaysDTO,
TUpdateGatewayByIdDTO TUpdateGatewayByIdDTO
} from "./gateway-types"; } from "./gateway-types";
import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal"; import { TOrgGatewayConfigDALFactory } from "./org-gateway-config-dal";
import { TProjectGatewayDALFactory } from "./project-gateway-dal";
type TGatewayServiceFactoryDep = { type TGatewayServiceFactoryDep = {
gatewayDAL: TGatewayDALFactory; gatewayDAL: TGatewayDALFactory;
projectGatewayDAL: TProjectGatewayDALFactory;
orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">; orgGatewayConfigDAL: Pick<TOrgGatewayConfigDALFactory, "findOne" | "create" | "transaction" | "findById">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">; licenseService: Pick<TLicenseServiceFactory, "onPremFeatures" | "getPlan">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "decryptWithRootKey">;
@@ -57,8 +53,7 @@ export const gatewayServiceFactory = ({
kmsService, kmsService,
permissionService, permissionService,
orgGatewayConfigDAL, orgGatewayConfigDAL,
keyStore, keyStore
projectGatewayDAL
}: TGatewayServiceFactoryDep) => { }: TGatewayServiceFactoryDep) => {
const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => { const $validateOrgAccessToGateway = async (orgId: string, actorId: string, actorAuthMethod: ActorAuthMethod) => {
// if (!licenseService.onPremFeatures.gateway) { // if (!licenseService.onPremFeatures.gateway) {
@@ -526,7 +521,7 @@ export const gatewayServiceFactory = ({
return gateway; return gateway;
}; };
const updateGatewayById = async ({ orgPermission, id, name, projectIds }: TUpdateGatewayByIdDTO) => { const updateGatewayById = async ({ orgPermission, id, name }: TUpdateGatewayByIdDTO) => {
const { permission } = await permissionService.getOrgPermission( const { permission } = await permissionService.getOrgPermission(
orgPermission.type, orgPermission.type,
orgPermission.id, orgPermission.id,
@@ -543,15 +538,6 @@ export const gatewayServiceFactory = ({
const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name }); const [gateway] = await gatewayDAL.update({ id, orgGatewayRootCaId: orgGatewayConfig.id }, { name });
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${id} not found.` });
if (projectIds) {
await projectGatewayDAL.transaction(async (tx) => {
await projectGatewayDAL.delete({ gatewayId: gateway.id }, tx);
await projectGatewayDAL.insertMany(
projectIds.map((el) => ({ gatewayId: gateway.id, projectId: el })),
tx
);
});
}
return gateway; return gateway;
}; };
@@ -576,27 +562,7 @@ export const gatewayServiceFactory = ({
return gateway; return gateway;
}; };
const getProjectGateways = async ({ projectId, projectPermission }: TGetProjectGatewayByIdDTO) => { const fnGetGatewayClientTlsByGatewayId = async (gatewayId: string) => {
await permissionService.getProjectPermission({
projectId,
actor: projectPermission.type,
actorId: projectPermission.id,
actorOrgId: projectPermission.orgId,
actorAuthMethod: projectPermission.authMethod,
actionProjectType: ActionProjectType.Any
});
const gateways = await gatewayDAL.findByProjectId(projectId);
return gateways;
};
// this has no permission check and used for dynamic secrets directly
// assumes permission check is already done
const fnGetGatewayClientTls = async (projectGatewayId: string) => {
const projectGateway = await projectGatewayDAL.findById(projectGatewayId);
if (!projectGateway) throw new NotFoundError({ message: `Project gateway with ID ${projectGatewayId} not found.` });
const { gatewayId } = projectGateway;
const gateway = await gatewayDAL.findById(gatewayId); const gateway = await gatewayDAL.findById(gatewayId);
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
@@ -645,8 +611,7 @@ export const gatewayServiceFactory = ({
getGatewayById, getGatewayById,
updateGatewayById, updateGatewayById,
deleteGatewayById, deleteGatewayById,
getProjectGateways, fnGetGatewayClientTlsByGatewayId,
fnGetGatewayClientTls,
heartbeat heartbeat
}; };
}; };
@@ -20,7 +20,6 @@ export type TGetGatewayByIdDTO = {
export type TUpdateGatewayByIdDTO = { export type TUpdateGatewayByIdDTO = {
id: string; id: string;
name?: string; name?: string;
projectIds?: string[];
orgPermission: OrgServiceActor; orgPermission: OrgServiceActor;
}; };
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TProjectGatewayDALFactory = ReturnType<typeof projectGatewayDALFactory>;
export const projectGatewayDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ProjectGateway);
return orm;
};
@@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Octokit } from "@octokit/core"; import { Octokit } from "@octokit/core";
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql"; import { paginateGraphql } from "@octokit/plugin-paginate-graphql";
import { Octokit as OctokitRest } from "@octokit/rest"; import { Octokit as OctokitRest } from "@octokit/rest";
import { OrgMembershipRole } from "@app/db/schemas"; import { OrgMembershipRole } from "@app/db/schemas";
@@ -18,7 +18,7 @@ import { TPermissionServiceFactory } from "../permission/permission-service";
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal"; import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types"; import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
const OctokitWithPlugin = Octokit.plugin(paginateGraphQL); const OctokitWithPlugin = Octokit.plugin(paginateGraphql);
type TGithubOrgSyncServiceFactoryDep = { type TGithubOrgSyncServiceFactoryDep = {
githubOrgSyncDAL: TGithubOrgSyncDALFactory; githubOrgSyncDAL: TGithubOrgSyncDALFactory;
@@ -157,10 +157,23 @@ export const groupDALFactory = (db: TDbClient) => {
} }
}; };
const findGroupsByProjectId = async (projectId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.Groups)
.join(TableName.GroupProjectMembership, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.Groups));
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "Find groups by project id" });
}
};
return { return {
findGroups, findGroups,
findByOrgId, findByOrgId,
findAllGroupPossibleMembers, findAllGroupPossibleMembers,
findGroupsByProjectId,
...groupOrm ...groupOrm
}; };
}; };
@@ -176,7 +176,8 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
db.ref("name").withSchema(TableName.Groups).as("groupName"), db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"), db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
db.ref("firstName").withSchema(TableName.Users).as("firstName"), db.ref("firstName").withSchema(TableName.Users).as("firstName"),
db.ref("lastName").withSchema(TableName.Users).as("lastName") db.ref("lastName").withSchema(TableName.Users).as("lastName"),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
); );
return docs; return docs;
+7 -3
View File
@@ -24,9 +24,13 @@ export const initializeHsmModule = (envConfig: Pick<TEnvConfig, "isHsmConfigured
isInitialized = true; isInitialized = true;
logger.info("PKCS#11 module initialized"); logger.info("PKCS#11 module initialized");
} catch (err) { } catch (error) {
logger.error(err, "Failed to initialize PKCS#11 module"); if (error instanceof pkcs11js.Pkcs11Error && error.code === pkcs11js.CKR_CRYPTOKI_ALREADY_INITIALIZED) {
throw err; logger.info("Skipping HSM initialization because it's already initialized.");
} else {
logger.error(error, "Failed to initialize PKCS#11 module");
throw error;
}
} }
}; };
@@ -14,6 +14,11 @@ export type TLDAPConfig = {
caCert: string; caCert: string;
}; };
export type TTestLDAPConfigDTO = Omit<
TLDAPConfig,
"organization" | "id" | "groupSearchBase" | "groupSearchFilter" | "isActive" | "uniqueUserAttribute" | "searchBase"
>;
export type TCreateLdapCfgDTO = { export type TCreateLdapCfgDTO = {
orgId: string; orgId: string;
isActive: boolean; isActive: boolean;
@@ -2,15 +2,14 @@ import ldapjs from "ldapjs";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { TLDAPConfig } from "./ldap-config-types"; import { TLDAPConfig, TTestLDAPConfigDTO } from "./ldap-config-types";
export const isValidLdapFilter = (filter: string) => { export const isValidLdapFilter = (filter: string) => {
try { try {
ldapjs.parseFilter(filter); ldapjs.parseFilter(filter);
return true; return true;
} catch (error) { } catch (error) {
logger.error("Invalid LDAP filter"); logger.error(error, "Invalid LDAP filter");
logger.error(error);
return false; return false;
} }
}; };
@@ -20,7 +19,7 @@ export const isValidLdapFilter = (filter: string) => {
* @param ldapConfig - The LDAP configuration to test * @param ldapConfig - The LDAP configuration to test
* @returns {Boolean} isConnected - Whether or not the connection was successful * @returns {Boolean} isConnected - Whether or not the connection was successful
*/ */
export const testLDAPConfig = async (ldapConfig: TLDAPConfig): Promise<boolean> => { export const testLDAPConfig = async (ldapConfig: TTestLDAPConfigDTO): Promise<boolean> => {
return new Promise((resolve) => { return new Promise((resolve) => {
const ldapClient = ldapjs.createClient({ const ldapClient = ldapjs.createClient({
url: ldapConfig.url, url: ldapConfig.url,
@@ -714,13 +714,15 @@ export const oidcConfigServiceFactory = ({
} }
} }
const groups = typeof claims.groups === "string" ? [claims.groups] : (claims.groups as string[] | undefined);
oidcLogin({ oidcLogin({
email: claims.email, email: claims.email,
externalId: claims.sub, externalId: claims.sub,
firstName: claims.given_name ?? "", firstName: claims.given_name ?? "",
lastName: claims.family_name ?? "", lastName: claims.family_name ?? "",
orgId: org.id, orgId: org.id,
groups: claims.groups as string[] | undefined, groups,
callbackPort, callbackPort,
manageGroupMemberships: oidcCfg.manageGroupMemberships manageGroupMemberships: oidcCfg.manageGroupMemberships
}) })
@@ -0,0 +1,473 @@
import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability";
import {
ProjectPermissionActions,
ProjectPermissionCertificateActions,
ProjectPermissionCmekActions,
ProjectPermissionCommitsActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionGroupActions,
ProjectPermissionIdentityActions,
ProjectPermissionKmipActions,
ProjectPermissionMemberActions,
ProjectPermissionPkiSubscriberActions,
ProjectPermissionSecretActions,
ProjectPermissionSecretRotationActions,
ProjectPermissionSecretSyncActions,
ProjectPermissionSet,
ProjectPermissionSshHostActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
const buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
// Admins get full access to everything
[
ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports,
ProjectPermissionSub.SecretApproval,
ProjectPermissionSub.Role,
ProjectPermissionSub.Integrations,
ProjectPermissionSub.Webhooks,
ProjectPermissionSub.ServiceTokens,
ProjectPermissionSub.Settings,
ProjectPermissionSub.Environments,
ProjectPermissionSub.Tags,
ProjectPermissionSub.AuditLogs,
ProjectPermissionSub.IpAllowList,
ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshCertificateTemplates,
ProjectPermissionSub.SshHostGroups
].forEach((el) => {
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
el
);
});
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete,
ProjectPermissionCertificateActions.ReadPrivateKey
],
ProjectPermissionSub.Certificates
);
can(
[ProjectPermissionCommitsActions.Read, ProjectPermissionCommitsActions.PerformRollback],
ProjectPermissionSub.Commits
);
can(
[
ProjectPermissionSshHostActions.Edit,
ProjectPermissionSshHostActions.Read,
ProjectPermissionSshHostActions.Create,
ProjectPermissionSshHostActions.Delete,
ProjectPermissionSshHostActions.IssueHostCert
],
ProjectPermissionSub.SshHosts
);
can(
[
ProjectPermissionPkiSubscriberActions.Edit,
ProjectPermissionPkiSubscriberActions.Read,
ProjectPermissionPkiSubscriberActions.Create,
ProjectPermissionPkiSubscriberActions.Delete,
ProjectPermissionPkiSubscriberActions.IssueCert,
ProjectPermissionPkiSubscriberActions.ListCerts
],
ProjectPermissionSub.PkiSubscribers
);
can(
[
ProjectPermissionMemberActions.Create,
ProjectPermissionMemberActions.Edit,
ProjectPermissionMemberActions.Delete,
ProjectPermissionMemberActions.Read,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionMemberActions.AssumePrivileges
],
ProjectPermissionSub.Member
);
can(
[
ProjectPermissionGroupActions.Create,
ProjectPermissionGroupActions.Edit,
ProjectPermissionGroupActions.Delete,
ProjectPermissionGroupActions.Read,
ProjectPermissionGroupActions.GrantPrivileges
],
ProjectPermissionSub.Groups
);
can(
[
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Delete,
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionIdentityActions.AssumePrivileges
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionKmipActions.CreateClients,
ProjectPermissionKmipActions.UpdateClients,
ProjectPermissionKmipActions.DeleteClients,
ProjectPermissionKmipActions.ReadClients,
ProjectPermissionKmipActions.GenerateClientCertificates
],
ProjectPermissionSub.Kmip
);
can(
[
ProjectPermissionSecretRotationActions.Create,
ProjectPermissionSecretRotationActions.Edit,
ProjectPermissionSecretRotationActions.Delete,
ProjectPermissionSecretRotationActions.Read,
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
ProjectPermissionSecretRotationActions.RotateSecrets
],
ProjectPermissionSub.SecretRotation
);
return rules;
};
const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretFolders
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretImports
);
can(
[ProjectPermissionCommitsActions.Read, ProjectPermissionCommitsActions.PerformRollback],
ProjectPermissionSub.Commits
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Integrations
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Webhooks
);
can(
[
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Delete
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.ServiceTokens
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Settings
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Environments
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Tags
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
// double check if all CRUD are needed for CA and Certificates
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
can(
[
ProjectPermissionCertificateActions.Read,
ProjectPermissionCertificateActions.Edit,
ProjectPermissionCertificateActions.Create,
ProjectPermissionCertificateActions.Delete
],
ProjectPermissionSub.Certificates
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
can([ProjectPermissionPkiSubscriberActions.Read], ProjectPermissionSub.PkiSubscribers);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
return rules;
};
const buildViewerPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue],
ProjectPermissionSub.Secrets
);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionCertificateActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Commits);
return rules;
};
const buildNoAccessProjectPermission = () => {
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return rules;
};
const buildSshHostBootstrapPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[ProjectPermissionSshHostActions.Create, ProjectPermissionSshHostActions.IssueHostCert],
ProjectPermissionSub.SshHosts
);
return rules;
};
const buildCryptographicOperatorPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
return rules;
};
// General
export const projectAdminPermissions = buildAdminPermissionRules();
export const projectMemberPermissions = buildMemberPermissionRules();
export const projectViewerPermission = buildViewerPermissionRules();
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
// SSH
export const sshHostBootstrapPermissions = buildSshHostBootstrapPermissionRules();
// KMS
export const cryptographicOperatorPermissions = buildCryptographicOperatorPermissionRules();
@@ -41,7 +41,8 @@ export enum OrgPermissionGatewayActions {
CreateGateways = "create-gateways", CreateGateways = "create-gateways",
ListGateways = "list-gateways", ListGateways = "list-gateways",
EditGateways = "edit-gateways", EditGateways = "edit-gateways",
DeleteGateways = "delete-gateways" DeleteGateways = "delete-gateways",
AttachGateways = "attach-gateways"
} }
export enum OrgPermissionIdentityActions { export enum OrgPermissionIdentityActions {
@@ -337,6 +338,7 @@ const buildAdminPermission = () => {
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.EditGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.DeleteGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole); can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
@@ -378,6 +380,7 @@ const buildMemberPermission = () => {
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections); can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.ListGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway); can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
return rules; return rules;
}; };
@@ -132,7 +132,7 @@ export const permissionDALFactory = (db: TDbClient) => {
} }
}; };
const getProjectGroupPermissions = async (projectId: string) => { const getProjectGroupPermissions = async (projectId: string, filterGroupId?: string) => {
try { try {
const docs = await db const docs = await db
.replicaNode()(TableName.GroupProjectMembership) .replicaNode()(TableName.GroupProjectMembership)
@@ -148,6 +148,11 @@ export const permissionDALFactory = (db: TDbClient) => {
`groupCustomRoles.id` `groupCustomRoles.id`
) )
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId) .where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
.where((bd) => {
if (filterGroupId) {
void bd.where(`${TableName.GroupProjectMembership}.groupId`, "=", filterGroupId);
}
})
.select( .select(
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"), db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
db.ref("id").withSchema(TableName.Groups).as("groupId"), db.ref("id").withSchema(TableName.Groups).as("groupId"),
@@ -12,6 +12,14 @@ import {
TIdentityProjectMemberships, TIdentityProjectMemberships,
TProjectMemberships TProjectMemberships
} from "@app/db/schemas"; } from "@app/db/schemas";
import {
cryptographicOperatorPermissions,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
projectViewerPermission,
sshHostBootstrapPermissions
} from "@app/ee/services/permission/default-roles";
import { conditionsMatcher } from "@app/lib/casl"; import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { objectify } from "@app/lib/fn"; import { objectify } from "@app/lib/fn";
@@ -32,14 +40,7 @@ import {
TGetServiceTokenProjectPermissionArg, TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg TGetUserProjectPermissionArg
} from "./permission-service-types"; } from "./permission-service-types";
import { import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
buildServiceTokenProjectPermission,
projectAdminPermissions,
projectMemberPermissions,
projectNoAccessPermissions,
ProjectPermissionSet,
projectViewerPermission
} from "./project-permission";
type TPermissionServiceFactoryDep = { type TPermissionServiceFactoryDep = {
orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">; orgRoleDAL: Pick<TOrgRoleDALFactory, "findOne">;
@@ -95,6 +96,10 @@ export const permissionServiceFactory = ({
return projectViewerPermission; return projectViewerPermission;
case ProjectMembershipRole.NoAccess: case ProjectMembershipRole.NoAccess:
return projectNoAccessPermissions; return projectNoAccessPermissions;
case ProjectMembershipRole.SshHostBootstrapper:
return sshHostBootstrapPermissions;
case ProjectMembershipRole.KmsCryptographicOperator:
return cryptographicOperatorPermissions;
case ProjectMembershipRole.Custom: { case ProjectMembershipRole.Custom: {
return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>( return unpackRules<RawRuleOf<MongoAbility<ProjectPermissionSet>>>(
permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[] permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[]
@@ -625,6 +630,34 @@ export const permissionServiceFactory = ({
return { permission }; return { permission };
}; };
const checkGroupProjectPermission = async ({
groupId,
projectId,
checkPermissions
}: {
groupId: string;
projectId: string;
checkPermissions: ProjectPermissionSet;
}) => {
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId, groupId);
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
const rolePermissions =
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const rules = buildProjectPermissionRules(rolePermissions);
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
conditionsMatcher
});
return {
permission,
id: groupProjectPermission.groupId,
name: groupProjectPermission.username,
membershipId: groupProjectPermission.id
};
});
return groupPermissions.some((groupPermission) => groupPermission.permission.can(...checkPermissions));
};
return { return {
getUserOrgPermission, getUserOrgPermission,
getOrgPermission, getOrgPermission,
@@ -634,6 +667,7 @@ export const permissionServiceFactory = ({
getOrgPermissionByRole, getOrgPermissionByRole,
getProjectPermissionByRole, getProjectPermissionByRole,
buildOrgPermission, buildOrgPermission,
buildProjectPermissionRules buildProjectPermissionRules,
checkGroupProjectPermission
}; };
}; };
@@ -22,6 +22,14 @@ export enum ProjectPermissionCommitsActions {
PerformRollback = "perform-rollback" PerformRollback = "perform-rollback"
} }
export enum ProjectPermissionCertificateActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
ReadPrivateKey = "read-private-key"
}
export enum ProjectPermissionSecretActions { export enum ProjectPermissionSecretActions {
DescribeAndReadValue = "read", DescribeAndReadValue = "read",
DescribeSecret = "describeSecret", DescribeSecret = "describeSecret",
@@ -84,6 +92,15 @@ export enum ProjectPermissionSshHostActions {
IssueHostCert = "issue-host-cert" IssueHostCert = "issue-host-cert"
} }
export enum ProjectPermissionPkiSubscriberActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
IssueCert = "issue-cert",
ListCerts = "list-certs"
}
export enum ProjectPermissionSecretSyncActions { export enum ProjectPermissionSecretSyncActions {
Read = "read", Read = "read",
Create = "create", Create = "create",
@@ -141,6 +158,7 @@ export enum ProjectPermissionSub {
SshCertificateTemplates = "ssh-certificate-templates", SshCertificateTemplates = "ssh-certificate-templates",
SshHosts = "ssh-hosts", SshHosts = "ssh-hosts",
SshHostGroups = "ssh-host-groups", SshHostGroups = "ssh-host-groups",
PkiSubscribers = "pki-subscribers",
PkiAlerts = "pki-alerts", PkiAlerts = "pki-alerts",
PkiCollections = "pki-collections", PkiCollections = "pki-collections",
Kms = "kms", Kms = "kms",
@@ -188,6 +206,11 @@ export type SshHostSubjectFields = {
hostname: string; hostname: string;
}; };
export type PkiSubscriberSubjectFields = {
name: string;
// (dangtony98): consider adding [commonName] as a subject field in the future
};
export type ProjectPermissionSet = export type ProjectPermissionSet =
| [ | [
ProjectPermissionSecretActions, ProjectPermissionSecretActions,
@@ -238,7 +261,7 @@ export type ProjectPermissionSet =
ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields) ProjectPermissionSub.Identity | (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
] ]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.Certificates] | [ProjectPermissionCertificateActions, ProjectPermissionSub.Certificates]
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates] | [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates] | [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
@@ -247,6 +270,13 @@ export type ProjectPermissionSet =
ProjectPermissionSshHostActions, ProjectPermissionSshHostActions,
ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields) ProjectPermissionSub.SshHosts | (ForcedSubject<ProjectPermissionSub.SshHosts> & SshHostSubjectFields)
] ]
| [
ProjectPermissionPkiSubscriberActions,
(
| ProjectPermissionSub.PkiSubscribers
| (ForcedSubject<ProjectPermissionSub.PkiSubscribers> & PkiSubscriberSubjectFields)
)
]
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups] | [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts] | [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections] | [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
@@ -398,6 +428,21 @@ const SshHostConditionSchema = z
}) })
.partial(); .partial();
const PkiSubscriberConditionSchema = z
.object({
name: z.union([
z.string(),
z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
})
.partial()
])
})
.partial();
const GeneralPermissionSchema = [ const GeneralPermissionSchema = [
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
@@ -485,7 +530,7 @@ const GeneralPermissionSchema = [
}), }),
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe( action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCertificateActions).describe(
"Describe what action an entity can take." "Describe what action an entity can take."
) )
}), }),
@@ -668,6 +713,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource." "When specified, only matching conditions will be allowed to access given resource."
).optional() ).optional()
}), }),
z.object({
subject: z.literal(ProjectPermissionSub.PkiSubscribers).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionPkiSubscriberActions).describe(
"Describe what action an entity can take."
),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
conditions: PkiSubscriberConditionSchema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({ z.object({
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."), subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."), inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
@@ -683,404 +738,6 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>; export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
const buildAdminPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
// Admins get full access to everything
[
ProjectPermissionSub.SecretFolders,
ProjectPermissionSub.SecretImports,
ProjectPermissionSub.SecretApproval,
ProjectPermissionSub.Role,
ProjectPermissionSub.Integrations,
ProjectPermissionSub.Webhooks,
ProjectPermissionSub.ServiceTokens,
ProjectPermissionSub.Settings,
ProjectPermissionSub.Environments,
ProjectPermissionSub.Tags,
ProjectPermissionSub.AuditLogs,
ProjectPermissionSub.IpAllowList,
ProjectPermissionSub.CertificateAuthorities,
ProjectPermissionSub.Certificates,
ProjectPermissionSub.CertificateTemplates,
ProjectPermissionSub.PkiAlerts,
ProjectPermissionSub.PkiCollections,
ProjectPermissionSub.SshCertificateAuthorities,
ProjectPermissionSub.SshCertificates,
ProjectPermissionSub.SshCertificateTemplates,
ProjectPermissionSub.SshHostGroups
].forEach((el) => {
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
el
);
});
can(
[
ProjectPermissionSshHostActions.Edit,
ProjectPermissionSshHostActions.Read,
ProjectPermissionSshHostActions.Create,
ProjectPermissionSshHostActions.Delete,
ProjectPermissionSshHostActions.IssueHostCert
],
ProjectPermissionSub.SshHosts
);
can(
[
ProjectPermissionMemberActions.Create,
ProjectPermissionMemberActions.Edit,
ProjectPermissionMemberActions.Delete,
ProjectPermissionMemberActions.Read,
ProjectPermissionMemberActions.GrantPrivileges,
ProjectPermissionMemberActions.AssumePrivileges
],
ProjectPermissionSub.Member
);
can(
[
ProjectPermissionGroupActions.Create,
ProjectPermissionGroupActions.Edit,
ProjectPermissionGroupActions.Delete,
ProjectPermissionGroupActions.Read,
ProjectPermissionGroupActions.GrantPrivileges
],
ProjectPermissionSub.Groups
);
can(
[
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Delete,
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.GrantPrivileges,
ProjectPermissionIdentityActions.AssumePrivileges
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
can(
[
ProjectPermissionKmipActions.CreateClients,
ProjectPermissionKmipActions.UpdateClients,
ProjectPermissionKmipActions.DeleteClients,
ProjectPermissionKmipActions.ReadClients,
ProjectPermissionKmipActions.GenerateClientCertificates
],
ProjectPermissionSub.Kmip
);
can(
[
ProjectPermissionSecretRotationActions.Create,
ProjectPermissionSecretRotationActions.Edit,
ProjectPermissionSecretRotationActions.Delete,
ProjectPermissionSecretRotationActions.Read,
ProjectPermissionSecretRotationActions.ReadGeneratedCredentials,
ProjectPermissionSecretRotationActions.RotateSecrets
],
ProjectPermissionSub.SecretRotation
);
can(
[ProjectPermissionCommitsActions.Read, ProjectPermissionCommitsActions.PerformRollback],
ProjectPermissionSub.Commits
);
return rules;
};
export const projectAdminPermissions = buildAdminPermissionRules();
const buildMemberPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(
[
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSecretActions.DescribeSecret,
ProjectPermissionSecretActions.ReadValue,
ProjectPermissionSecretActions.Edit,
ProjectPermissionSecretActions.Create,
ProjectPermissionSecretActions.Delete
],
ProjectPermissionSub.Secrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretFolders
);
can(
[
ProjectPermissionDynamicSecretActions.ReadRootCredential,
ProjectPermissionDynamicSecretActions.EditRootCredential,
ProjectPermissionDynamicSecretActions.CreateRootCredential,
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
ProjectPermissionDynamicSecretActions.Lease
],
ProjectPermissionSub.DynamicSecrets
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.SecretImports
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
can([ProjectPermissionSecretRotationActions.Read], ProjectPermissionSub.SecretRotation);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionMemberActions.Read, ProjectPermissionMemberActions.Create], ProjectPermissionSub.Member);
can([ProjectPermissionGroupActions.Read], ProjectPermissionSub.Groups);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Integrations
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Webhooks
);
can(
[
ProjectPermissionIdentityActions.Read,
ProjectPermissionIdentityActions.Edit,
ProjectPermissionIdentityActions.Create,
ProjectPermissionIdentityActions.Delete
],
ProjectPermissionSub.Identity
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.ServiceTokens
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Settings
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Environments
);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Tags
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
// double check if all CRUD are needed for CA and Certificates
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
can(
[
ProjectPermissionActions.Read,
ProjectPermissionActions.Edit,
ProjectPermissionActions.Create,
ProjectPermissionActions.Delete
],
ProjectPermissionSub.Certificates
);
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Create], ProjectPermissionSub.SshCertificates);
can([ProjectPermissionActions.Read], ProjectPermissionSub.SshCertificateTemplates);
can([ProjectPermissionSshHostActions.Read], ProjectPermissionSub.SshHosts);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt,
ProjectPermissionCmekActions.Sign,
ProjectPermissionCmekActions.Verify
],
ProjectPermissionSub.Cmek
);
can(
[
ProjectPermissionSecretSyncActions.Create,
ProjectPermissionSecretSyncActions.Edit,
ProjectPermissionSecretSyncActions.Delete,
ProjectPermissionSecretSyncActions.Read,
ProjectPermissionSecretSyncActions.SyncSecrets,
ProjectPermissionSecretSyncActions.ImportSecrets,
ProjectPermissionSecretSyncActions.RemoveSecrets
],
ProjectPermissionSub.SecretSyncs
);
can(
[ProjectPermissionCommitsActions.Read, ProjectPermissionCommitsActions.PerformRollback],
ProjectPermissionSub.Commits
);
return rules;
};
export const projectMemberPermissions = buildMemberPermissionRules();
const buildViewerPermissionRules = () => {
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionSecretRotationActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionGroupActions.Read, ProjectPermissionSub.Groups);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
can(ProjectPermissionIdentityActions.Read, ProjectPermissionSub.Identity);
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
can(ProjectPermissionCommitsActions.Read, ProjectPermissionSub.Commits);
return rules;
};
export const projectViewerPermission = buildViewerPermissionRules();
const buildNoAccessProjectPermission = () => {
const { rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
return rules;
};
export const buildServiceTokenProjectPermission = ( export const buildServiceTokenProjectPermission = (
scopes: Array<{ secretPath: string; environment: string }>, scopes: Array<{ secretPath: string; environment: string }>,
permission: string[] permission: string[]
@@ -1122,8 +779,6 @@ export const buildServiceTokenProjectPermission = (
return build({ conditionsMatcher }); return build({ conditionsMatcher });
}; };
export const projectNoAccessPermissions = buildNoAccessProjectPermission();
/* eslint-disable */ /* eslint-disable */
/** /**
@@ -1,22 +1,27 @@
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants"; import { ProjectType } from "@app/db/schemas";
import { import {
InfisicalProjectTemplate, InfisicalProjectTemplate,
TUnpackedPermission TUnpackedPermission
} from "@app/ee/services/project-template/project-template-types"; } from "@app/ee/services/project-template/project-template-types";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"; import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
export const getDefaultProjectTemplate = (orgId: string) => ({ import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
type,
name: InfisicalProjectTemplate.Default, name: InfisicalProjectTemplate.Default,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
description: "Infisical's default project template", description: `Infisical's ${type} default project template`,
environments: ProjectTemplateDefaultEnvironments, environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: [...getPredefinedRoles("project-template")].map(({ name, slug, permissions }) => ({ roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
orgId orgId
}); });
@@ -1,10 +1,11 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { TProjectTemplates } from "@app/db/schemas"; import { 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"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns"; import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import { import {
TCreateProjectTemplateDTO, TCreateProjectTemplateDTO,
@@ -32,11 +33,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
...rest, ...rest,
environments: environments as TProjectTemplateEnvironment[], environments: environments as TProjectTemplateEnvironment[],
roles: [ roles: [
...getPredefinedRoles("project-template").map(({ name, slug, permissions }) => ({ ...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
...(roles as TProjectTemplateRole[]).map((role) => ({ ...(roles as TProjectTemplateRole[]).map((role) => ({
...role, ...role,
permissions: unpackPermissions(role.permissions) permissions: unpackPermissions(role.permissions)
@@ -49,7 +52,7 @@ export const projectTemplateServiceFactory = ({
permissionService, permissionService,
projectTemplateDAL projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep) => { }: TProjectTemplatesServiceFactoryDep) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor) => { const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates) if (!plan.projectTemplates)
@@ -68,11 +71,14 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const projectTemplates = await projectTemplateDAL.find({ const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId orgId: actor.orgId,
...(type ? { type } : {})
}); });
return [ return [
getDefaultProjectTemplate(actor.orgId), ...(type
? [getDefaultProjectTemplate(actor.orgId, type)]
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
...projectTemplates.map((template) => $unpackProjectTemplate(template)) ...projectTemplates.map((template) => $unpackProjectTemplate(template))
]; ];
}; };
@@ -134,7 +140,7 @@ export const projectTemplateServiceFactory = ({
}; };
const createProjectTemplate = async ( const createProjectTemplate = async (
{ roles, environments, ...params }: TCreateProjectTemplateDTO, { roles, environments, type, ...params }: TCreateProjectTemplateDTO,
actor: OrgServiceActor actor: OrgServiceActor
) => { ) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
@@ -154,6 +160,17 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
if (environments && type !== ProjectType.SecretManager) {
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
}
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to create project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
const isConflictingName = Boolean( const isConflictingName = Boolean(
await projectTemplateDAL.findOne({ await projectTemplateDAL.findOne({
name: params.name, name: params.name,
@@ -169,8 +186,10 @@ export const projectTemplateServiceFactory = ({
const projectTemplate = await projectTemplateDAL.create({ const projectTemplate = await projectTemplateDAL.create({
...params, ...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))), roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: JSON.stringify(environments), environments:
orgId: actor.orgId type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
orgId: actor.orgId,
type
}); });
return $unpackProjectTemplate(projectTemplate); return $unpackProjectTemplate(projectTemplate);
@@ -202,6 +221,19 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments)
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
message: `Failed to update project template due to environment count exceeding your current limit of ${plan.environmentLimit}. Contact Infisical to increase limit.`
});
}
if (params.name && projectTemplate.name !== params.name) { if (params.name && projectTemplate.name !== params.name) {
const isConflictingName = Boolean( const isConflictingName = Boolean(
await projectTemplateDAL.findOne({ await projectTemplateDAL.findOne({
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { TProjectEnvironments } from "@app/db/schemas"; import { 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 { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
@@ -15,8 +15,9 @@ export type TProjectTemplateRole = {
export type TCreateProjectTemplateDTO = { export type TCreateProjectTemplateDTO = {
name: string; name: string;
description?: string; description?: string;
type: ProjectType;
roles: TProjectTemplateRole[]; roles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[]; environments?: TProjectTemplateEnvironment[] | null;
}; };
export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>; export type TUpdateProjectTemplateDTO = Partial<TCreateProjectTemplateDTO>;
@@ -334,7 +334,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecret).as("commitSecretId"), db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecret).as("commitSecretId"),
db.ref("id").withSchema(TableName.SecretApprovalRequestSecret).as("commitId"), db.ref("id").withSchema(TableName.SecretApprovalRequestSecret).as("commitId"),
db.raw( db.raw(
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank` `DENSE_RANK() OVER (PARTITION BY ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."createdAt" DESC) as rank`
), ),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"), db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"), db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
@@ -483,7 +483,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitSecretId"), db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitSecretId"),
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitId"), db.ref("id").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitId"),
db.raw( db.raw(
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank` `DENSE_RANK() OVER (PARTITION BY ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."createdAt" DESC) as rank`
), ),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"), db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"), db.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
@@ -0,0 +1,11 @@
import { getConfig } from "@app/lib/config/env";
export const canUseSecretScanning = (orgId: string) => {
const appCfg = getConfig();
if (!appCfg.isCloud) {
return true;
}
return appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(orgId);
};
@@ -12,6 +12,7 @@ import { NotFoundError } from "@app/lib/errors";
import { TGitAppDALFactory } from "./git-app-dal"; import { TGitAppDALFactory } from "./git-app-dal";
import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal"; import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal";
import { TSecretScanningDALFactory } from "./secret-scanning-dal"; import { TSecretScanningDALFactory } from "./secret-scanning-dal";
import { canUseSecretScanning } from "./secret-scanning-fns";
import { TSecretScanningQueueFactory } from "./secret-scanning-queue"; import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
import { import {
SecretScanningRiskStatus, SecretScanningRiskStatus,
@@ -47,12 +48,14 @@ export const secretScanningServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId
}: TInstallAppSessionDTO) => { }: TInstallAppSessionDTO) => {
const appCfg = getConfig();
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId); const { permission } = await permissionService.getOrgPermission(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");
await gitAppInstallSessionDAL.upsert({ orgId, sessionId, userId: actorId }); await gitAppInstallSessionDAL.upsert({ orgId, sessionId, userId: actorId });
return { sessionId }; return { sessionId, gitAppSlug: appCfg.SECRET_SCANNING_GIT_APP_SLUG };
}; };
const linkInstallationToOrg = async ({ const linkInstallationToOrg = async ({
@@ -91,7 +94,8 @@ export const secretScanningServiceFactory = ({
const { const {
data: { repositories } data: { repositories }
} = await octokit.apps.listReposAccessibleToInstallation(); } = await octokit.apps.listReposAccessibleToInstallation();
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) {
if (canUseSecretScanning(actorOrgId)) {
await Promise.all( await Promise.all(
repositories.map(({ id, full_name }) => repositories.map(({ id, full_name }) =>
secretScanningQueue.startFullRepoScan({ secretScanningQueue.startFullRepoScan({
@@ -102,6 +106,7 @@ export const secretScanningServiceFactory = ({
) )
); );
} }
return { installatedApp }; return { installatedApp };
}; };
@@ -164,7 +169,6 @@ export const secretScanningServiceFactory = ({
}; };
const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => { const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => {
const appCfg = getConfig();
const { commits, repository, installation, pusher } = payload; const { commits, repository, installation, pusher } = payload;
if (!commits || !repository || !installation || !pusher) { if (!commits || !repository || !installation || !pusher) {
return; return;
@@ -175,7 +179,7 @@ export const secretScanningServiceFactory = ({
}); });
if (!installationLink) return; if (!installationLink) return;
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) { if (canUseSecretScanning(installationLink.orgId)) {
await secretScanningQueue.startPushEventScan({ await secretScanningQueue.startPushEventScan({
commits, commits,
pusher: { name: pusher.name, email: pusher.email }, pusher: { name: pusher.name, email: pusher.email },
@@ -28,6 +28,7 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.SshHostGroup}.projectId`, projectId) .where(`${TableName.SshHostGroup}.projectId`, projectId)
.select( .select(
db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"), db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"),
@@ -35,7 +36,8 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
db.ref("name").withSchema(TableName.SshHostGroup), db.ref("name").withSchema(TableName.SshHostGroup),
db.ref("loginUser").withSchema(TableName.SshHostLoginUser), db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping) db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
) )
.orderBy(`${TableName.SshHostGroup}.updatedAt`, "desc"); .orderBy(`${TableName.SshHostGroup}.updatedAt`, "desc");
@@ -69,7 +71,8 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({ const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
} }
})); }));
return { return {
@@ -99,6 +102,7 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.SshHostGroup}.id`, sshHostGroupId) .where(`${TableName.SshHostGroup}.id`, sshHostGroupId)
.select( .select(
db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"), db.ref("id").withSchema(TableName.SshHostGroup).as("sshHostGroupId"),
@@ -106,7 +110,8 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
db.ref("name").withSchema(TableName.SshHostGroup), db.ref("name").withSchema(TableName.SshHostGroup),
db.ref("loginUser").withSchema(TableName.SshHostLoginUser), db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping) db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
); );
if (rows.length === 0) return null; if (rows.length === 0) return null;
@@ -121,7 +126,8 @@ export const sshHostGroupDALFactory = (db: TDbClient) => {
const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({ const loginMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
} }
})); }));
@@ -12,6 +12,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
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 { TGroupDALFactory } from "../group/group-dal";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { createSshLoginMappings } from "../ssh-host/ssh-host-fns"; import { createSshLoginMappings } from "../ssh-host/ssh-host-fns";
import { import {
@@ -43,8 +44,12 @@ type TSshHostGroupServiceFactoryDep = {
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction" | "delete">; sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction" | "delete">;
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">; sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
userDAL: Pick<TUserDALFactory, "find">; userDAL: Pick<TUserDALFactory, "find">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">; permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
>;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
}; };
export type TSshHostGroupServiceFactory = ReturnType<typeof sshHostGroupServiceFactory>; export type TSshHostGroupServiceFactory = ReturnType<typeof sshHostGroupServiceFactory>;
@@ -58,7 +63,8 @@ export const sshHostGroupServiceFactory = ({
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
userDAL, userDAL,
permissionService, permissionService,
licenseService licenseService,
groupDAL
}: TSshHostGroupServiceFactoryDep) => { }: TSshHostGroupServiceFactoryDep) => {
const createSshHostGroup = async ({ const createSshHostGroup = async ({
projectId, projectId,
@@ -127,6 +133,7 @@ export const sshHostGroupServiceFactory = ({
loginMappings, loginMappings,
sshHostLoginUserDAL, sshHostLoginUserDAL,
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
groupDAL,
userDAL, userDAL,
permissionService, permissionService,
projectId, projectId,
@@ -179,13 +186,42 @@ export const sshHostGroupServiceFactory = ({
}); });
const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => { const updatedSshHostGroup = await sshHostGroupDAL.transaction(async (tx) => {
await sshHostGroupDAL.updateById( if (name && name !== sshHostGroup.name) {
sshHostGroupId, // (dangtony98): room to optimize check to ensure that
{ // the SSH host group name is unique across the whole org
name const project = await projectDAL.findById(sshHostGroup.projectId, tx);
}, if (!project) throw new NotFoundError({ message: `Project with ID '${sshHostGroup.projectId}' not found` });
tx const projects = await projectDAL.find(
); {
orgId: project.orgId
},
{ tx }
);
const existingSshHostGroup = await sshHostGroupDAL.find(
{
name,
$in: {
projectId: projects.map((p) => p.id)
}
},
{ tx }
);
if (existingSshHostGroup.length) {
throw new BadRequestError({
message: `SSH host group with name '${name}' already exists in the organization`
});
}
await sshHostGroupDAL.updateById(
sshHostGroupId,
{
name
},
tx
);
}
if (loginMappings) { if (loginMappings) {
await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx); await sshHostLoginUserDAL.delete({ sshHostGroupId: sshHostGroup.id }, tx);
if (loginMappings.length) { if (loginMappings.length) {
@@ -194,6 +230,7 @@ export const sshHostGroupServiceFactory = ({
loginMappings, loginMappings,
sshHostLoginUserDAL, sshHostLoginUserDAL,
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
groupDAL,
userDAL, userDAL,
permissionService, permissionService,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
@@ -9,12 +9,7 @@ export type TCreateSshHostGroupDTO = {
export type TUpdateSshHostGroupDTO = { export type TUpdateSshHostGroupDTO = {
sshHostGroupId: string; sshHostGroupId: string;
name?: string; name?: string;
loginMappings?: { loginMappings?: TLoginMapping[];
loginUser: string;
allowedPrincipals: {
usernames: string[];
};
}[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetSshHostGroupDTO = { export type TGetSshHostGroupDTO = {
@@ -31,8 +31,18 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUser}.id`, `${TableName.SshHostLoginUser}.id`,
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.Users}.id`, `${TableName.SshHostLoginUserMapping}.userId`)
.leftJoin(
TableName.UserGroupMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.SshHostLoginUserMapping}.groupId`
)
.whereIn(`${TableName.SshHost}.projectId`, projectIds) .whereIn(`${TableName.SshHost}.projectId`, projectIds)
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId) .andWhere((bd) => {
void bd
.where(`${TableName.SshHostLoginUserMapping}.userId`, userId)
.orWhere(`${TableName.UserGroupMembership}.userId`, userId);
})
.select( .select(
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"), db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
db.ref("projectId").withSchema(TableName.SshHost), db.ref("projectId").withSchema(TableName.SshHost),
@@ -58,8 +68,17 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.join(TableName.SshHost, `${TableName.SshHostGroupMembership}.sshHostId`, `${TableName.SshHost}.id`) .join(TableName.SshHost, `${TableName.SshHostGroupMembership}.sshHostId`, `${TableName.SshHost}.id`)
.leftJoin(
TableName.UserGroupMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.SshHostLoginUserMapping}.groupId`
)
.whereIn(`${TableName.SshHost}.projectId`, projectIds) .whereIn(`${TableName.SshHost}.projectId`, projectIds)
.andWhere(`${TableName.SshHostLoginUserMapping}.userId`, userId) .andWhere((bd) => {
void bd
.where(`${TableName.SshHostLoginUserMapping}.userId`, userId)
.orWhere(`${TableName.UserGroupMembership}.userId`, userId);
})
.select( .select(
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"), db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
db.ref("projectId").withSchema(TableName.SshHost), db.ref("projectId").withSchema(TableName.SshHost),
@@ -133,6 +152,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.SshHost}.projectId`, projectId) .where(`${TableName.SshHost}.projectId`, projectId)
.select( .select(
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"), db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
@@ -144,6 +164,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
db.ref("loginUser").withSchema(TableName.SshHostLoginUser), db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping), db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug"),
db.ref("userSshCaId").withSchema(TableName.SshHost), db.ref("userSshCaId").withSchema(TableName.SshHost),
db.ref("hostSshCaId").withSchema(TableName.SshHost) db.ref("hostSshCaId").withSchema(TableName.SshHost)
) )
@@ -163,10 +184,12 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.select( .select(
db.ref("sshHostId").withSchema(TableName.SshHostGroupMembership), db.ref("sshHostId").withSchema(TableName.SshHostGroupMembership),
db.ref("loginUser").withSchema(TableName.SshHostLoginUser), db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
db.ref("username").withSchema(TableName.Users) db.ref("username").withSchema(TableName.Users),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
) )
.whereIn(`${TableName.SshHostGroupMembership}.sshHostId`, hostIds); .whereIn(`${TableName.SshHostGroupMembership}.sshHostId`, hostIds);
@@ -185,7 +208,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
const directMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({ const directMappings = Object.entries(loginMappingGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
}, },
source: LoginMappingSource.HOST source: LoginMappingSource.HOST
})); }));
@@ -197,7 +221,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
const groupMappings = Object.entries(inheritedGrouped).map(([loginUser, entries]) => ({ const groupMappings = Object.entries(inheritedGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
}, },
source: LoginMappingSource.HOST_GROUP source: LoginMappingSource.HOST_GROUP
})); }));
@@ -229,6 +254,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.SshHost}.id`, sshHostId) .where(`${TableName.SshHost}.id`, sshHostId)
.select( .select(
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"), db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
@@ -241,7 +267,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
db.ref("username").withSchema(TableName.Users), db.ref("username").withSchema(TableName.Users),
db.ref("userId").withSchema(TableName.SshHostLoginUserMapping), db.ref("userId").withSchema(TableName.SshHostLoginUserMapping),
db.ref("userSshCaId").withSchema(TableName.SshHost), db.ref("userSshCaId").withSchema(TableName.SshHost),
db.ref("hostSshCaId").withSchema(TableName.SshHost) db.ref("hostSshCaId").withSchema(TableName.SshHost),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
); );
if (rows.length === 0) return null; if (rows.length === 0) return null;
@@ -257,7 +284,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
const directMappings = Object.entries(directGrouped).map(([loginUser, entries]) => ({ const directMappings = Object.entries(directGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
}, },
source: LoginMappingSource.HOST source: LoginMappingSource.HOST
})); }));
@@ -275,10 +303,12 @@ export const sshHostDALFactory = (db: TDbClient) => {
`${TableName.SshHostLoginUserMapping}.sshHostLoginUserId` `${TableName.SshHostLoginUserMapping}.sshHostLoginUserId`
) )
.leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`) .leftJoin(TableName.Users, `${TableName.SshHostLoginUserMapping}.userId`, `${TableName.Users}.id`)
.leftJoin(TableName.Groups, `${TableName.SshHostLoginUserMapping}.groupId`, `${TableName.Groups}.id`)
.where(`${TableName.SshHostGroupMembership}.sshHostId`, sshHostId) .where(`${TableName.SshHostGroupMembership}.sshHostId`, sshHostId)
.select( .select(
db.ref("loginUser").withSchema(TableName.SshHostLoginUser), db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
db.ref("username").withSchema(TableName.Users) db.ref("username").withSchema(TableName.Users),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug")
); );
const groupGrouped = groupBy( const groupGrouped = groupBy(
@@ -289,7 +319,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
const groupMappings = Object.entries(groupGrouped).map(([loginUser, entries]) => ({ const groupMappings = Object.entries(groupGrouped).map(([loginUser, entries]) => ({
loginUser, loginUser,
allowedPrincipals: { allowedPrincipals: {
usernames: unique(entries.map((e) => e.username)).filter(Boolean) usernames: unique(entries.map((e) => e.username)).filter(Boolean),
groups: unique(entries.map((e) => e.groupSlug)).filter(Boolean)
}, },
source: LoginMappingSource.HOST_GROUP source: LoginMappingSource.HOST_GROUP
})); }));
@@ -3,6 +3,7 @@ import { Knex } from "knex";
import { ActionProjectType } from "@app/db/schemas"; import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
import { TCreateSshLoginMappingsDTO } from "./ssh-host-types"; import { TCreateSshLoginMappingsDTO } from "./ssh-host-types";
/** /**
@@ -15,6 +16,7 @@ export const createSshLoginMappings = async ({
loginMappings, loginMappings,
sshHostLoginUserDAL, sshHostLoginUserDAL,
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
groupDAL,
userDAL, userDAL,
permissionService, permissionService,
projectId, projectId,
@@ -35,7 +37,7 @@ export const createSshLoginMappings = async ({
tx tx
); );
if (allowedPrincipals.usernames.length > 0) { if (allowedPrincipals.usernames && allowedPrincipals.usernames.length > 0) {
const users = await userDAL.find( const users = await userDAL.find(
{ {
$in: { $in: {
@@ -74,6 +76,41 @@ export const createSshLoginMappings = async ({
tx tx
); );
} }
if (allowedPrincipals.groups && allowedPrincipals.groups.length > 0) {
const projectGroups = await groupDAL.findGroupsByProjectId(projectId);
const groups = projectGroups.filter((g) => allowedPrincipals.groups?.includes(g.slug));
if (groups.length !== allowedPrincipals.groups?.length) {
throw new BadRequestError({
message: `Invalid group slugs: ${allowedPrincipals.groups
.filter((g) => !projectGroups.some((pg) => pg.slug === g))
.join(", ")}`
});
}
for await (const group of groups) {
// check that each group has access to the SSH project and have read access to hosts
const hasPermission = await permissionService.checkGroupProjectPermission({
groupId: group.id,
projectId,
checkPermissions: [ProjectPermissionSshHostActions.Read, ProjectPermissionSub.SshHosts]
});
if (!hasPermission) {
throw new BadRequestError({
message: `Group ${group.slug} does not have access to the SSH project`
});
}
}
await sshHostLoginUserMappingDAL.insertMany(
groups.map((group) => ({
sshHostLoginUserId: sshHostLoginUser.id,
groupId: group.id
})),
tx
);
}
} }
}; };
@@ -15,7 +15,24 @@ export const sanitizedSshHost = SshHostsSchema.pick({
export const loginMappingSchema = z.object({ export const loginMappingSchema = z.object({
loginUser: z.string().trim(), loginUser: z.string().trim(),
allowedPrincipals: z.object({ allowedPrincipals: z
usernames: z.array(z.string().trim()).transform((usernames) => Array.from(new Set(usernames))) .object({
}) usernames: z
.array(z.string().trim())
.transform((usernames) => Array.from(new Set(usernames)))
.optional(),
groups: z
.array(z.string().trim())
.transform((groups) => Array.from(new Set(groups)))
.optional()
})
.refine(
(data) => {
return (data.usernames && data.usernames.length > 0) || (data.groups && data.groups.length > 0);
},
{
message: "At least one username or group must be provided",
path: ["allowedPrincipals"]
}
)
}); });
@@ -1,6 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType, ProjectType } from "@app/db/schemas"; import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
@@ -19,6 +20,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal"; import { TProjectSshConfigDALFactory } from "@app/services/project/project-ssh-config-dal";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
import { import {
convertActorToPrincipals, convertActorToPrincipals,
createSshCert, createSshCert,
@@ -39,12 +41,14 @@ import {
type TSshHostServiceFactoryDep = { type TSshHostServiceFactoryDep = {
userDAL: Pick<TUserDALFactory, "findById" | "find">; userDAL: Pick<TUserDALFactory, "findById" | "find">;
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
projectDAL: Pick<TProjectDALFactory, "find">; projectDAL: Pick<TProjectDALFactory, "find">;
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">; projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "findOne">;
sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">; sshCertificateAuthorityDAL: Pick<TSshCertificateAuthorityDALFactory, "findOne">;
sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">; sshCertificateAuthoritySecretDAL: Pick<TSshCertificateAuthoritySecretDALFactory, "findOne">;
sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">; sshCertificateDAL: Pick<TSshCertificateDALFactory, "create" | "transaction">;
sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">; sshCertificateBodyDAL: Pick<TSshCertificateBodyDALFactory, "create">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findGroupMembershipsByUserIdInOrg">;
sshHostDAL: Pick< sshHostDAL: Pick<
TSshHostDALFactory, TSshHostDALFactory,
| "transaction" | "transaction"
@@ -58,7 +62,10 @@ type TSshHostServiceFactoryDep = {
>; >;
sshHostLoginUserDAL: TSshHostLoginUserDALFactory; sshHostLoginUserDAL: TSshHostLoginUserDALFactory;
sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory; sshHostLoginUserMappingDAL: TSshHostLoginUserMappingDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">; permissionService: Pick<
TPermissionServiceFactory,
"getProjectPermission" | "getUserProjectPermission" | "checkGroupProjectPermission"
>;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">; kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
}; };
@@ -66,6 +73,8 @@ export type TSshHostServiceFactory = ReturnType<typeof sshHostServiceFactory>;
export const sshHostServiceFactory = ({ export const sshHostServiceFactory = ({
userDAL, userDAL,
userGroupMembershipDAL,
groupDAL,
projectDAL, projectDAL,
projectSshConfigDAL, projectSshConfigDAL,
sshCertificateAuthorityDAL, sshCertificateAuthorityDAL,
@@ -208,6 +217,7 @@ export const sshHostServiceFactory = ({
loginMappings, loginMappings,
sshHostLoginUserDAL, sshHostLoginUserDAL,
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
groupDAL,
userDAL, userDAL,
permissionService, permissionService,
projectId, projectId,
@@ -278,6 +288,7 @@ export const sshHostServiceFactory = ({
loginMappings, loginMappings,
sshHostLoginUserDAL, sshHostLoginUserDAL,
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
groupDAL,
userDAL, userDAL,
permissionService, permissionService,
projectId: host.projectId, projectId: host.projectId,
@@ -324,7 +335,7 @@ export const sshHostServiceFactory = ({
return host; return host;
}; };
const getSshHost = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => { const getSshHostById = async ({ sshHostId, actorId, actorAuthMethod, actor, actorOrgId }: TGetSshHostDTO) => {
const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId); const host = await sshHostDAL.findSshHostByIdWithLoginMappings(sshHostId);
if (!host) { if (!host) {
throw new NotFoundError({ throw new NotFoundError({
@@ -387,10 +398,14 @@ export const sshHostServiceFactory = ({
userDAL userDAL
}); });
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(actorId, actorOrgId);
const userGroupSlugs = userGroups.map((g) => g.groupSlug);
const mapping = host.loginMappings.find( const mapping = host.loginMappings.find(
(m) => (m) =>
m.loginUser === loginUser && m.loginUser === loginUser &&
m.allowedPrincipals.usernames.some((allowed) => internalPrincipals.includes(allowed)) (m.allowedPrincipals.usernames?.some((allowed) => internalPrincipals.includes(allowed)) ||
m.allowedPrincipals.groups?.some((allowed) => userGroupSlugs.includes(allowed)))
); );
if (!mapping) { if (!mapping) {
@@ -616,7 +631,7 @@ export const sshHostServiceFactory = ({
createSshHost, createSshHost,
updateSshHost, updateSshHost,
deleteSshHost, deleteSshHost,
getSshHost, getSshHostById,
issueSshHostUserCert, issueSshHostUserCert,
issueSshHostHostCert, issueSshHostHostCert,
getSshHostUserCaPk, getSshHostUserCaPk,
@@ -7,12 +7,15 @@ import { TProjectPermission } from "@app/lib/types";
import { ActorAuthMethod } from "@app/services/auth/auth-type"; import { ActorAuthMethod } from "@app/services/auth/auth-type";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { TGroupDALFactory } from "../group/group-dal";
export type TListSshHostsDTO = Omit<TProjectPermission, "projectId">; export type TListSshHostsDTO = Omit<TProjectPermission, "projectId">;
export type TLoginMapping = { export type TLoginMapping = {
loginUser: string; loginUser: string;
allowedPrincipals: { allowedPrincipals: {
usernames: string[]; usernames?: string[];
groups?: string[];
}; };
}; };
@@ -63,7 +66,8 @@ type BaseCreateSshLoginMappingsDTO = {
sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction">; sshHostLoginUserDAL: Pick<TSshHostLoginUserDALFactory, "create" | "transaction">;
sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">; sshHostLoginUserMappingDAL: Pick<TSshHostLoginUserMappingDALFactory, "insertMany">;
userDAL: Pick<TUserDALFactory, "find">; userDAL: Pick<TUserDALFactory, "find">;
permissionService: Pick<TPermissionServiceFactory, "getUserProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getUserProjectPermission" | "checkGroupProjectPermission">;
groupDAL: Pick<TGroupDALFactory, "findGroupsByProjectId">;
projectId: string; projectId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string; actorOrgId: string;
+40 -1
View File
@@ -1,6 +1,8 @@
import { Redis } from "ioredis"; import { Redis } from "ioredis";
import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext"; import { pgAdvisoryLockHashText } from "@app/lib/crypto/hashtext";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { Redlock, Settings } from "@app/lib/red-lock"; import { Redlock, Settings } from "@app/lib/red-lock";
export const PgSqlLock = { export const PgSqlLock = {
@@ -48,6 +50,13 @@ export const KeyStoreTtls = {
AccessTokenStatusUpdateInSeconds: 120 AccessTokenStatusUpdateInSeconds: 120
}; };
type TDeleteItems = {
pattern: string;
batchSize?: number;
delay?: number;
jitter?: number;
};
type TWaitTillReady = { type TWaitTillReady = {
key: string; key: string;
waitingCb?: () => void; waitingCb?: () => void;
@@ -75,6 +84,35 @@ export const keyStoreFactory = (redisUrl: string) => {
const deleteItem = async (key: string) => redis.del(key); const deleteItem = async (key: string) => redis.del(key);
const deleteItems = async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }: TDeleteItems) => {
let cursor = "0";
let totalDeleted = 0;
do {
// Await in loop is needed so that Redis is not overwhelmed
// eslint-disable-next-line no-await-in-loop
const [nextCursor, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 1000); // Count should be 1000 - 5000 for prod loads
cursor = nextCursor;
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
const pipeline = redis.pipeline();
for (const key of batch) {
pipeline.unlink(key);
}
// eslint-disable-next-line no-await-in-loop
await pipeline.exec();
totalDeleted += batch.length;
console.log("BATCH DONE");
// eslint-disable-next-line no-await-in-loop
await delayMs(Math.max(0, applyJitter(delay, jitter)));
}
} while (cursor !== "0");
return totalDeleted;
};
const incrementBy = async (key: string, value: number) => redis.incrby(key, value); const incrementBy = async (key: string, value: number) => redis.incrby(key, value);
const setExpiry = async (key: string, expiryInSeconds: number) => redis.expire(key, expiryInSeconds); const setExpiry = async (key: string, expiryInSeconds: number) => redis.expire(key, expiryInSeconds);
@@ -94,7 +132,7 @@ export const keyStoreFactory = (redisUrl: string) => {
// eslint-disable-next-line // eslint-disable-next-line
await new Promise((resolve) => { await new Promise((resolve) => {
waitingCb?.(); waitingCb?.();
setTimeout(resolve, Math.max(0, delay + Math.floor((Math.random() * 2 - 1) * jitter))); setTimeout(resolve, Math.max(0, applyJitter(delay, jitter)));
}); });
attempts += 1; attempts += 1;
// eslint-disable-next-line // eslint-disable-next-line
@@ -108,6 +146,7 @@ export const keyStoreFactory = (redisUrl: string) => {
setExpiry, setExpiry,
setItemWithExpiry, setItemWithExpiry,
deleteItem, deleteItem,
deleteItems,
incrementBy, incrementBy,
acquireLock(resources: string[], duration: number, settings?: Partial<Settings>) { acquireLock(resources: string[], duration: number, settings?: Partial<Settings>) {
return redisLock.acquire(resources, duration, settings); return redisLock.acquire(resources, duration, settings);
+25
View File
@@ -1,3 +1,7 @@
import RE2 from "re2";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { Lock } from "@app/lib/red-lock"; import { Lock } from "@app/lib/red-lock";
import { TKeyStoreFactory } from "./keystore"; import { TKeyStoreFactory } from "./keystore";
@@ -19,6 +23,27 @@ export const inMemoryKeyStore = (): TKeyStoreFactory => {
delete store[key]; delete store[key];
return 1; return 1;
}, },
deleteItems: async ({ pattern, batchSize = 500, delay = 1500, jitter = 200 }) => {
const regex = new RE2(`^${pattern.replace(/[-[\]/{}()+?.\\^$|]/g, "\\$&").replace(/\*/g, ".*")}$`);
let totalDeleted = 0;
const keys = Object.keys(store);
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
for (const key of batch) {
if (regex.test(key)) {
delete store[key];
totalDeleted += 1;
}
}
// eslint-disable-next-line no-await-in-loop
await delayMs(Math.max(0, applyJitter(delay, jitter)));
}
return totalDeleted;
},
getItem: async (key) => { getItem: async (key) => {
const value = store[key]; const value = store[key];
if (typeof value === "string") { if (typeof value === "string") {
+169 -4
View File
@@ -14,10 +14,12 @@ export enum ApiDocsTags {
UniversalAuth = "Universal Auth", UniversalAuth = "Universal Auth",
GcpAuth = "GCP Auth", GcpAuth = "GCP Auth",
AwsAuth = "AWS Auth", AwsAuth = "AWS Auth",
OciAuth = "OCI Auth",
AzureAuth = "Azure Auth", AzureAuth = "Azure Auth",
KubernetesAuth = "Kubernetes Auth", KubernetesAuth = "Kubernetes Auth",
JwtAuth = "JWT Auth", JwtAuth = "JWT Auth",
OidcAuth = "OIDC Auth", OidcAuth = "OIDC Auth",
LdapAuth = "LDAP Auth",
Groups = "Groups", Groups = "Groups",
Organizations = "Organizations", Organizations = "Organizations",
Projects = "Projects", Projects = "Projects",
@@ -45,6 +47,7 @@ export enum ApiDocsTags {
PkiCertificateTemplates = "PKI Certificate Templates", PkiCertificateTemplates = "PKI Certificate Templates",
PkiCertificateCollections = "PKI Certificate Collections", PkiCertificateCollections = "PKI Certificate Collections",
PkiAlerting = "PKI Alerting", PkiAlerting = "PKI Alerting",
PkiSubscribers = "PKI Subscribers",
SshCertificates = "SSH Certificates", SshCertificates = "SSH Certificates",
SshCertificateAuthorities = "SSH Certificate Authorities", SshCertificateAuthorities = "SSH Certificate Authorities",
SshCertificateTemplates = "SSH Certificate Templates", SshCertificateTemplates = "SSH Certificate Templates",
@@ -184,6 +187,49 @@ export const UNIVERSAL_AUTH = {
} }
} as const; } as const;
export const LDAP_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
username: "The username of the LDAP user to login.",
password: "The password of the LDAP user to login."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
url: "The URL of the LDAP server.",
allowedFields:
"The comma-separated array of key/value pairs of required fields that the LDAP entry must have in order to authenticate.",
searchBase: "The base DN to search for the LDAP user.",
searchFilter: "The filter to use to search for the LDAP user.",
bindDN: "The DN of the user to bind to the LDAP server.",
bindPass: "The password of the user to bind to the LDAP server.",
ldapCaCertificate: "The PEM-encoded CA certificate for the LDAP server.",
accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the configuration for.",
url: "The new URL of the LDAP server.",
allowedFields: "The comma-separated list of allowed fields to return from the LDAP user.",
searchBase: "The new base DN to search for the LDAP user.",
searchFilter: "The new filter to use to search for the LDAP user.",
bindDN: "The new DN of the user to bind to the LDAP server.",
bindPass: "The new password of the user to bind to the LDAP server.",
ldapCaCertificate: "The new PEM-encoded CA certificate for the LDAP server.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the configuration for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the configuration for."
}
} as const;
export const AWS_AUTH = { export const AWS_AUTH = {
LOGIN: { LOGIN: {
identityId: "The ID of the identity to login.", identityId: "The ID of the identity to login.",
@@ -226,6 +272,40 @@ export const AWS_AUTH = {
} }
} as const; } as const;
export const OCI_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
userOcid: "The OCID of the user attempting login.",
headers: "The headers of the signed request."
},
ATTACH: {
identityId: "The ID of the identity to attach the configuration onto.",
tenancyOcid: "The OCID of your tenancy.",
allowedUsernames:
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
},
UPDATE: {
identityId: "The ID of the identity to update the auth method for.",
tenancyOcid: "The OCID of your tenancy.",
allowedUsernames:
"The comma-separated list of trusted OCI account usernames that are allowed to authenticate with Infisical.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
},
RETRIEVE: {
identityId: "The ID of the identity to retrieve the auth method for."
},
REVOKE: {
identityId: "The ID of the identity to revoke the auth method for."
}
} as const;
export const AZURE_AUTH = { export const AZURE_AUTH = {
LOGIN: { LOGIN: {
identityId: "The ID of the identity to login." identityId: "The ID of the identity to login."
@@ -313,6 +393,7 @@ export const KUBERNETES_AUTH = {
allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.", allowedNames: "The comma-separated list of trusted service account names that can authenticate with Infisical.",
allowedAudience: allowedAudience:
"The optional audience claim that the service account JWT token must have to authenticate with Infisical.", "The optional audience claim that the service account JWT token must have to authenticate with Infisical.",
gatewayId: "The ID of the gateway to use when performing kubernetes API requests.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The lifetime for an access token in seconds.", accessTokenTTL: "The lifetime for an access token in seconds.",
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.", accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
@@ -329,6 +410,7 @@ export const KUBERNETES_AUTH = {
allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.", allowedNames: "The new comma-separated list of trusted service account names that can authenticate with Infisical.",
allowedAudience: allowedAudience:
"The new optional audience claim that the service account JWT token must have to authenticate with Infisical.", "The new optional audience claim that the service account JWT token must have to authenticate with Infisical.",
gatewayId: "The ID of the gateway to use when performing kubernetes API requests.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.", accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an acccess token in seconds.", accessTokenTTL: "The new lifetime for an acccess token in seconds.",
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.", accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
@@ -526,7 +608,8 @@ export const PROJECTS = {
projectDescription: "An optional description label for the project.", projectDescription: "An optional description label for the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project.", autoCapitalization: "Disable or enable auto-capitalization for the project.",
slug: "An optional slug for the project. (must be unique within the organization)", slug: "An optional slug for the project. (must be unique within the organization)",
hasDeleteProtection: "Enable or disable delete protection for the project." hasDeleteProtection: "Enable or disable delete protection for the project.",
secretSharing: "Enable or disable secret sharing for the project."
}, },
GET_KEY: { GET_KEY: {
workspaceId: "The ID of the project to get the key from." workspaceId: "The ID of the project to get the key from."
@@ -595,6 +678,9 @@ export const PROJECTS = {
commonName: "The common name of the certificate to filter by.", commonName: "The common name of the certificate to filter by.",
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.", offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
limit: "The number of certificates to return." limit: "The number of certificates to return."
},
LIST_PKI_SUBSCRIBERS: {
projectId: "The ID of the project to list PKI subscribers for."
} }
} as const; } as const;
@@ -1434,7 +1520,7 @@ export const SSH_HOSTS = {
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')", loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
allowedPrincipals: "A list of allowed principals that can log in as the login user.", allowedPrincipals: "A list of allowed principals that can log in as the login user.",
loginMappings: loginMappings:
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project.", "A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users or groups slugs in the Infisical SSH project.",
userSshCaId: userSshCaId:
"The ID of the SSH CA to use for user certificates. If not specified, the default user SSH CA will be used if it exists.", "The ID of the SSH CA to use for user certificates. If not specified, the default user SSH CA will be used if it exists.",
hostSshCaId: hostSshCaId:
@@ -1449,7 +1535,7 @@ export const SSH_HOSTS = {
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')", loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
allowedPrincipals: "A list of allowed principals that can log in as the login user.", allowedPrincipals: "A list of allowed principals that can log in as the login user.",
loginMappings: loginMappings:
"A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users in the Infisical SSH project." "A list of login mappings for the SSH host. Each login mapping contains a login user and a list of corresponding allowed principals being usernames of users or groups slugs in the Infisical SSH project."
}, },
DELETE: { DELETE: {
sshHostId: "The ID of the SSH host to delete." sshHostId: "The ID of the SSH host to delete."
@@ -1619,7 +1705,8 @@ export const CERTIFICATES = {
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.", serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
certificate: "The certificate body of the certificate.", certificate: "The certificate body of the certificate.",
certificateChain: "The certificate chain of the certificate.", certificateChain: "The certificate chain of the certificate.",
serialNumberRes: "The serial number of the certificate." serialNumberRes: "The serial number of the certificate.",
privateKey: "The private key of the certificate."
} }
}; };
@@ -1686,6 +1773,67 @@ export const ALERTS = {
} }
}; };
export const PKI_SUBSCRIBERS = {
GET: {
subscriberName: "The name of the PKI subscriber to get.",
projectId: "The ID of the project to get the PKI subscriber for."
},
CREATE: {
projectId: "The ID of the project to create the PKI subscriber in.",
caId: "The ID of the CA that will issue certificates for the PKI subscriber.",
name: "The name of the PKI subscriber.",
commonName: "The common name (CN) to be used on certificates issued for this subscriber.",
status: "The status of the PKI subscriber. This can be one of active or disabled.",
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
subjectAlternativeNames:
"A list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
keyUsages: "The key usage extension to be used on certificates issued for this subscriber.",
extendedKeyUsages: "The extended key usage extension to be used on certificates issued for this subscriber."
},
UPDATE: {
projectId: "The ID of the project to update the PKI subscriber in.",
subscriberName: "The name of the PKI subscriber to update.",
caId: "The ID of the CA that will issue certificates for the PKI subscriber to update to.",
name: "The name of the PKI subscriber to update to.",
commonName: "The common name (CN) to be used on certificates issued for this subscriber to update to.",
status: "The status of the PKI subscriber to update to. This can be one of active or disabled.",
ttl: "The time to live for the certificates issued for this subscriber such as 1m, 1h, 1d, 1y, ...",
subjectAlternativeNames:
"A comma-delimited list of Subject Alternative Names (SANs) to be used on certificates issued for this subscriber; these can be host names or email addresses.",
keyUsages: "The key usage extension to be used on certificates issued for this subscriber to update to.",
extendedKeyUsages:
"The extended key usage extension to be used on certificates issued for this subscriber to update to."
},
DELETE: {
subscriberName: "The name of the PKI subscriber to delete.",
projectId: "The ID of the project of the PKI subscriber to delete."
},
ISSUE_CERT: {
subscriberName: "The name of the PKI subscriber to issue the certificate for.",
projectId: "The ID of the project of the PKI subscriber to issue the certificate for.",
certificate: "The issued certificate.",
issuingCaCertificate: "The certificate of the issuing CA.",
certificateChain: "The certificate chain of the issued certificate.",
privateKey: "The private key of the issued certificate.",
serialNumber: "The serial number of the issued certificate."
},
SIGN_CERT: {
subscriberName: "The name of the PKI subscriber to sign the certificate for.",
projectId: "The ID of the project of the PKI subscriber to sign the certificate for.",
csr: "The CSR to be used to sign the certificate.",
certificate: "The signed certificate.",
issuingCaCertificate: "The certificate of the issuing CA.",
certificateChain: "The certificate chain of the signed certificate.",
serialNumber: "The serial number of the signed certificate."
},
LIST_CERTS: {
subscriberName: "The name of the PKI subscriber to list the certificates for.",
projectId: "The ID of the project of the PKI subscriber to list the certificates for.",
offset: "The offset to start from.",
limit: "The number of certificates to return."
}
};
export const PKI_COLLECTIONS = { export const PKI_COLLECTIONS = {
CREATE: { CREATE: {
projectId: "The ID of the project to create the PKI collection in.", projectId: "The ID of the project to create the PKI collection in.",
@@ -1821,8 +1969,12 @@ export const KMS = {
}; };
export const ProjectTemplates = { export const ProjectTemplates = {
LIST: {
type: "The type of project template to list."
},
CREATE: { CREATE: {
name: "The name of the project template to be created. Must be slug-friendly.", name: "The name of the project template to be created. Must be slug-friendly.",
type: "The type of project template to be created.",
description: "An optional description of the project template.", description: "An optional description of the project template.",
roles: "The roles to be created when the template is applied to a project.", roles: "The roles to be created when the template is applied to a project.",
environments: "The environments to be created when the template is applied to a project." environments: "The environments to be created when the template is applied to a project."
@@ -1925,6 +2077,13 @@ export const AppConnections = {
AZURE_CLIENT_SECRETS: { AZURE_CLIENT_SECRETS: {
code: "The OAuth code to use to connect with Azure Client Secrets.", code: "The OAuth code to use to connect with Azure Client Secrets.",
tenantId: "The Tenant ID to use to connect with Azure Client Secrets." tenantId: "The Tenant ID to use to connect with Azure Client Secrets."
},
OCI: {
userOcid: "The OCID (Oracle Cloud Identifier) of the user making the request.",
tenancyOcid: "The OCID (Oracle Cloud Identifier) of the tenancy in Oracle Cloud Infrastructure.",
region: "The region identifier in Oracle Cloud Infrastructure where the vault is located.",
fingerprint: "The fingerprint of the public key uploaded to the user's API keys.",
privateKey: "The private key content in PEM format used to sign API requests."
} }
} }
}; };
@@ -1988,6 +2147,7 @@ export const SecretSyncs = {
const destinationName = SECRET_SYNC_NAME_MAP[destination]; const destinationName = SECRET_SYNC_NAME_MAP[destination];
return { return {
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`, initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
keySchema: `Specify the format to use for structuring secret keys in the ${destinationName} destination.`,
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.` disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
}; };
}, },
@@ -2072,6 +2232,11 @@ export const SecretSyncs = {
TEAMCITY: { TEAMCITY: {
project: "The TeamCity project to sync secrets to.", project: "The TeamCity project to sync secrets to.",
buildConfig: "The TeamCity build configuration to sync secrets to." buildConfig: "The TeamCity build configuration to sync secrets to."
},
OCI_VAULT: {
compartmentOcid: "The OCID (Oracle Cloud Identifier) of the compartment where the vault is located.",
vaultOcid: "The OCID (Oracle Cloud Identifier) of the vault to sync secrets to.",
keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault."
} }
} }
}; };
+1
View File
@@ -146,6 +146,7 @@ const envSchema = z
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()), SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()), SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()), SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
SECRET_SCANNING_GIT_APP_SLUG: zpStr(z.string().default("infisical-radar")),
// LICENSE // LICENSE
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")), LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
LICENSE_SERVER_KEY: zpStr(z.string().optional()), LICENSE_SERVER_KEY: zpStr(z.string().optional()),
+4
View File
@@ -0,0 +1,4 @@
export const delay = (ms: number) =>
new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});
+46 -14
View File
@@ -174,6 +174,8 @@ const setupProxyServer = async ({
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const server = net.createServer(); const server = net.createServer();
let streamClosed = false;
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
server.on("connection", async (clientConn) => { server.on("connection", async (clientConn) => {
try { try {
@@ -202,9 +204,15 @@ const setupProxyServer = async ({
// Handle client connection close // Handle client connection close
clientConn.on("end", () => { clientConn.on("end", () => {
writer.close().catch((err) => { if (!streamClosed) {
logger.error(err); try {
}); writer.close().catch((err) => {
logger.debug(err, "Error closing writer (already closed)");
});
} catch (error) {
logger.debug(error, "Error in writer close");
}
}
}); });
clientConn.on("error", (clientConnErr) => { clientConn.on("error", (clientConnErr) => {
@@ -249,14 +257,29 @@ const setupProxyServer = async ({
setupCopy(); setupCopy();
// Handle connection closure // Handle connection closure
clientConn.on("close", () => { clientConn.on("close", () => {
stream.destroy().catch((err) => { if (!streamClosed) {
proxyErrorMsg.push((err as Error)?.message); streamClosed = true;
}); stream.destroy().catch((err) => {
logger.debug(err, "Stream already destroyed during close event");
});
}
}); });
const cleanup = async () => { const cleanup = async () => {
clientConn?.destroy(); try {
await stream.destroy(); clientConn?.destroy();
} catch (err) {
logger.debug(err, "Error destroying client connection");
}
if (!streamClosed) {
streamClosed = true;
try {
await stream.destroy();
} catch (err) {
logger.debug(err, "Error destroying stream (might be already closed)");
}
}
}; };
clientConn.on("error", (clientConnErr) => { clientConn.on("error", (clientConnErr) => {
@@ -301,8 +324,17 @@ const setupProxyServer = async ({
server, server,
port: address.port, port: address.port,
cleanup: async () => { cleanup: async () => {
server.close(); try {
await quicClient?.destroy(); server.close();
} catch (err) {
logger.debug(err, "Error closing server");
}
try {
await quicClient?.destroy();
} catch (err) {
logger.debug(err, "Error destroying QUIC client");
}
}, },
getProxyError: () => proxyErrorMsg.join(",") getProxyError: () => proxyErrorMsg.join(",")
}); });
@@ -320,10 +352,10 @@ interface ProxyOptions {
orgId: string; orgId: string;
} }
export const withGatewayProxy = async ( export const withGatewayProxy = async <T>(
callback: (port: number) => Promise<void>, callback: (port: number) => Promise<T>,
options: ProxyOptions options: ProxyOptions
): Promise<void> => { ): Promise<T> => {
const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options; const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options;
// Setup the proxy server // Setup the proxy server
@@ -339,7 +371,7 @@ export const withGatewayProxy = async (
try { try {
// Execute the callback with the allocated port // Execute the callback with the allocated port
await callback(port); return await callback(port);
} catch (err) { } catch (err) {
const proxyErrorMessage = getProxyError(); const proxyErrorMessage = getProxyError();
if (proxyErrorMessage) { if (proxyErrorMessage) {
+2 -2
View File
@@ -32,13 +32,13 @@ export const buildFindFilter =
<R extends object = object>( <R extends object = object>(
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>, { $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
tableName?: TableName, tableName?: TableName,
excludeKeys?: Array<keyof R> excludeKeys?: string[]
) => ) =>
(bd: Knex.QueryBuilder<R, R>) => { (bd: Knex.QueryBuilder<R, R>) => {
const processedFilter = tableName const processedFilter = tableName
? Object.fromEntries( ? Object.fromEntries(
Object.entries(filter) Object.entries(filter)
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R)) .filter(([key]) => !excludeKeys || !excludeKeys.includes(key))
.map(([key, value]) => [`${tableName}.${key}`, value]) .map(([key, value]) => [`${tableName}.${key}`, value])
) )
: filter; : filter;
+3 -1
View File
@@ -84,7 +84,9 @@ const redactedKeys = [
"secrets", "secrets",
"key", "key",
"password", "password",
"config" "config",
"bindPass",
"bindDN"
]; ];
const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID"; const UNKNOWN_REQUEST_ID = "UNKNOWN_REQUEST_ID";
+13 -2
View File
@@ -25,6 +25,7 @@ import {
TQueueSecretSyncSyncSecretsByIdDTO, TQueueSecretSyncSyncSecretsByIdDTO,
TQueueSendSecretSyncActionFailedNotificationsDTO TQueueSendSecretSyncActionFailedNotificationsDTO
} from "@app/services/secret-sync/secret-sync-types"; } from "@app/services/secret-sync/secret-sync-types";
import { CacheType } from "@app/services/super-admin/super-admin-types";
import { TWebhookPayloads } from "@app/services/webhook/webhook-types"; import { TWebhookPayloads } from "@app/services/webhook/webhook-types";
export enum QueueName { export enum QueueName {
@@ -50,7 +51,8 @@ export enum QueueName {
ImportSecretsFromExternalSource = "import-secrets-from-external-source", ImportSecretsFromExternalSource = "import-secrets-from-external-source",
AppConnectionSecretSync = "app-connection-secret-sync", AppConnectionSecretSync = "app-connection-secret-sync",
SecretRotationV2 = "secret-rotation-v2", SecretRotationV2 = "secret-rotation-v2",
FolderTreeCheckpoint = "folder-tree-checkpoint" FolderTreeCheckpoint = "folder-tree-checkpoint",
InvalidateCache = "invalidate-cache"
} }
export enum QueueJobs { export enum QueueJobs {
@@ -83,7 +85,8 @@ export enum QueueJobs {
SecretRotationV2QueueRotations = "secret-rotation-v2-queue-rotations", SecretRotationV2QueueRotations = "secret-rotation-v2-queue-rotations",
SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets", SecretRotationV2RotateSecrets = "secret-rotation-v2-rotate-secrets",
SecretRotationV2SendNotification = "secret-rotation-v2-send-notification", SecretRotationV2SendNotification = "secret-rotation-v2-send-notification",
CreateFolderTreeCheckpoint = "create-folder-tree-checkpoint" CreateFolderTreeCheckpoint = "create-folder-tree-checkpoint",
InvalidateCache = "invalidate-cache"
} }
export type TQueueJobTypes = { export type TQueueJobTypes = {
@@ -242,6 +245,14 @@ export type TQueueJobTypes = {
name: QueueJobs.SecretRotationV2SendNotification; name: QueueJobs.SecretRotationV2SendNotification;
payload: TSecretRotationSendNotificationJobPayload; payload: TSecretRotationSendNotificationJobPayload;
}; };
[QueueName.InvalidateCache]: {
name: QueueJobs.InvalidateCache;
payload: {
data: {
type: CacheType;
};
};
};
}; };
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>; export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
+15
View File
@@ -100,3 +100,18 @@ export const publicSshCaLimit: RateLimitOptions = {
max: 30, // conservative default max: 30, // conservative default
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };
export const invalidateCacheLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
hook: "preValidation",
max: 2,
keyGenerator: (req) => req.realIp
};
// Makes spamming "request access" harder, preventing email DDoS
export const requestAccessLimit: RateLimitOptions = {
timeWindow: 60 * 1000,
hook: "preValidation",
max: 10,
keyGenerator: (req) => req.realIp
};
+8
View File
@@ -0,0 +1,8 @@
import { FastifyReply } from "fastify";
export const addNoCacheHeaders = (reply: FastifyReply) => {
void reply.header("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
void reply.header("Pragma", "no-cache");
void reply.header("Expires", "0");
void reply.header("Surrogate-Control", "no-store");
};
+18 -2
View File
@@ -5,7 +5,7 @@
import type { FastifySchema, FastifySchemaCompiler, FastifyTypeProvider } from "fastify"; import type { FastifySchema, FastifySchemaCompiler, FastifyTypeProvider } from "fastify";
import type { FastifySerializerCompiler } from "fastify/types/schema"; import type { FastifySerializerCompiler } from "fastify/types/schema";
import type { z, ZodAny, ZodTypeAny } from "zod"; import type { z, ZodAny, ZodTypeAny } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema"; import { PostProcessCallback, zodToJsonSchema } from "zod-to-json-schema";
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
type FreeformRecord = Record<string, any>; type FreeformRecord = Record<string, any>;
@@ -28,9 +28,25 @@ interface Schema extends FastifySchema {
hide?: boolean; hide?: boolean;
} }
// Credit: https://github.com/StefanTerdell/zod-to-json-schema
const jsonDescription: PostProcessCallback = (jsonSchema, def) => {
if (def.description) {
try {
return {
...jsonSchema,
description: undefined,
...JSON.parse(def.description)
};
} catch {}
}
return jsonSchema;
};
const zodToJsonSchemaOptions = { const zodToJsonSchemaOptions = {
target: "openApi3", target: "openApi3",
$refStrategy: "none" $refStrategy: "none",
postProcess: jsonDescription
} as const; } as const;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
+3 -1
View File
@@ -57,7 +57,9 @@ export const registerServeUI = async (
reply.callNotFound(); reply.callNotFound();
return; return;
} }
return reply.sendFile("index.html"); // reference: https://github.com/fastify/fastify-static?tab=readme-ov-file#managing-cache-control-headers
// to avoid ui bundle skew on new deployment
return reply.sendFile("index.html", { maxAge: 0, immutable: false });
} }
}); });
} }
+73 -10
View File
@@ -32,7 +32,6 @@ import { externalKmsServiceFactory } from "@app/ee/services/external-kms/externa
import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal"; import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal"; import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal"; import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service"; import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
import { groupDALFactory } from "@app/ee/services/group/group-dal"; import { groupDALFactory } from "@app/ee/services/group/group-dal";
@@ -126,6 +125,7 @@ import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
@@ -167,6 +167,10 @@ import { identityJwtAuthDALFactory } from "@app/services/identity-jwt-auth/ident
import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service"; import { identityJwtAuthServiceFactory } from "@app/services/identity-jwt-auth/identity-jwt-auth-service";
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal"; import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service"; import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { identityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
import { identityLdapAuthServiceFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-service";
import { identityOciAuthDALFactory } from "@app/services/identity-oci-auth/identity-oci-auth-dal";
import { identityOciAuthServiceFactory } from "@app/services/identity-oci-auth/identity-oci-auth-service";
import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal"; import { identityOidcAuthDALFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-dal";
import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service"; import { identityOidcAuthServiceFactory } from "@app/services/identity-oidc-auth/identity-oidc-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
@@ -202,6 +206,8 @@ import { pkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-servic
import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; import { pkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal";
import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal"; import { pkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal";
import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service"; import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { projectDALFactory } from "@app/services/project/project-dal"; import { projectDALFactory } from "@app/services/project/project-dal";
import { projectQueueFactory } from "@app/services/project/project-queue"; import { projectQueueFactory } from "@app/services/project/project-queue";
import { projectServiceFactory } from "@app/services/project/project-service"; import { projectServiceFactory } from "@app/services/project/project-service";
@@ -249,6 +255,7 @@ import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-
import { slackIntegrationDALFactory } from "@app/services/slack/slack-integration-dal"; import { slackIntegrationDALFactory } from "@app/services/slack/slack-integration-dal";
import { slackServiceFactory } from "@app/services/slack/slack-service"; import { slackServiceFactory } from "@app/services/slack/slack-service";
import { TSmtpService } from "@app/services/smtp/smtp-service"; import { TSmtpService } from "@app/services/smtp/smtp-service";
import { invalidateCacheQueueFactory } from "@app/services/super-admin/invalidate-cache-queue";
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal"; import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service"; import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
import { telemetryDALFactory } from "@app/services/telemetry/telemetry-dal"; import { telemetryDALFactory } from "@app/services/telemetry/telemetry-dal";
@@ -357,9 +364,11 @@ export const registerRoutes = async (
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db); const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsAuthDAL = identityAwsAuthDALFactory(db); const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
const identityGcpAuthDAL = identityGcpAuthDALFactory(db); const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
const identityOciAuthDAL = identityOciAuthDALFactory(db);
const identityOidcAuthDAL = identityOidcAuthDALFactory(db); const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
const identityJwtAuthDAL = identityJwtAuthDALFactory(db); const identityJwtAuthDAL = identityJwtAuthDALFactory(db);
const identityAzureAuthDAL = identityAzureAuthDALFactory(db); const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
const identityLdapAuthDAL = identityLdapAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db); const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
const auditLogStreamDAL = auditLogStreamDALFactory(db); const auditLogStreamDAL = auditLogStreamDALFactory(db);
@@ -437,7 +446,6 @@ export const registerRoutes = async (
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db); const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
const gatewayDAL = gatewayDALFactory(db); const gatewayDAL = gatewayDALFactory(db);
const projectGatewayDAL = projectGatewayDALFactory(db);
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db); const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
const githubOrgSyncDAL = githubOrgSyncDALFactory(db); const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
@@ -649,6 +657,11 @@ export const registerRoutes = async (
queueService queueService
}); });
const invalidateCacheQueue = invalidateCacheQueueFactory({
keyStore,
queueService
});
const userService = userServiceFactory({ const userService = userServiceFactory({
userDAL, userDAL,
userAliasDAL, userAliasDAL,
@@ -760,7 +773,8 @@ export const registerRoutes = async (
keyStore, keyStore,
licenseService, licenseService,
kmsService, kmsService,
microsoftTeamsService microsoftTeamsService,
invalidateCacheQueue
}); });
const orgAdminService = orgAdminServiceFactory({ const orgAdminService = orgAdminServiceFactory({
@@ -851,14 +865,17 @@ export const registerRoutes = async (
const certificateDAL = certificateDALFactory(db); const certificateDAL = certificateDALFactory(db);
const certificateBodyDAL = certificateBodyDALFactory(db); const certificateBodyDAL = certificateBodyDALFactory(db);
const certificateSecretDAL = certificateSecretDALFactory(db);
const pkiAlertDAL = pkiAlertDALFactory(db); const pkiAlertDAL = pkiAlertDALFactory(db);
const pkiCollectionDAL = pkiCollectionDALFactory(db); const pkiCollectionDAL = pkiCollectionDALFactory(db);
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db); const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
const certificateService = certificateServiceFactory({ const certificateService = certificateServiceFactory({
certificateDAL, certificateDAL,
certificateBodyDAL, certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL, certificateAuthorityCertDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
@@ -896,6 +913,8 @@ export const registerRoutes = async (
const sshHostService = sshHostServiceFactory({ const sshHostService = sshHostServiceFactory({
userDAL, userDAL,
groupDAL,
userGroupMembershipDAL,
projectDAL, projectDAL,
projectSshConfigDAL, projectSshConfigDAL,
sshCertificateAuthorityDAL, sshCertificateAuthorityDAL,
@@ -918,7 +937,8 @@ export const registerRoutes = async (
sshHostLoginUserMappingDAL, sshHostLoginUserMappingDAL,
userDAL, userDAL,
permissionService, permissionService,
licenseService licenseService,
groupDAL
}); });
const certificateAuthorityService = certificateAuthorityServiceFactory({ const certificateAuthorityService = certificateAuthorityServiceFactory({
@@ -930,6 +950,7 @@ export const registerRoutes = async (
certificateAuthorityQueue, certificateAuthorityQueue,
certificateDAL, certificateDAL,
certificateBodyDAL, certificateBodyDAL,
certificateSecretDAL,
pkiCollectionDAL, pkiCollectionDAL,
pkiCollectionItemDAL, pkiCollectionItemDAL,
projectDAL, projectDAL,
@@ -984,6 +1005,20 @@ export const registerRoutes = async (
projectDAL projectDAL
}); });
const pkiSubscriberService = pkiSubscriberServiceFactory({
pkiSubscriberDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
projectDAL,
kmsService,
permissionService
});
const projectTemplateService = projectTemplateServiceFactory({ const projectTemplateService = projectTemplateServiceFactory({
licenseService, licenseService,
permissionService, permissionService,
@@ -1083,6 +1118,7 @@ export const registerRoutes = async (
projectRoleDAL, projectRoleDAL,
folderDAL, folderDAL,
licenseService, licenseService,
pkiSubscriberDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateDAL, certificateDAL,
pkiAlertDAL, pkiAlertDAL,
@@ -1430,12 +1466,24 @@ export const registerRoutes = async (
identityUaDAL, identityUaDAL,
licenseService licenseService
}); });
const gatewayService = gatewayServiceFactory({
permissionService,
gatewayDAL,
kmsService,
licenseService,
orgGatewayConfigDAL,
keyStore
});
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({ const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityKubernetesAuthDAL, identityKubernetesAuthDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
permissionService, permissionService,
licenseService, licenseService,
gatewayService,
gatewayDAL,
kmsService kmsService
}); });
const identityGcpAuthService = identityGcpAuthServiceFactory({ const identityGcpAuthService = identityGcpAuthServiceFactory({
@@ -1462,6 +1510,14 @@ export const registerRoutes = async (
licenseService licenseService
}); });
const identityOciAuthService = identityOciAuthServiceFactory({
identityAccessTokenDAL,
identityOciAuthDAL,
identityOrgMembershipDAL,
licenseService,
permissionService
});
const identityOidcAuthService = identityOidcAuthServiceFactory({ const identityOidcAuthService = identityOidcAuthServiceFactory({
identityOidcAuthDAL, identityOidcAuthDAL,
identityOrgMembershipDAL, identityOrgMembershipDAL,
@@ -1480,14 +1536,14 @@ export const registerRoutes = async (
kmsService kmsService
}); });
const gatewayService = gatewayServiceFactory({ const identityLdapAuthService = identityLdapAuthServiceFactory({
identityLdapAuthDAL,
permissionService, permissionService,
gatewayDAL,
kmsService, kmsService,
identityAccessTokenDAL,
identityOrgMembershipDAL,
licenseService, licenseService,
orgGatewayConfigDAL, identityDAL
keyStore,
projectGatewayDAL
}); });
const dynamicSecretProviders = buildDynamicSecretProviders({ const dynamicSecretProviders = buildDynamicSecretProviders({
@@ -1511,7 +1567,7 @@ export const registerRoutes = async (
permissionService, permissionService,
licenseService, licenseService,
kmsService, kmsService,
projectGatewayDAL, gatewayDAL,
resourceMetadataDAL resourceMetadataDAL
}); });
@@ -1741,8 +1797,10 @@ export const registerRoutes = async (
identityGcpAuth: identityGcpAuthService, identityGcpAuth: identityGcpAuthService,
identityAwsAuth: identityAwsAuthService, identityAwsAuth: identityAwsAuthService,
identityAzureAuth: identityAzureAuthService, identityAzureAuth: identityAzureAuthService,
identityOciAuth: identityOciAuthService,
identityOidcAuth: identityOidcAuthService, identityOidcAuth: identityOidcAuthService,
identityJwtAuth: identityJwtAuthService, identityJwtAuth: identityJwtAuthService,
identityLdapAuth: identityLdapAuthService,
accessApprovalPolicy: accessApprovalPolicyService, accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService, accessApprovalRequest: accessApprovalRequestService,
secretApprovalPolicy: secretApprovalPolicyService, secretApprovalPolicy: secretApprovalPolicyService,
@@ -1766,6 +1824,7 @@ export const registerRoutes = async (
certificateEst: certificateEstService, certificateEst: certificateEstService,
pkiAlert: pkiAlertService, pkiAlert: pkiAlertService,
pkiCollection: pkiCollectionService, pkiCollection: pkiCollectionService,
pkiSubscriber: pkiSubscriberService,
secretScanning: secretScanningService, secretScanning: secretScanningService,
license: licenseService, license: licenseService,
trustedIp: trustedIpService, trustedIp: trustedIpService,
@@ -1809,6 +1868,10 @@ export const registerRoutes = async (
if (licenseSyncJob) { if (licenseSyncJob) {
cronJobs.push(licenseSyncJob); cronJobs.push(licenseSyncJob);
} }
const microsoftTeamsSyncJob = await microsoftTeamsService.initializeBackgroundSync();
if (microsoftTeamsSyncJob) {
cronJobs.push(microsoftTeamsSyncJob);
}
} }
server.decorate<FastifyZodProvider["store"]>("store", { server.decorate<FastifyZodProvider["store"]>("store", {
@@ -261,7 +261,8 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
pitVersionLimit: true, pitVersionLimit: true,
kmsCertificateKeyId: true, kmsCertificateKeyId: true,
auditLogsRetentionDays: true, auditLogsRetentionDays: true,
hasDeleteProtection: true hasDeleteProtection: true,
secretSharing: true
}); });
export const SanitizedTagSchema = SecretTagsSchema.pick({ export const SanitizedTagSchema = SecretTagsSchema.pick({
+68 -2
View File
@@ -4,13 +4,14 @@ import { z } from "zod";
import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas"; import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } 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 { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin"; import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
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";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types"; import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { LoginMethod } from "@app/services/super-admin/super-admin-types"; import { CacheType, LoginMethod } from "@app/services/super-admin/super-admin-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
export const registerAdminRouter = async (server: FastifyZodProvider) => { export const registerAdminRouter = async (server: FastifyZodProvider) => {
@@ -548,4 +549,69 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}; };
} }
}); });
server.route({
method: "POST",
url: "/invalidate-cache",
config: {
rateLimit: invalidateCacheLimit
},
schema: {
body: z.object({
type: z.nativeEnum(CacheType)
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
await server.services.superAdmin.invalidateCache(req.body.type);
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.InvalidateCache,
distinctId: getTelemetryDistinctId(req),
properties: {
...req.auditLogInfo
}
});
return {
message: "Cache invalidation job started"
};
}
});
server.route({
method: "GET",
url: "/invalidating-cache-status",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
invalidating: z.boolean()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async () => {
const invalidating = await server.services.superAdmin.checkIfInvalidatingCache();
return {
invalidating
};
}
});
}; };
@@ -38,6 +38,7 @@ import {
} from "@app/services/app-connection/humanitec"; } from "@app/services/app-connection/humanitec";
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap"; import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql"; import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { OCIConnectionListItemSchema, SanitizedOCIConnectionSchema } from "@app/services/app-connection/oci";
import { import {
PostgresConnectionListItemSchema, PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema SanitizedPostgresConnectionSchema
@@ -76,7 +77,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedAzureClientSecretsConnectionSchema.options, ...SanitizedAzureClientSecretsConnectionSchema.options,
...SanitizedWindmillConnectionSchema.options, ...SanitizedWindmillConnectionSchema.options,
...SanitizedLdapConnectionSchema.options, ...SanitizedLdapConnectionSchema.options,
...SanitizedTeamCityConnectionSchema.options ...SanitizedTeamCityConnectionSchema.options,
...SanitizedOCIConnectionSchema.options
]); ]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -97,7 +99,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
AzureClientSecretsConnectionListItemSchema, AzureClientSecretsConnectionListItemSchema,
WindmillConnectionListItemSchema, WindmillConnectionListItemSchema,
LdapConnectionListItemSchema, LdapConnectionListItemSchema,
TeamCityConnectionListItemSchema TeamCityConnectionListItemSchema,
OCIConnectionListItemSchema
]); ]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => { export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {

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