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

This commit is contained in:
carlosmonastyrski
2025-06-05 18:31:27 -03:00
757 changed files with 31611 additions and 4112 deletions
+10 -1
View File
@@ -11,7 +11,7 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
return {
errorResponseBuilder: (_, context) => {
throw new RateLimitError({
message: `Rate limit exceeded. Please try again in ${context.after}`
message: `Rate limit exceeded. Please try again in ${Math.ceil(context.ttl / 1000)} seconds`
});
},
timeWindow: 60 * 1000,
@@ -113,3 +113,12 @@ export const requestAccessLimit: RateLimitOptions = {
max: 10,
keyGenerator: (req) => req.realIp
};
export const smtpRateLimit = ({
keyGenerator = (req) => req.realIp
}: Pick<RateLimitOptions, "keyGenerator"> = {}): RateLimitOptions => ({
timeWindow: 40 * 1000,
hook: "preValidation",
max: 2,
keyGenerator
});
@@ -0,0 +1,66 @@
import type { EmitterWebhookEventName } from "@octokit/webhooks/dist-types/types";
import { PushEvent } from "@octokit/webhooks-types";
import { Probot } from "probot";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { writeLimit } from "@app/server/config/rateLimiter";
export const registerSecretScanningV2Webhooks = async (server: FastifyZodProvider) => {
const probotApp = (app: Probot) => {
app.on("installation.deleted", async (context) => {
const { payload } = context;
const { installation } = payload;
await server.services.secretScanningV2.github.handleInstallationDeletedEvent(installation.id);
});
app.on("installation", async (context) => {
const { payload } = context;
logger.info({ repositories: payload.repositories }, "Installed secret scanner to");
});
app.on("push", async (context) => {
const { payload } = context;
await server.services.secretScanningV2.github.handlePushEvent(payload as PushEvent);
});
};
const appCfg = getConfig();
if (!appCfg.isSecretScanningV2Configured) {
logger.info("Secret Scanning V2 is not configured. Skipping registration of secret scanning v2 webhooks.");
return;
}
const probot = new Probot({
appId: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_ID as string,
privateKey: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_PRIVATE_KEY as string,
secret: appCfg.INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET as string
});
await probot.load(probotApp);
// github push event webhook
server.route({
method: "POST",
url: "/github",
config: {
rateLimit: writeLimit
},
handler: async (req, res) => {
const eventName = req.headers["x-github-event"] as EmitterWebhookEventName;
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
const id = req.headers["x-github-delivery"] as string;
await probot.webhooks.verifyAndReceive({
id,
name: eventName,
payload: JSON.stringify(req.body),
signature: signatureSHA256
});
return res.send("ok");
}
});
};
+67 -5
View File
@@ -6,7 +6,10 @@ import { z } from "zod";
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
import { registerV1EERoutes } from "@app/ee/routes/v1";
import { registerV2EERoutes } from "@app/ee/routes/v2";
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
import {
accessApprovalPolicyApproverDALFactory,
accessApprovalPolicyBypasserDALFactory
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
@@ -68,7 +71,10 @@ import { samlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-d
import { samlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { scimDALFactory } from "@app/ee/services/scim/scim-dal";
import { scimServiceFactory } from "@app/ee/services/scim/scim-service";
import { secretApprovalPolicyApproverDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
import {
secretApprovalPolicyApproverDALFactory,
secretApprovalPolicyBypasserDALFactory
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
@@ -87,6 +93,9 @@ import { gitAppInstallSessionDALFactory } from "@app/ee/services/secret-scanning
import { secretScanningDALFactory } from "@app/ee/services/secret-scanning/secret-scanning-dal";
import { secretScanningQueueFactory } from "@app/ee/services/secret-scanning/secret-scanning-queue";
import { secretScanningServiceFactory } from "@app/ee/services/secret-scanning/secret-scanning-service";
import { secretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal";
import { secretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue";
import { secretScanningV2ServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-service";
import { secretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
import { snapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
import { snapshotFolderDALFactory } from "@app/ee/services/secret-snapshot/snapshot-folder-dal";
@@ -113,6 +122,7 @@ import { getConfig, TEnvConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { TQueueServiceFactory } from "@app/queue";
import { readLimit } from "@app/server/config/rateLimiter";
import { registerSecretScanningV2Webhooks } from "@app/server/plugins/secret-scanner-v2";
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
import { apiKeyDALFactory } from "@app/services/api-key/api-key-dal";
import { apiKeyServiceFactory } from "@app/services/api-key/api-key-service";
@@ -214,6 +224,8 @@ import { pkiCollectionServiceFactory } from "@app/services/pki-collection/pki-co
import { pkiSubscriberDALFactory } from "@app/services/pki-subscriber/pki-subscriber-dal";
import { pkiSubscriberQueueServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-queue";
import { pkiSubscriberServiceFactory } from "@app/services/pki-subscriber/pki-subscriber-service";
import { pkiTemplatesDALFactory } from "@app/services/pki-templates/pki-templates-dal";
import { pkiTemplatesServiceFactory } from "@app/services/pki-templates/pki-templates-service";
import { projectDALFactory } from "@app/services/project/project-dal";
import { projectQueueFactory } from "@app/services/project/project-queue";
import { projectServiceFactory } from "@app/services/project/project-service";
@@ -313,6 +325,9 @@ export const registerRoutes = async (
) => {
const appCfg = getConfig();
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
await server.register(registerSecretScanningV2Webhooks, {
prefix: "/secret-scanning/webhooks"
});
// db layers
const userDAL = userDALFactory(db);
@@ -394,9 +409,11 @@ export const registerRoutes = async (
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
@@ -458,6 +475,7 @@ export const registerRoutes = async (
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
const microsoftTeamsIntegrationDAL = microsoftTeamsIntegrationDALFactory(db);
const projectMicrosoftTeamsConfigDAL = projectMicrosoftTeamsConfigDALFactory(db);
const secretScanningV2DAL = secretScanningV2DALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@@ -528,6 +546,7 @@ export const registerRoutes = async (
const secretApprovalPolicyService = secretApprovalPolicyServiceFactory({
projectEnvDAL,
secretApprovalPolicyApproverDAL: sapApproverDAL,
secretApprovalPolicyBypasserDAL: sapBypasserDAL,
permissionService,
secretApprovalPolicyDAL,
licenseService,
@@ -774,12 +793,14 @@ export const registerRoutes = async (
userAliasDAL,
identityTokenAuthDAL,
identityAccessTokenDAL,
orgMembershipDAL,
identityOrgMembershipDAL,
authService: loginService,
serverCfgDAL: superAdminDAL,
kmsRootConfigDAL,
orgService,
keyStore,
orgDAL,
licenseService,
kmsService,
microsoftTeamsService,
@@ -838,7 +859,8 @@ export const registerRoutes = async (
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
permissionService,
projectMembershipDAL,
projectUserAdditionalPrivilegeDAL
projectUserAdditionalPrivilegeDAL,
accessApprovalRequestDAL
});
const projectKeyService = projectKeyServiceFactory({
permissionService,
@@ -882,6 +904,7 @@ export const registerRoutes = async (
const pkiCollectionDAL = pkiCollectionDALFactory(db);
const pkiCollectionItemDAL = pkiCollectionItemDALFactory(db);
const pkiSubscriberDAL = pkiSubscriberDALFactory(db);
const pkiTemplatesDAL = pkiTemplatesDALFactory(db);
const certificateService = certificateServiceFactory({
certificateDAL,
@@ -1268,6 +1291,7 @@ export const registerRoutes = async (
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL,
accessApprovalPolicyBypasserDAL,
groupDAL,
permissionService,
projectEnvDAL,
@@ -1276,7 +1300,8 @@ export const registerRoutes = async (
userDAL,
accessApprovalRequestDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
accessApprovalRequestReviewerDAL
accessApprovalRequestReviewerDAL,
orgMembershipDAL
});
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
@@ -1807,6 +1832,21 @@ export const registerRoutes = async (
internalCaFns
});
const pkiTemplateService = pkiTemplatesServiceFactory({
pkiTemplatesDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL,
certificateDAL,
certificateBodyDAL,
certificateSecretDAL,
projectDAL,
kmsService,
permissionService,
internalCaFns
});
await secretRotationV2QueueServiceFactory({
secretRotationV2Service,
secretRotationV2DAL,
@@ -1816,6 +1856,26 @@ export const registerRoutes = async (
smtpService
});
const secretScanningV2Queue = await secretScanningV2QueueServiceFactory({
auditLogService,
secretScanningV2DAL,
queueService,
projectDAL,
projectMembershipDAL,
smtpService,
kmsService,
keyStore
});
const secretScanningV2Service = secretScanningV2ServiceFactory({
permissionService,
appConnectionService,
licenseService,
secretScanningV2DAL,
secretScanningV2Queue,
kmsService
});
await superAdminService.initServerCfg();
// setup the communication with license key server
@@ -1901,6 +1961,7 @@ export const registerRoutes = async (
pkiAlert: pkiAlertService,
pkiCollection: pkiCollectionService,
pkiSubscriber: pkiSubscriberService,
pkiTemplate: pkiTemplateService,
secretScanning: secretScanningService,
license: licenseService,
trustedIp: trustedIpService,
@@ -1931,7 +1992,8 @@ export const registerRoutes = async (
microsoftTeams: microsoftTeamsService,
assumePrivileges: assumePrivilegeService,
githubOrgSync: githubOrgSyncConfigService,
folderCommit: folderCommitService
folderCommit: folderCommitService,
secretScanningV2: secretScanningV2Service
});
const cronJobs: CronJob[] = [];
@@ -235,11 +235,9 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true,
inputTag: true,
algorithm: true
}).merge(
z.object({
metadata: ResourceMetadataSchema.optional()
})
);
}).extend({
metadata: ResourceMetadataSchema.optional()
});
export const SanitizedAuditLogStreamSchema = z.object({
id: z.string(),
+130 -1
View File
@@ -1,7 +1,13 @@
import DOMPurify from "isomorphic-dompurify";
import { z } from "zod";
import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
import {
IdentitiesSchema,
OrganizationsSchema,
OrgMembershipsSchema,
SuperAdminSchema,
UsersSchema
} from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { invalidateCacheLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -161,6 +167,129 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "GET",
url: "/organization-management/organizations",
config: {
rateLimit: readLimit
},
schema: {
querystring: z.object({
searchTerm: z.string().default(""),
offset: z.coerce.number().default(0),
limit: z.coerce.number().max(100).default(20)
}),
response: {
200: z.object({
organizations: OrganizationsSchema.extend({
members: z
.object({
user: z.object({
id: z.string(),
email: z.string().nullish(),
username: z.string(),
firstName: z.string().nullish(),
lastName: z.string().nullish()
}),
membershipId: z.string(),
role: z.string(),
roleId: z.string().nullish()
})
.array(),
projects: z
.object({
name: z.string(),
id: z.string(),
slug: z.string(),
createdAt: z.date()
})
.array()
}).array()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizations = await server.services.superAdmin.getOrganizations({
...req.query
});
return {
organizations
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId/memberships/:membershipId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string(),
membershipId: z.string()
}),
response: {
200: z.object({
organizationMembership: OrgMembershipsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organizationMembership = await server.services.superAdmin.deleteOrganizationMembership(
req.params.organizationId,
req.params.membershipId,
req.permission.id,
req.permission.type
);
return {
organizationMembership
};
}
});
server.route({
method: "DELETE",
url: "/organization-management/organizations/:organizationId",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
organizationId: z.string()
}),
response: {
200: z.object({
organization: OrganizationsSchema
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const organization = await server.services.superAdmin.deleteOrganization(req.params.organizationId);
return {
organization
};
}
});
server.route({
method: "GET",
url: "/identity-management/identities",
@@ -33,6 +33,10 @@ import {
} from "@app/services/app-connection/databricks";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import {
GitHubRadarConnectionListItemSchema,
SanitizedGitHubRadarConnectionSchema
} from "@app/services/app-connection/github-radar";
import {
HCVaultConnectionListItemSchema,
SanitizedHCVaultConnectionSchema
@@ -43,6 +47,7 @@ import {
} from "@app/services/app-connection/humanitec";
import { LdapConnectionListItemSchema, SanitizedLdapConnectionSchema } from "@app/services/app-connection/ldap";
import { MsSqlConnectionListItemSchema, SanitizedMsSqlConnectionSchema } from "@app/services/app-connection/mssql";
import { MySqlConnectionListItemSchema, SanitizedMySqlConnectionSchema } from "@app/services/app-connection/mysql";
import {
PostgresConnectionListItemSchema,
SanitizedPostgresConnectionSchema
@@ -66,6 +71,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
const SanitizedAppConnectionSchema = z.union([
...SanitizedAwsConnectionSchema.options,
...SanitizedGitHubConnectionSchema.options,
...SanitizedGitHubRadarConnectionSchema.options,
...SanitizedGcpConnectionSchema.options,
...SanitizedAzureKeyVaultConnectionSchema.options,
...SanitizedAzureAppConfigurationConnectionSchema.options,
@@ -75,6 +81,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedVercelConnectionSchema.options,
...SanitizedPostgresConnectionSchema.options,
...SanitizedMsSqlConnectionSchema.options,
...SanitizedMySqlConnectionSchema.options,
...SanitizedCamundaConnectionSchema.options,
...SanitizedAuth0ConnectionSchema.options,
...SanitizedHCVaultConnectionSchema.options,
@@ -89,6 +96,7 @@ const SanitizedAppConnectionSchema = z.union([
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
AwsConnectionListItemSchema,
GitHubConnectionListItemSchema,
GitHubRadarConnectionListItemSchema,
GcpConnectionListItemSchema,
AzureKeyVaultConnectionListItemSchema,
AzureAppConfigurationConnectionListItemSchema,
@@ -98,6 +106,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
VercelConnectionListItemSchema,
PostgresConnectionListItemSchema,
MsSqlConnectionListItemSchema,
MySqlConnectionListItemSchema,
CamundaConnectionListItemSchema,
Auth0ConnectionListItemSchema,
HCVaultConnectionListItemSchema,
@@ -0,0 +1,54 @@
import { z } from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateGitHubRadarConnectionSchema,
SanitizedGitHubRadarConnectionSchema,
UpdateGitHubRadarConnectionSchema
} from "@app/services/app-connection/github-radar";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerGitHubRadarConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.GitHubRadar,
server,
sanitizedResponseSchema: SanitizedGitHubRadarConnectionSchema,
createSchema: CreateGitHubRadarConnectionSchema,
updateSchema: UpdateGitHubRadarConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/repositories`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
repositories: z.object({ id: z.number(), name: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const repositories = await server.services.appConnection.githubRadar.listRepositories(
connectionId,
req.permission
);
return { repositories };
}
});
};
@@ -11,10 +11,12 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";
import { registerGitHubRadarConnectionRouter } from "./github-radar-connection-router";
import { registerHCVaultConnectionRouter } from "./hc-vault-connection-router";
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
import { registerLdapConnectionRouter } from "./ldap-connection-router";
import { registerMsSqlConnectionRouter } from "./mssql-connection-router";
import { registerMySqlConnectionRouter } from "./mysql-connection-router";
import { registerPostgresConnectionRouter } from "./postgres-connection-router";
import { registerTeamCityConnectionRouter } from "./teamcity-connection-router";
import { registerTerraformCloudConnectionRouter } from "./terraform-cloud-router";
@@ -27,6 +29,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
{
[AppConnection.AWS]: registerAwsConnectionRouter,
[AppConnection.GitHub]: registerGitHubConnectionRouter,
[AppConnection.GitHubRadar]: registerGitHubRadarConnectionRouter,
[AppConnection.GCP]: registerGcpConnectionRouter,
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
@@ -37,6 +40,7 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Vercel]: registerVercelConnectionRouter,
[AppConnection.Postgres]: registerPostgresConnectionRouter,
[AppConnection.MsSql]: registerMsSqlConnectionRouter,
[AppConnection.MySql]: registerMySqlConnectionRouter,
[AppConnection.Camunda]: registerCamundaConnectionRouter,
[AppConnection.Windmill]: registerWindmillConnectionRouter,
[AppConnection.Auth0]: registerAuth0ConnectionRouter,
@@ -0,0 +1,18 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateMySqlConnectionSchema,
SanitizedMySqlConnectionSchema,
UpdateMySqlConnectionSchema
} from "@app/services/app-connection/mysql";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerMySqlConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.MySql,
server,
sanitizedResponseSchema: SanitizedMySqlConnectionSchema,
createSchema: CreateMySqlConnectionSchema,
updateSchema: UpdateMySqlConnectionSchema
});
};
@@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
@@ -72,7 +73,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
body: z.object({
caId: z.string().describe(CERTIFICATE_TEMPLATES.CREATE.caId),
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.CREATE.pkiCollectionId),
name: z.string().min(1).describe(CERTIFICATE_TEMPLATES.CREATE.name),
name: slugSchema().describe(CERTIFICATE_TEMPLATES.CREATE.name),
commonName: validateTemplateRegexField.describe(CERTIFICATE_TEMPLATES.CREATE.commonName),
subjectAlternativeName: validateTemplateRegexField.describe(
CERTIFICATE_TEMPLATES.CREATE.subjectAlternativeName
@@ -141,7 +142,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
body: z.object({
caId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.caId),
pkiCollectionId: z.string().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.pkiCollectionId),
name: z.string().min(1).optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
name: slugSchema().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.name),
commonName: validateTemplateRegexField.optional().describe(CERTIFICATE_TEMPLATES.UPDATE.commonName),
subjectAlternativeName: validateTemplateRegexField
.optional()
@@ -8,6 +8,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { IdentityKubernetesAuthTokenReviewMode } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-types";
import { isSuperAdmin } from "@app/services/super-admin/super-admin-fns";
const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick({
@@ -18,6 +19,7 @@ const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.pick(
accessTokenTrustedIps: true,
createdAt: true,
updatedAt: true,
tokenReviewMode: true,
identityId: true,
kubernetesHost: true,
allowedNamespaces: true,
@@ -124,6 +126,10 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
),
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
tokenReviewerJwt: z.string().trim().optional().describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
tokenReviewMode: z
.nativeEnum(IdentityKubernetesAuthTokenReviewMode)
.default(IdentityKubernetesAuthTokenReviewMode.Api)
.describe(KUBERNETES_AUTH.ATTACH.tokenReviewMode),
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
@@ -157,10 +163,22 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.default(0)
.describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit)
})
.refine(
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
"Access Token TTL cannot be greater than Access Token Max TTL."
),
.superRefine((data, ctx) => {
if (data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway && !data.gatewayId) {
ctx.addIssue({
path: ["gatewayId"],
code: z.ZodIssueCode.custom,
message: "When token review mode is set to Gateway, a gateway must be selected"
});
}
if (data.accessTokenTTL > data.accessTokenMaxTTL) {
ctx.addIssue({
path: ["accessTokenTTL"],
code: z.ZodIssueCode.custom,
message: "Access Token TTL cannot be greater than Access Token Max TTL."
});
}
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
@@ -247,6 +265,10 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
),
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
tokenReviewerJwt: z.string().trim().nullable().optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
tokenReviewMode: z
.nativeEnum(IdentityKubernetesAuthTokenReviewMode)
.optional()
.describe(KUBERNETES_AUTH.UPDATE.tokenReviewMode),
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
@@ -280,10 +302,26 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
.optional()
.describe(KUBERNETES_AUTH.UPDATE.accessTokenMaxTTL)
})
.refine(
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
"Access Token TTL cannot be greater than Access Token Max TTL."
),
.superRefine((data, ctx) => {
if (
data.tokenReviewMode &&
data.tokenReviewMode === IdentityKubernetesAuthTokenReviewMode.Gateway &&
!data.gatewayId
) {
ctx.addIssue({
path: ["gatewayId"],
code: z.ZodIssueCode.custom,
message: "When token review mode is set to Gateway, a gateway must be selected"
});
}
if (data.accessTokenMaxTTL && data.accessTokenTTL ? data.accessTokenTTL > data.accessTokenMaxTTL : false) {
ctx.addIssue({
path: ["accessTokenTTL"],
code: z.ZodIssueCode.custom,
message: "Access Token TTL cannot be greater than Access Token Max TTL."
});
}
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
@@ -1,7 +1,7 @@
import { z } from "zod";
import { OrgMembershipRole, ProjectMembershipRole, UsersSchema } from "@app/db/schemas";
import { inviteUserRateLimit } from "@app/server/config/rateLimiter";
import { inviteUserRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
@@ -11,7 +11,7 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/signup",
config: {
rateLimit: inviteUserRateLimit
rateLimit: smtpRateLimit()
},
method: "POST",
schema: {
@@ -81,7 +81,10 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/signup-resend",
config: {
rateLimit: inviteUserRateLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) =>
(req.body as { membershipId?: string })?.membershipId?.trim().substring(0, 100) ?? req.realIp
})
},
method: "POST",
schema: {
@@ -2,9 +2,9 @@ import { z } from "zod";
import { ProjectMembershipsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { readLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { SanitizedProjectSchema } from "../sanitizedSchemas";
@@ -47,7 +47,9 @@ export const registerOrgAdminRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/projects/:projectId/grant-admin-access",
config: {
rateLimit: writeLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) => (req.auth.actor === ActorType.USER ? req.auth.userId : req.realIp)
})
},
schema: {
params: z.object({
@@ -2,10 +2,10 @@ import { z } from "zod";
import { BackupPrivateKeySchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { authRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
import { AuthMode } from "@app/services/auth/auth-type";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { UserEncryption } from "@app/services/user/user-types";
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
@@ -80,7 +80,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/email/password-reset",
config: {
rateLimit: authRateLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) => (req.body as { email?: string })?.email?.trim().substring(0, 100) ?? req.realIp
})
},
schema: {
body: z.object({
@@ -224,7 +226,9 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/email/password-setup",
config: {
rateLimit: authRateLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) => (req.auth.actor === ActorType.USER ? req.auth.userId : req.realIp)
})
},
schema: {
response: {
@@ -233,6 +237,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.password.sendPasswordSetupEmail(req.permission);
@@ -267,6 +272,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req, res) => {
await server.services.password.setupPassword(req.body, req.permission);
@@ -160,7 +160,14 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
.default("false")
.transform((value) => value === "true"),
type: z
.enum([ProjectType.SecretManager, ProjectType.KMS, ProjectType.CertificateManager, ProjectType.SSH, "all"])
.enum([
ProjectType.SecretManager,
ProjectType.KMS,
ProjectType.CertificateManager,
ProjectType.SSH,
ProjectType.SecretScanning,
"all"
])
.optional()
}),
response: {
@@ -173,7 +180,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY]),
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const workspaces = await server.services.project.getProjects({
includeRoles: req.query.includeRoles,
+10 -1
View File
@@ -5,6 +5,7 @@ import { registerIdentityProjectRouter } from "./identity-project-router";
import { registerMfaRouter } from "./mfa-router";
import { registerOrgRouter } from "./organization-router";
import { registerPasswordRouter } from "./password-router";
import { registerPkiTemplatesRouter } from "./pki-templates-router";
import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router";
import { registerServiceTokenRouter } from "./service-token-router";
@@ -15,7 +16,15 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerCaRouter, { prefix: "/pki/ca" });
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
await pkiRouter.register(registerPkiTemplatesRouter, { prefix: "/certificate-templates" });
},
{ prefix: "/pki" }
);
await server.register(
async (orgRouter) => {
await orgRouter.register(registerOrgRouter);
@@ -0,0 +1,309 @@
import { z } from "zod";
import { CertificateTemplatesSchema } from "@app/db/schemas";
import { ApiDocsTags } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { slugSchema } from "@app/server/lib/schemas";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
export const registerPkiTemplatesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
body: z.object({
name: slugSchema(),
caName: slugSchema({ field: "caName" }),
projectId: z.string(),
commonName: validateTemplateRegexField,
subjectAlternativeName: validateTemplateRegexField,
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z
.nativeEnum(CertKeyUsage)
.array()
.optional()
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.createTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
return { certificateTemplate };
}
});
server.route({
method: "PATCH",
url: "/:templateName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
name: slugSchema().optional(),
caName: slugSchema(),
projectId: z.string(),
commonName: validateTemplateRegexField.optional(),
subjectAlternativeName: validateTemplateRegexField.optional(),
ttl: z
.string()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.optional(),
keyUsages: z
.nativeEnum(CertKeyUsage)
.array()
.optional()
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.updateTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return { certificateTemplate };
}
});
server.route({
method: "DELETE",
url: "/:templateName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: z.string().min(1)
}),
body: z.object({
projectId: z.string()
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.deleteTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
projectId: req.body.projectId
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/:templateName",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
querystring: z.object({
projectId: z.string()
}),
response: {
200: z.object({
certificateTemplate: CertificateTemplatesSchema.extend({
ca: z.object({ id: z.string(), name: z.string() })
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.pkiTemplate.getTemplateByName({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
projectId: req.query.projectId
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
querystring: z.object({
projectId: z.string(),
limit: z.coerce.number().default(100),
offset: z.coerce.number().default(0)
}),
response: {
200: z.object({
certificateTemplates: CertificateTemplatesSchema.extend({
ca: z.object({ id: z.string(), name: z.string() })
}).array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { certificateTemplates, totalCount } = await server.services.pkiTemplate.listTemplate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
return { certificateTemplates, totalCount };
}
});
server.route({
method: "POST",
url: "/:templateName/issue-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
projectId: z.string(),
commonName: validateTemplateRegexField,
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
altNames: validateAltNamesField
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
privateKey: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.pkiTemplate.issueCertificate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return data;
}
});
server.route({
method: "POST",
url: "/:templateName/sign-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: z.object({
templateName: slugSchema()
}),
body: z.object({
projectId: z.string(),
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
csr: z.string().trim().min(1).max(4096)
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
serialNumber: z.string().trim()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.pkiTemplate.signCertificate({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
templateName: req.params.templateName,
...req.body
});
return data;
}
});
};
+4 -2
View File
@@ -2,7 +2,7 @@ import { z } from "zod";
import { AuthTokenSessionsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { authRateLimit, readLimit, smtpRateLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMethod, AuthMode, MfaMethod } from "@app/services/auth/auth-type";
import { sanitizedOrganizationSchema } from "@app/services/org/org-schema";
@@ -12,7 +12,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
method: "POST",
url: "/me/emails/code",
config: {
rateLimit: authRateLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) => (req.body as { username?: string })?.username?.trim().substring(0, 100) ?? req.realIp
})
},
schema: {
body: z.object({
@@ -3,7 +3,7 @@ import { z } from "zod";
import { UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { ForbiddenRequestError } from "@app/lib/errors";
import { authRateLimit } from "@app/server/config/rateLimiter";
import { authRateLimit, smtpRateLimit } from "@app/server/config/rateLimiter";
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@@ -13,7 +13,9 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
url: "/email/signup",
method: "POST",
config: {
rateLimit: authRateLimit
rateLimit: smtpRateLimit({
keyGenerator: (req) => (req.body as { email?: string })?.email?.trim().substring(0, 100) ?? req.realIp
})
},
schema: {
body: z.object({