mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 22:46:24 +00:00
Merge branch 'main' into feat/remindersImprovement
This commit is contained in:
@@ -3,11 +3,14 @@ import { z } from "zod";
|
||||
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { isValidFolderName } from "@app/lib/validator";
|
||||
import { readLimit, secretsLimit } from "@app/server/config/rateLimiter";
|
||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { booleanSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { commitChangesResponseSchema, resourceChangeSchema } from "@app/services/folder-commit/folder-commit-schemas";
|
||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||
|
||||
const commitHistoryItemSchema = z.object({
|
||||
id: z.string(),
|
||||
@@ -413,4 +416,166 @@ export const registerPITRouter = async (server: FastifyZodProvider) => {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/batch/commit",
|
||||
config: {
|
||||
rateLimit: secretsLimit
|
||||
},
|
||||
schema: {
|
||||
hide: true,
|
||||
description: "Commit changes",
|
||||
security: [
|
||||
{
|
||||
bearerAuth: []
|
||||
}
|
||||
],
|
||||
body: z.object({
|
||||
projectId: z.string().trim(),
|
||||
environment: z.string().trim(),
|
||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||
message: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(255)
|
||||
.refine((message) => message.trim() !== "", {
|
||||
message: "Commit message cannot be empty"
|
||||
}),
|
||||
changes: z.object({
|
||||
secrets: z.object({
|
||||
create: z
|
||||
.array(
|
||||
z.object({
|
||||
secretKey: SecretNameSchema,
|
||||
secretValue: z.string().transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())),
|
||||
secretComment: z.string().trim().optional().default(""),
|
||||
skipMultilineEncoding: z.boolean().optional(),
|
||||
metadata: z.record(z.string()).optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tagIds: z.string().array().optional()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
update: z
|
||||
.array(
|
||||
z.object({
|
||||
secretKey: SecretNameSchema,
|
||||
newSecretName: SecretNameSchema.optional(),
|
||||
secretValue: z
|
||||
.string()
|
||||
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
|
||||
.optional(),
|
||||
secretComment: z.string().trim().optional().default(""),
|
||||
skipMultilineEncoding: z.boolean().optional(),
|
||||
metadata: z.record(z.string()).optional(),
|
||||
secretMetadata: ResourceMetadataSchema.optional(),
|
||||
tagIds: z.string().array().optional()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
delete: z
|
||||
.array(
|
||||
z.object({
|
||||
secretKey: SecretNameSchema
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
}),
|
||||
folders: z.object({
|
||||
create: z
|
||||
.array(
|
||||
z.object({
|
||||
folderName: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((name) => isValidFolderName(name), {
|
||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
||||
}),
|
||||
description: z.string().optional()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
update: z
|
||||
.array(
|
||||
z.object({
|
||||
folderName: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((name) => isValidFolderName(name), {
|
||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
||||
}),
|
||||
description: z.string().nullable().optional(),
|
||||
id: z.string()
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
delete: z
|
||||
.array(
|
||||
z.object({
|
||||
folderName: z
|
||||
.string()
|
||||
.trim()
|
||||
.refine((name) => isValidFolderName(name), {
|
||||
message: "Invalid folder name. Only alphanumeric characters, dashes, and underscores are allowed."
|
||||
}),
|
||||
id: z.string()
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
})
|
||||
})
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const result = await server.services.pit.processNewCommitRaw({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorOrgId: req.permission.orgId,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
projectId: req.body.projectId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
message: req.body.message,
|
||||
changes: {
|
||||
secrets: req.body.changes.secrets,
|
||||
folders: req.body.changes.folders
|
||||
}
|
||||
});
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: req.body.projectId,
|
||||
event: {
|
||||
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW,
|
||||
metadata: {
|
||||
commitId: result.commitId,
|
||||
approvalId: result.approvalId,
|
||||
projectId: req.body.projectId,
|
||||
environment: req.body.environment,
|
||||
secretPath: req.body.secretPath,
|
||||
message: req.body.message
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for await (const event of result.secretMutationEvents) {
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId: req.body.projectId,
|
||||
event
|
||||
});
|
||||
}
|
||||
|
||||
return { message: "success" };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.ProjectTemplates],
|
||||
description: "List project templates for the current organization.",
|
||||
querystring: z.object({
|
||||
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
projectTemplates: SanitizedProjectTemplateSchema.array()
|
||||
@@ -112,7 +115,10 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission);
|
||||
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(
|
||||
req.permission,
|
||||
req.query.type
|
||||
);
|
||||
|
||||
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
|
||||
|
||||
@@ -191,6 +197,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
|
||||
.describe(ProjectTemplates.CREATE.name),
|
||||
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
|
||||
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
|
||||
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
|
||||
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
|
||||
}),
|
||||
response: {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-r
|
||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
||||
import { registerOktaClientSecretRotationRouter } from "./okta-client-secret-rotation-router";
|
||||
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
|
||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||
|
||||
@@ -22,5 +23,6 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter
|
||||
[SecretRotation.LdapPassword]: registerLdapPasswordRotationRouter,
|
||||
[SecretRotation.OktaClientSecret]: registerOktaClientSecretRotationRouter
|
||||
};
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import {
|
||||
CreateOktaClientSecretRotationSchema,
|
||||
OktaClientSecretRotationGeneratedCredentialsSchema,
|
||||
OktaClientSecretRotationSchema,
|
||||
UpdateOktaClientSecretRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
|
||||
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||
|
||||
export const registerOktaClientSecretRotationRouter = async (server: FastifyZodProvider) =>
|
||||
registerSecretRotationEndpoints({
|
||||
type: SecretRotation.OktaClientSecret,
|
||||
server,
|
||||
responseSchema: OktaClientSecretRotationSchema,
|
||||
createSchema: CreateOktaClientSecretRotationSchema,
|
||||
updateSchema: UpdateOktaClientSecretRotationSchema,
|
||||
generatedCredentialsSchema: OktaClientSecretRotationGeneratedCredentialsSchema
|
||||
});
|
||||
@@ -7,6 +7,7 @@ import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret
|
||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { OktaClientSecretRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
||||
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||
@@ -23,7 +24,8 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||
Auth0ClientSecretRotationListItemSchema,
|
||||
AzureClientSecretRotationListItemSchema,
|
||||
AwsIamUserSecretRotationListItemSchema,
|
||||
LdapPasswordRotationListItemSchema
|
||||
LdapPasswordRotationListItemSchema,
|
||||
OktaClientSecretRotationListItemSchema
|
||||
]);
|
||||
|
||||
export const registerSecretRotationV2Router = async (server: FastifyZodProvider) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -116,7 +117,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -272,7 +274,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||
@@ -337,7 +340,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: accessApprovalPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
@@ -533,7 +537,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: policy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
@@ -583,7 +588,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@@ -622,7 +628,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: policy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import msFn from "ms";
|
||||
|
||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@@ -107,7 +107,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@@ -216,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
);
|
||||
|
||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||
const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`;
|
||||
const approvalUrl = `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`;
|
||||
|
||||
await triggerWorkflowIntegrationNotification({
|
||||
input: {
|
||||
@@ -289,7 +290,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
@@ -335,7 +337,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: accessApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
if (!membership) {
|
||||
@@ -551,7 +554,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
bypassReason: bypassReason || "No reason provided",
|
||||
secretPath: policy.secretPath || "/",
|
||||
environment,
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`,
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`,
|
||||
requestType: "access"
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
@@ -582,7 +585,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (!membership) {
|
||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||
|
||||
@@ -45,7 +45,10 @@ export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion(
|
||||
]);
|
||||
|
||||
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true })
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, {
|
||||
supportsPlatformManagedCredentials: true,
|
||||
supportsGateways: true
|
||||
})
|
||||
);
|
||||
|
||||
export const UpdateOracleDBConnectionSchema = z
|
||||
@@ -54,7 +57,12 @@ export const UpdateOracleDBConnectionSchema = z
|
||||
AppConnections.UPDATE(AppConnection.OracleDB).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true }));
|
||||
.and(
|
||||
GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, {
|
||||
supportsPlatformManagedCredentials: true,
|
||||
supportsGateways: true
|
||||
})
|
||||
);
|
||||
|
||||
export const OracleDBConnectionListItemSchema = z.object({
|
||||
name: z.literal("OracleDB"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -37,7 +38,8 @@ export const assumePrivilegeServiceFactory = ({
|
||||
actorId: actorPermissionDetails.id,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
if (targetActorType === ActorType.USER) {
|
||||
@@ -58,7 +60,8 @@ export const assumePrivilegeServiceFactory = ({
|
||||
actorId: targetActorId,
|
||||
projectId,
|
||||
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||
actorOrgId: actorPermissionDetails.orgId
|
||||
actorOrgId: actorPermissionDetails.orgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
const appCfg = getConfig();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { requestContext } from "@fastify/request-context";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
@@ -37,7 +38,8 @@ export const auditLogServiceFactory = ({
|
||||
actorId,
|
||||
projectId: filter.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||
} else {
|
||||
|
||||
@@ -449,6 +449,7 @@ export enum EventType {
|
||||
PIT_REVERT_COMMIT = "pit-revert-commit",
|
||||
PIT_GET_FOLDER_STATE = "pit-get-folder-state",
|
||||
PIT_COMPARE_FOLDER_STATES = "pit-compare-folder-states",
|
||||
PIT_PROCESS_NEW_COMMIT_RAW = "pit-process-new-commit-raw",
|
||||
SECRET_SCANNING_DATA_SOURCE_LIST = "secret-scanning-data-source-list",
|
||||
SECRET_SCANNING_DATA_SOURCE_CREATE = "secret-scanning-data-source-create",
|
||||
SECRET_SCANNING_DATA_SOURCE_UPDATE = "secret-scanning-data-source-update",
|
||||
@@ -1550,8 +1551,9 @@ interface UpdateFolderEvent {
|
||||
metadata: {
|
||||
environment: string;
|
||||
folderId: string;
|
||||
oldFolderName: string;
|
||||
oldFolderName?: string;
|
||||
newFolderName: string;
|
||||
newFolderDescription?: string;
|
||||
folderPath: string;
|
||||
};
|
||||
}
|
||||
@@ -3226,6 +3228,18 @@ interface PitCompareFolderStatesEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface PitProcessNewCommitRawEvent {
|
||||
type: EventType.PIT_PROCESS_NEW_COMMIT_RAW;
|
||||
metadata: {
|
||||
projectId: string;
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
message: string;
|
||||
approvalId?: string;
|
||||
commitId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface SecretScanningDataSourceListEvent {
|
||||
type: EventType.SECRET_SCANNING_DATA_SOURCE_LIST;
|
||||
metadata: {
|
||||
@@ -3687,6 +3701,7 @@ export type Event =
|
||||
| PitRevertCommitEvent
|
||||
| PitCompareFolderStatesEvent
|
||||
| PitGetFolderStateEvent
|
||||
| PitProcessNewCommitRawEvent
|
||||
| SecretScanningDataSourceListEvent
|
||||
| SecretScanningDataSourceGetEvent
|
||||
| SecretScanningDataSourceCreateEvent
|
||||
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
@@ -77,7 +78,8 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.CertificateManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@@ -84,7 +85,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@@ -200,7 +202,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
@@ -297,7 +300,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||
@@ -385,7 +389,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@@ -432,7 +437,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@@ -78,7 +79,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -207,7 +209,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@@ -358,7 +361,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@@ -423,7 +427,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@@ -487,7 +492,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
// verify user has access to each env in request
|
||||
@@ -530,7 +536,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||
@@ -578,7 +585,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||
@@ -615,7 +623,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||
@@ -659,7 +668,8 @@ export const dynamicSecretServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||
|
||||
+19
-10
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { packRules } from "@casl/ability/extra";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@@ -61,7 +61,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -158,7 +160,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@@ -169,7 +172,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -256,7 +260,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@@ -267,7 +272,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
@@ -315,7 +321,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@@ -349,7 +356,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@@ -384,7 +392,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
|
||||
+17
-8
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -85,7 +87,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -172,7 +175,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -185,7 +189,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -288,7 +293,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Edit,
|
||||
@@ -300,7 +306,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: identityProjectMembership.identityId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
const permissionBoundary = validatePrivilegeChangeOperation(
|
||||
membership.shouldUseNewPrivilegeSystem,
|
||||
@@ -359,7 +366,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionIdentityActions.Read,
|
||||
@@ -401,7 +409,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: identityProjectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { isValidIp } from "@app/lib/ip";
|
||||
@@ -78,7 +79,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -131,7 +133,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -162,7 +165,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -195,7 +199,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
@@ -216,7 +221,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
||||
@@ -252,7 +258,8 @@ export const kmipServiceFactory = ({
|
||||
actorId,
|
||||
projectId: kmipClient.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { MongoQuery } from "@ucast/mongo2js";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { OrgPermissionSet } from "./org-permission";
|
||||
@@ -20,6 +21,7 @@ export type TGetUserProjectPermissionArg = {
|
||||
userId: string;
|
||||
projectId: string;
|
||||
authMethod: ActorAuthMethod;
|
||||
actionProjectType: ActionProjectType;
|
||||
userOrgId?: string;
|
||||
};
|
||||
|
||||
@@ -27,12 +29,14 @@ export type TGetIdentityProjectPermissionArg = {
|
||||
identityId: string;
|
||||
projectId: string;
|
||||
identityOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TGetServiceTokenProjectPermissionArg = {
|
||||
serviceTokenId: string;
|
||||
projectId: string;
|
||||
actorOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TGetProjectPermissionArg = {
|
||||
@@ -41,6 +45,7 @@ export type TGetProjectPermissionArg = {
|
||||
projectId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
actorOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TPermissionServiceFactory = {
|
||||
@@ -138,7 +143,13 @@ export type TPermissionServiceFactory = {
|
||||
};
|
||||
}
|
||||
>;
|
||||
getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{
|
||||
getUserProjectPermission: ({
|
||||
userId,
|
||||
projectId,
|
||||
authMethod,
|
||||
userOrgId,
|
||||
actionProjectType
|
||||
}: TGetUserProjectPermissionArg) => Promise<{
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
id: string;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MongoQuery } from "@ucast/mongo2js";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
import {
|
||||
ActionProjectType,
|
||||
OrgMembershipRole,
|
||||
ProjectMembershipRole,
|
||||
ServiceTokenScopes,
|
||||
@@ -213,7 +214,8 @@ export const permissionServiceFactory = ({
|
||||
userId,
|
||||
projectId,
|
||||
authMethod,
|
||||
userOrgId
|
||||
userOrgId,
|
||||
actionProjectType
|
||||
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||
@@ -240,6 +242,12 @@ export const permissionServiceFactory = ({
|
||||
userProjectPermission.orgRole
|
||||
);
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
// join two permissions and pass to build the final permission set
|
||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
@@ -287,7 +295,8 @@ export const permissionServiceFactory = ({
|
||||
const getIdentityProjectPermission = async ({
|
||||
identityId,
|
||||
projectId,
|
||||
identityOrgId
|
||||
identityOrgId,
|
||||
actionProjectType
|
||||
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||
if (!identityProjectPermission)
|
||||
@@ -307,6 +316,12 @@ export const permissionServiceFactory = ({
|
||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||
}
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
const rolePermissions =
|
||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||
const additionalPrivileges =
|
||||
@@ -361,7 +376,8 @@ export const permissionServiceFactory = ({
|
||||
const getServiceTokenProjectPermission = async ({
|
||||
serviceTokenId,
|
||||
projectId,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}: TGetServiceTokenProjectPermissionArg) => {
|
||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||
@@ -386,6 +402,12 @@ export const permissionServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
|
||||
throw new BadRequestError({
|
||||
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
|
||||
});
|
||||
}
|
||||
|
||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||
return {
|
||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||
@@ -537,7 +559,8 @@ export const permissionServiceFactory = ({
|
||||
actorId: inputActorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||
let actor = inputActor;
|
||||
let actorId = inputActorId;
|
||||
@@ -558,19 +581,22 @@ export const permissionServiceFactory = ({
|
||||
userId: actorId,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.SERVICE:
|
||||
return getServiceTokenProjectPermission({
|
||||
serviceTokenId: actorId,
|
||||
projectId,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
case ActorType.IDENTITY:
|
||||
return getIdentityProjectPermission({
|
||||
identityId: actorId,
|
||||
projectId,
|
||||
identityOrgId: actorOrgId
|
||||
identityOrgId: actorOrgId,
|
||||
actionProjectType
|
||||
}) as Promise<TProjectPermissionRT<T>>;
|
||||
default:
|
||||
throw new BadRequestError({
|
||||
|
||||
@@ -1,29 +1,53 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { ResourceType, TFolderCommitServiceFactory } from "@app/services/folder-commit/folder-commit-service";
|
||||
import { TFolderCommitDALFactory } from "@app/services/folder-commit/folder-commit-dal";
|
||||
import {
|
||||
ResourceType,
|
||||
TCommitResourceChangeDTO,
|
||||
TFolderCommitServiceFactory
|
||||
} from "@app/services/folder-commit/folder-commit-service";
|
||||
import {
|
||||
isFolderCommitChange,
|
||||
isSecretCommitChange
|
||||
} from "@app/services/folder-commit-changes/folder-commit-changes-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||
import { TProcessNewCommitRawDTO } from "@app/services/secret/secret-types";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
||||
import { TSecretV2BridgeServiceFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-service";
|
||||
import { SecretOperations, SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "../secret-approval-policy/secret-approval-policy-service";
|
||||
import { TSecretApprovalRequestServiceFactory } from "../secret-approval-request/secret-approval-request-service";
|
||||
|
||||
type TPitServiceFactoryDep = {
|
||||
folderCommitService: TFolderCommitServiceFactory;
|
||||
secretService: Pick<TSecretServiceFactory, "getSecretVersionsV2ByIds" | "getChangeVersions">;
|
||||
folderService: Pick<TSecretFolderServiceFactory, "getFolderById" | "getFolderVersions">;
|
||||
folderService: Pick<
|
||||
TSecretFolderServiceFactory,
|
||||
"getFolderById" | "getFolderVersions" | "createManyFolders" | "updateManyFolders" | "deleteManyFolders"
|
||||
>;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds">;
|
||||
folderDAL: Pick<TSecretFolderDALFactory, "findSecretPathByFolderIds" | "findBySecretPath">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
secretApprovalRequestService: Pick<
|
||||
TSecretApprovalRequestServiceFactory,
|
||||
"generateSecretApprovalRequest" | "generateSecretApprovalRequestV2Bridge"
|
||||
>;
|
||||
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectBySlug" | "findById">;
|
||||
secretV2BridgeService: TSecretV2BridgeServiceFactory;
|
||||
folderCommitDAL: Pick<TFolderCommitDALFactory, "transaction">;
|
||||
};
|
||||
|
||||
export type TPitServiceFactory = ReturnType<typeof pitServiceFactory>;
|
||||
@@ -34,7 +58,12 @@ export const pitServiceFactory = ({
|
||||
folderService,
|
||||
permissionService,
|
||||
folderDAL,
|
||||
projectEnvDAL
|
||||
projectEnvDAL,
|
||||
secretApprovalRequestService,
|
||||
secretApprovalPolicyService,
|
||||
projectDAL,
|
||||
secretV2BridgeService,
|
||||
folderCommitDAL
|
||||
}: TPitServiceFactoryDep) => {
|
||||
const getCommitsCount = async ({
|
||||
actor,
|
||||
@@ -320,7 +349,8 @@ export const pitServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(userPermission).throwUnlessCan(
|
||||
@@ -471,6 +501,347 @@ export const pitServiceFactory = ({
|
||||
});
|
||||
};
|
||||
|
||||
const processNewCommitRaw = async ({
|
||||
actorId,
|
||||
projectId,
|
||||
environment,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
secretPath,
|
||||
message,
|
||||
changes = {
|
||||
secrets: {
|
||||
create: [],
|
||||
update: [],
|
||||
delete: []
|
||||
},
|
||||
folders: {
|
||||
create: [],
|
||||
update: [],
|
||||
delete: []
|
||||
}
|
||||
}
|
||||
}: {
|
||||
actorId: string;
|
||||
projectId: string;
|
||||
environment: string;
|
||||
actor: ActorType;
|
||||
actorOrgId: string;
|
||||
actorAuthMethod: ActorAuthMethod;
|
||||
secretPath: string;
|
||||
message: string;
|
||||
changes: TProcessNewCommitRawDTO;
|
||||
}) => {
|
||||
const policy =
|
||||
actor === ActorType.USER
|
||||
? await secretApprovalPolicyService.getSecretApprovalPolicy(projectId, environment, secretPath)
|
||||
: undefined;
|
||||
const secretMutationEvents: Event[] = [];
|
||||
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (project.enforceCapitalization) {
|
||||
const caseViolatingSecretKeys = [
|
||||
// Check create operations
|
||||
...(changes.secrets?.create
|
||||
?.filter((sec) => sec.secretKey !== sec.secretKey.toUpperCase())
|
||||
.map((sec) => sec.secretKey) ?? []),
|
||||
|
||||
// Check update operations
|
||||
...(changes.secrets?.update
|
||||
?.filter((sec) => sec.newSecretName && sec.newSecretName !== sec.newSecretName.toUpperCase())
|
||||
.map((sec) => sec.secretKey) ?? [])
|
||||
];
|
||||
|
||||
if (caseViolatingSecretKeys.length) {
|
||||
throw new BadRequestError({
|
||||
message: `Secret names must be in UPPERCASE per project requirements: ${caseViolatingSecretKeys.join(
|
||||
", "
|
||||
)}. You can disable this requirement in project settings`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const response = await folderCommitDAL.transaction(async (trx) => {
|
||||
const targetFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath, trx);
|
||||
if (!targetFolder)
|
||||
throw new NotFoundError({
|
||||
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`,
|
||||
name: "CreateManySecret"
|
||||
});
|
||||
const commitChanges: TCommitResourceChangeDTO[] = [];
|
||||
const folderChanges: { create: string[]; update: string[]; delete: string[] } = {
|
||||
create: [],
|
||||
update: [],
|
||||
delete: []
|
||||
};
|
||||
|
||||
if ((changes.folders?.create?.length ?? 0) > 0) {
|
||||
const createdFolders = await folderService.createManyFolders({
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
folders:
|
||||
changes.folders?.create?.map((folder) => ({
|
||||
name: folder.folderName,
|
||||
environment,
|
||||
path: secretPath,
|
||||
description: folder.description
|
||||
})) ?? [],
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
const newFolderEvents = createdFolders.folders.map(
|
||||
(folder) =>
|
||||
({
|
||||
type: EventType.CREATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
folderName: folder.name,
|
||||
folderPath: secretPath,
|
||||
...(folder.description ? { description: folder.description } : {})
|
||||
}
|
||||
}) as Event
|
||||
);
|
||||
secretMutationEvents.push(...newFolderEvents);
|
||||
folderChanges.create.push(...createdFolders.folders.map((folder) => folder.id));
|
||||
}
|
||||
|
||||
if ((changes.folders?.update?.length ?? 0) > 0) {
|
||||
const updatedFolders = await folderService.updateManyFolders({
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
folders:
|
||||
changes.folders?.update?.map((folder) => ({
|
||||
environment,
|
||||
path: secretPath,
|
||||
id: folder.id,
|
||||
name: folder.folderName,
|
||||
description: folder.description
|
||||
})) ?? [],
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
|
||||
const updatedFolderEvents = updatedFolders.newFolders.map(
|
||||
(folder) =>
|
||||
({
|
||||
type: EventType.UPDATE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
folderPath: secretPath,
|
||||
newFolderName: folder.name,
|
||||
newFolderDescription: folder.description
|
||||
}
|
||||
}) as Event
|
||||
);
|
||||
secretMutationEvents.push(...updatedFolderEvents);
|
||||
folderChanges.update.push(...updatedFolders.newFolders.map((folder) => folder.id));
|
||||
}
|
||||
|
||||
if ((changes.folders?.delete?.length ?? 0) > 0) {
|
||||
const deletedFolders = await folderService.deleteManyFolders({
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
folders:
|
||||
changes.folders?.delete?.map((folder) => ({
|
||||
environment,
|
||||
path: secretPath,
|
||||
idOrName: folder.id
|
||||
})) ?? [],
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
const deletedFolderEvents = deletedFolders.folders.map(
|
||||
(folder) =>
|
||||
({
|
||||
type: EventType.DELETE_FOLDER,
|
||||
metadata: {
|
||||
environment,
|
||||
folderId: folder.id,
|
||||
folderPath: secretPath,
|
||||
folderName: folder.name
|
||||
}
|
||||
}) as Event
|
||||
);
|
||||
secretMutationEvents.push(...deletedFolderEvents);
|
||||
folderChanges.delete.push(...deletedFolders.folders.map((folder) => folder.id));
|
||||
}
|
||||
|
||||
if (policy) {
|
||||
if (
|
||||
(changes.secrets?.create?.length ?? 0) > 0 ||
|
||||
(changes.secrets?.update?.length ?? 0) > 0 ||
|
||||
(changes.secrets?.delete?.length ?? 0) > 0
|
||||
) {
|
||||
const approval = await secretApprovalRequestService.generateSecretApprovalRequestV2Bridge({
|
||||
policy,
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
data: {
|
||||
[SecretOperations.Create]:
|
||||
changes.secrets?.create?.map((el) => ({
|
||||
tagIds: el.tagIds,
|
||||
secretValue: el.secretValue,
|
||||
secretComment: el.secretComment,
|
||||
metadata: el.metadata,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
secretKey: el.secretKey,
|
||||
secretMetadata: el.secretMetadata
|
||||
})) ?? [],
|
||||
[SecretOperations.Update]:
|
||||
changes.secrets?.update?.map((el) => ({
|
||||
tagIds: el.tagIds,
|
||||
newSecretName: el.newSecretName,
|
||||
secretValue: el.secretValue,
|
||||
secretComment: el.secretComment,
|
||||
metadata: el.metadata,
|
||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||
secretKey: el.secretKey,
|
||||
secretMetadata: el.secretMetadata
|
||||
})) ?? [],
|
||||
[SecretOperations.Delete]:
|
||||
changes.secrets?.delete?.map((el) => ({
|
||||
secretKey: el.secretKey
|
||||
})) ?? []
|
||||
}
|
||||
});
|
||||
return {
|
||||
approvalId: approval.id,
|
||||
folderChanges,
|
||||
secretMutationEvents
|
||||
};
|
||||
}
|
||||
return {
|
||||
folderChanges,
|
||||
secretMutationEvents
|
||||
};
|
||||
}
|
||||
|
||||
if ((changes.secrets?.create?.length ?? 0) > 0) {
|
||||
const newSecrets = await secretV2BridgeService.createManySecret({
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
secrets: changes.secrets?.create ?? [],
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
secretMutationEvents.push({
|
||||
type: EventType.CREATE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secrets: newSecrets.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((changes.secrets?.update?.length ?? 0) > 0) {
|
||||
const updatedSecrets = await secretV2BridgeService.updateManySecret({
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
secrets: changes.secrets?.update ?? [],
|
||||
mode: SecretUpdateMode.FailOnNotFound,
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
secretMutationEvents.push({
|
||||
type: EventType.UPDATE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secrets: updatedSecrets.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((changes.secrets?.delete?.length ?? 0) > 0) {
|
||||
const deletedSecrets = await secretV2BridgeService.deleteManySecret({
|
||||
secretPath,
|
||||
environment,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
secrets: changes.secrets?.delete ?? [],
|
||||
tx: trx,
|
||||
commitChanges
|
||||
});
|
||||
secretMutationEvents.push({
|
||||
type: EventType.DELETE_SECRETS,
|
||||
metadata: {
|
||||
environment,
|
||||
secretPath,
|
||||
secrets: deletedSecrets.map((secret) => ({
|
||||
secretId: secret.id,
|
||||
secretKey: secret.secretKey,
|
||||
secretVersion: secret.version
|
||||
}))
|
||||
}
|
||||
});
|
||||
}
|
||||
if (commitChanges?.length > 0) {
|
||||
const commit = await folderCommitService.createCommit(
|
||||
{
|
||||
actor: {
|
||||
type: actor || ActorType.PLATFORM,
|
||||
metadata: {
|
||||
id: actorId
|
||||
}
|
||||
},
|
||||
message,
|
||||
folderId: targetFolder.id,
|
||||
changes: commitChanges
|
||||
},
|
||||
trx
|
||||
);
|
||||
return {
|
||||
folderChanges,
|
||||
commitId: commit?.id,
|
||||
secretMutationEvents
|
||||
};
|
||||
}
|
||||
return {
|
||||
folderChanges,
|
||||
secretMutationEvents
|
||||
};
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
return {
|
||||
getCommitsCount,
|
||||
getCommitsForFolder,
|
||||
@@ -478,6 +849,7 @@ export const pitServiceFactory = ({
|
||||
compareCommitChanges,
|
||||
rollbackToCommit,
|
||||
revertCommit,
|
||||
getFolderStateAtCommit
|
||||
getFolderStateAtCommit,
|
||||
processNewCommitRaw
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ProjectType } from "@app/db/schemas";
|
||||
import {
|
||||
InfisicalProjectTemplate,
|
||||
TUnpackedPermission
|
||||
@@ -6,18 +7,21 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"
|
||||
|
||||
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
|
||||
|
||||
export const getDefaultProjectTemplate = (orgId: string) => ({
|
||||
export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
|
||||
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
|
||||
type,
|
||||
name: InfisicalProjectTemplate.Default,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
description: `Infisical's default project template`,
|
||||
environments: ProjectTemplateDefaultEnvironments,
|
||||
roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{
|
||||
name: string;
|
||||
slug: string;
|
||||
permissions: TUnpackedPermission[];
|
||||
}>,
|
||||
description: `Infisical's ${type} default project template`,
|
||||
environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
|
||||
roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
|
||||
({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})
|
||||
),
|
||||
orgId
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
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 { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
@@ -29,11 +29,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
|
||||
...rest,
|
||||
environments: environments as TProjectTemplateEnvironment[],
|
||||
roles: [
|
||||
...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})),
|
||||
...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
|
||||
({ name, slug, permissions }) => ({
|
||||
name,
|
||||
slug,
|
||||
permissions: permissions as TUnpackedPermission[]
|
||||
})
|
||||
),
|
||||
...(roles as TProjectTemplateRole[]).map((role) => ({
|
||||
...role,
|
||||
permissions: unpackPermissions(role.permissions)
|
||||
@@ -46,7 +48,10 @@ export const projectTemplateServiceFactory = ({
|
||||
permissionService,
|
||||
projectTemplateDAL
|
||||
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
|
||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => {
|
||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
|
||||
actor,
|
||||
type
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
@@ -65,11 +70,14 @@ export const projectTemplateServiceFactory = ({
|
||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
|
||||
|
||||
const projectTemplates = await projectTemplateDAL.find({
|
||||
orgId: actor.orgId
|
||||
orgId: actor.orgId,
|
||||
...(type ? { type } : {})
|
||||
});
|
||||
|
||||
return [
|
||||
getDefaultProjectTemplate(actor.orgId),
|
||||
...(type
|
||||
? [getDefaultProjectTemplate(actor.orgId, type)]
|
||||
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
|
||||
...projectTemplates.map((template) => $unpackProjectTemplate(template))
|
||||
];
|
||||
};
|
||||
@@ -134,7 +142,7 @@ export const projectTemplateServiceFactory = ({
|
||||
};
|
||||
|
||||
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
|
||||
{ roles, environments, ...params },
|
||||
{ roles, environments, type, ...params },
|
||||
actor
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
@@ -154,6 +162,10 @@ export const projectTemplateServiceFactory = ({
|
||||
|
||||
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
|
||||
@@ -176,8 +188,10 @@ export const projectTemplateServiceFactory = ({
|
||||
const projectTemplate = await projectTemplateDAL.create({
|
||||
...params,
|
||||
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
|
||||
environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||
orgId: actor.orgId
|
||||
environments:
|
||||
type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
|
||||
orgId: actor.orgId,
|
||||
type
|
||||
});
|
||||
|
||||
return $unpackProjectTemplate(projectTemplate);
|
||||
@@ -208,6 +222,11 @@ export const projectTemplateServiceFactory = ({
|
||||
);
|
||||
|
||||
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({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
@@ -15,6 +15,7 @@ export type TProjectTemplateRole = {
|
||||
|
||||
export type TCreateProjectTemplateDTO = {
|
||||
name: string;
|
||||
type: ProjectType;
|
||||
description?: string;
|
||||
roles: TProjectTemplateRole[];
|
||||
environments?: TProjectTemplateEnvironment[] | null;
|
||||
@@ -29,11 +30,15 @@ export enum InfisicalProjectTemplate {
|
||||
}
|
||||
|
||||
export type TProjectTemplateServiceFactory = {
|
||||
listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise<
|
||||
listProjectTemplatesByOrg: (
|
||||
actor: OrgServiceActor,
|
||||
type?: ProjectType
|
||||
) => Promise<
|
||||
(
|
||||
| {
|
||||
id: string;
|
||||
name: InfisicalProjectTemplate;
|
||||
type: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description: string;
|
||||
@@ -58,6 +63,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
}
|
||||
| {
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
type: string;
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
@@ -94,6 +100,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
}[];
|
||||
name: string;
|
||||
orgId: string;
|
||||
type: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -118,6 +125,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
type: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
@@ -140,6 +148,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
type: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
@@ -162,6 +171,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
}[];
|
||||
name: string;
|
||||
orgId: string;
|
||||
type: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -184,6 +194,7 @@ export type TProjectTemplateServiceFactory = {
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
|
||||
+15
-8
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||
@@ -61,7 +61,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
|
||||
@@ -69,7 +70,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -164,7 +166,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||
@@ -172,7 +175,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
|
||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||
@@ -272,7 +276,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
@@ -317,7 +322,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
@@ -343,7 +349,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorId,
|
||||
projectId: projectMembership.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import picomatch from "picomatch";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -110,7 +111,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
@@ -304,7 +306,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@@ -459,7 +462,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sapPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Delete,
|
||||
@@ -498,7 +502,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@@ -542,7 +547,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||
@@ -568,7 +574,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sapPolicy.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
@@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
|
||||
firstName: reviewerUser.firstName,
|
||||
projectName: project.name,
|
||||
organizationName: project.organization.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval?requestId=${secretApprovalRequest.id}`
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval?requestId=${secretApprovalRequest.id}`
|
||||
},
|
||||
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
||||
});
|
||||
|
||||
+28
-13
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import {
|
||||
ActionProjectType,
|
||||
ProjectMembershipRole,
|
||||
SecretEncryptionAlgo,
|
||||
SecretKeyEncoding,
|
||||
@@ -183,7 +185,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
|
||||
@@ -210,7 +213,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
@@ -262,7 +266,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@@ -411,7 +416,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@@ -480,7 +486,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId: secretApprovalRequest.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
@@ -536,7 +543,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
if (
|
||||
@@ -954,7 +962,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
bypassReason,
|
||||
secretPath: policy.secretPath,
|
||||
environment: env.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`
|
||||
approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
});
|
||||
@@ -1088,7 +1096,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
||||
@@ -1368,8 +1377,9 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
policy,
|
||||
projectId,
|
||||
secretPath,
|
||||
environment
|
||||
}: TGenerateSecretApprovalRequestV2BridgeDTO) => {
|
||||
environment,
|
||||
trx: providedTx
|
||||
}: TGenerateSecretApprovalRequestV2BridgeDTO & { trx?: Knex }) => {
|
||||
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
||||
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
||||
|
||||
@@ -1378,7 +1388,8 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||
if (!folder)
|
||||
@@ -1595,7 +1606,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
);
|
||||
});
|
||||
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||
const executeApprovalRequestCreation = async (tx: Knex) => {
|
||||
const doc = await secretApprovalRequestDAL.create(
|
||||
{
|
||||
folderId,
|
||||
@@ -1657,7 +1668,11 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
}
|
||||
|
||||
return { ...doc, commits: approvalCommits };
|
||||
});
|
||||
};
|
||||
|
||||
const secretApprovalRequest = providedTx
|
||||
? await executeApprovalRequestCreation(providedTx)
|
||||
: await secretApprovalRequestDAL.transaction(executeApprovalRequestCreation);
|
||||
|
||||
const user = await userDAL.findById(actorId);
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./okta-client-secret-rotation-constants";
|
||||
export * from "./okta-client-secret-rotation-schemas";
|
||||
export * from "./okta-client-secret-rotation-types";
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
|
||||
name: "Okta Client Secret",
|
||||
type: SecretRotation.OktaClientSecret,
|
||||
connection: AppConnection.Okta,
|
||||
template: {
|
||||
secretsMapping: {
|
||||
clientId: "OKTA_CLIENT_ID",
|
||||
clientSecret: "OKTA_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
};
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
TRotationFactoryIssueCredentials,
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { delay as delayMs } from "@app/lib/delay";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { getOktaInstanceUrl } from "@app/services/app-connection/okta";
|
||||
|
||||
import {
|
||||
TOktaClientSecret,
|
||||
TOktaClientSecretRotationGeneratedCredentials,
|
||||
TOktaClientSecretRotationWithConnection
|
||||
} from "./okta-client-secret-rotation-types";
|
||||
|
||||
type OktaErrorResponse = { errorCode: string; errorSummary: string; errorCauses?: { errorSummary: string }[] };
|
||||
|
||||
const isOktaErrorResponse = (data: unknown): data is OktaErrorResponse => {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
"errorSummary" in data &&
|
||||
typeof (data as OktaErrorResponse).errorSummary === "string"
|
||||
);
|
||||
};
|
||||
|
||||
const createErrorMessage = (error: unknown) => {
|
||||
if (error instanceof AxiosError) {
|
||||
if (error.response?.data && isOktaErrorResponse(error.response.data)) {
|
||||
const oktaError = error.response.data;
|
||||
if (oktaError.errorCauses && oktaError.errorCauses.length > 0) {
|
||||
return oktaError.errorCauses[0].errorSummary;
|
||||
}
|
||||
return oktaError.errorSummary;
|
||||
}
|
||||
if (error.message) {
|
||||
return error.message;
|
||||
}
|
||||
}
|
||||
return "Unknown error";
|
||||
};
|
||||
|
||||
// Delay between each revocation call in revokeCredentials
|
||||
const DELAY_MS = 1000;
|
||||
|
||||
export const oktaClientSecretRotationFactory: TRotationFactory<
|
||||
TOktaClientSecretRotationWithConnection,
|
||||
TOktaClientSecretRotationGeneratedCredentials
|
||||
> = (secretRotation) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { clientId },
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
/**
|
||||
* Creates a new client secret for the Okta app.
|
||||
*/
|
||||
const $rotateClientSecret = async () => {
|
||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
||||
|
||||
try {
|
||||
const { data } = await request.post<TOktaClientSecret>(
|
||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!data.client_secret || !data.id) {
|
||||
throw new Error("Invalid response from Okta: missing 'client_secret' or secret 'id'.");
|
||||
}
|
||||
|
||||
return {
|
||||
clientSecret: data.client_secret,
|
||||
secretId: data.id,
|
||||
clientId
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error instanceof AxiosError &&
|
||||
error.response?.data &&
|
||||
isOktaErrorResponse(error.response.data) &&
|
||||
error.response.data.errorCode === "E0000001"
|
||||
) {
|
||||
// Okta has a maximum of 2 secrets per app, thus we must warn the users in case they already have 2
|
||||
throw new BadRequestError({
|
||||
message: `Failed to add client secret to Okta app ${clientId}: You must have only a single secret for the Okta app prior to creating this secret rotation.`
|
||||
});
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Failed to add client secret to Okta app ${clientId}: ${createErrorMessage(error)}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* List client secrets.
|
||||
*/
|
||||
const $listClientSecrets = async () => {
|
||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
||||
|
||||
try {
|
||||
const { data } = await request.get<TOktaClientSecret[]>(
|
||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (error: unknown) {
|
||||
throw new BadRequestError({
|
||||
message: `Failed to list client secrets for Okta app ${clientId}: ${createErrorMessage(error)}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a credential with the given secretId exists.
|
||||
*/
|
||||
const credentialExists = async (secretId: string): Promise<boolean> => {
|
||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
||||
|
||||
try {
|
||||
const { data } = await request.get<TOktaClientSecret>(
|
||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`,
|
||||
{
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return data.id === secretId;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Revokes a client secret from the Okta app using its secretId.
|
||||
* First checks if the credential exists before attempting revocation.
|
||||
*/
|
||||
const revokeCredential = async (secretId: string) => {
|
||||
// Check if credential exists before attempting revocation
|
||||
const exists = await credentialExists(secretId);
|
||||
if (!exists) {
|
||||
return; // Credential doesn't exist, nothing to revoke
|
||||
}
|
||||
|
||||
const instanceUrl = await getOktaInstanceUrl(connection);
|
||||
|
||||
try {
|
||||
// First deactivate the secret
|
||||
await request.post(
|
||||
`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}/lifecycle/deactivate`,
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Then delete it
|
||||
await request.delete(`${instanceUrl}/api/v1/apps/${clientId}/credentials/secrets/${secretId}`, {
|
||||
headers: {
|
||||
Authorization: `SSWS ${connection.credentials.apiToken}`
|
||||
}
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
error instanceof AxiosError &&
|
||||
error.response?.data &&
|
||||
isOktaErrorResponse(error.response.data) &&
|
||||
error.response.data.errorCode === "E0000001"
|
||||
) {
|
||||
// If this is the last secret, we cannot revoke it
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Failed to remove client secret with secretId ${secretId} from app ${clientId}: ${createErrorMessage(error)}`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Issues a new set of credentials.
|
||||
*/
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const credentials = await $rotateClientSecret();
|
||||
return callback(credentials);
|
||||
};
|
||||
|
||||
/**
|
||||
* Revokes a list of credentials.
|
||||
*/
|
||||
const revokeCredentials: TRotationFactoryRevokeCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
||||
credentials,
|
||||
callback
|
||||
) => {
|
||||
if (!credentials?.length) return callback();
|
||||
|
||||
for (const { secretId } of credentials) {
|
||||
await revokeCredential(secretId);
|
||||
await delayMs(DELAY_MS);
|
||||
}
|
||||
return callback();
|
||||
};
|
||||
|
||||
/**
|
||||
* Rotates credentials by issuing new ones and revoking the old.
|
||||
*/
|
||||
const rotateCredentials: TRotationFactoryRotateCredentials<TOktaClientSecretRotationGeneratedCredentials> = async (
|
||||
oldCredentials,
|
||||
callback,
|
||||
activeCredentials
|
||||
) => {
|
||||
// Since in Okta you can only have a maximum of 2 secrets at a time, we must delete any other secret besides the current one PRIOR to generating the second secret
|
||||
if (oldCredentials?.secretId) {
|
||||
await revokeCredential(oldCredentials.secretId);
|
||||
} else if (activeCredentials) {
|
||||
// On the first rotation oldCredentials won't be set so we must find the second secret manually
|
||||
const secrets = await $listClientSecrets();
|
||||
|
||||
if (secrets.length > 1) {
|
||||
const nonActiveSecret = secrets.find((secret) => secret.id !== activeCredentials.secretId);
|
||||
if (nonActiveSecret) {
|
||||
await revokeCredential(nonActiveSecret.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newCredentials = await $rotateClientSecret();
|
||||
return callback(newCredentials);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the generated credentials into the secret payload format.
|
||||
*/
|
||||
const getSecretsPayload: TRotationFactoryGetSecretsPayload<TOktaClientSecretRotationGeneratedCredentials> = ({
|
||||
clientSecret
|
||||
}) => [
|
||||
{ key: secretsMapping.clientId, value: clientId },
|
||||
{ key: secretsMapping.clientSecret, value: clientSecret }
|
||||
];
|
||||
|
||||
return {
|
||||
issueCredentials,
|
||||
revokeCredentials,
|
||||
rotateCredentials,
|
||||
getSecretsPayload
|
||||
};
|
||||
};
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import {
|
||||
BaseCreateSecretRotationSchema,
|
||||
BaseSecretRotationSchema,
|
||||
BaseUpdateSecretRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-schemas";
|
||||
import { SecretRotations } from "@app/lib/api-docs";
|
||||
import { SecretNameSchema } from "@app/server/lib/schemas";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const OktaClientSecretRotationGeneratedCredentialsSchema = z
|
||||
.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string(),
|
||||
secretId: z.string()
|
||||
})
|
||||
.array()
|
||||
.min(1)
|
||||
.max(2);
|
||||
|
||||
const OktaClientSecretRotationParametersSchema = z.object({
|
||||
clientId: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Client ID Required")
|
||||
.describe(SecretRotations.PARAMETERS.OKTA_CLIENT_SECRET.clientId)
|
||||
});
|
||||
|
||||
const OktaClientSecretRotationSecretsMappingSchema = z.object({
|
||||
clientId: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientId),
|
||||
clientSecret: SecretNameSchema.describe(SecretRotations.SECRETS_MAPPING.OKTA_CLIENT_SECRET.clientSecret)
|
||||
});
|
||||
|
||||
export const OktaClientSecretRotationTemplateSchema = z.object({
|
||||
secretsMapping: z.object({
|
||||
clientId: z.string(),
|
||||
clientSecret: z.string()
|
||||
})
|
||||
});
|
||||
|
||||
export const OktaClientSecretRotationSchema = BaseSecretRotationSchema(SecretRotation.OktaClientSecret).extend({
|
||||
type: z.literal(SecretRotation.OktaClientSecret),
|
||||
parameters: OktaClientSecretRotationParametersSchema,
|
||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const CreateOktaClientSecretRotationSchema = BaseCreateSecretRotationSchema(
|
||||
SecretRotation.OktaClientSecret
|
||||
).extend({
|
||||
parameters: OktaClientSecretRotationParametersSchema,
|
||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema
|
||||
});
|
||||
|
||||
export const UpdateOktaClientSecretRotationSchema = BaseUpdateSecretRotationSchema(
|
||||
SecretRotation.OktaClientSecret
|
||||
).extend({
|
||||
parameters: OktaClientSecretRotationParametersSchema.optional(),
|
||||
secretsMapping: OktaClientSecretRotationSecretsMappingSchema.optional()
|
||||
});
|
||||
|
||||
export const OktaClientSecretRotationListItemSchema = z.object({
|
||||
name: z.literal("Okta Client Secret"),
|
||||
connection: z.literal(AppConnection.Okta),
|
||||
type: z.literal(SecretRotation.OktaClientSecret),
|
||||
template: OktaClientSecretRotationTemplateSchema
|
||||
});
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { TOktaConnection } from "@app/services/app-connection/okta";
|
||||
|
||||
import {
|
||||
CreateOktaClientSecretRotationSchema,
|
||||
OktaClientSecretRotationGeneratedCredentialsSchema,
|
||||
OktaClientSecretRotationListItemSchema,
|
||||
OktaClientSecretRotationSchema
|
||||
} from "./okta-client-secret-rotation-schemas";
|
||||
|
||||
export type TOktaClientSecretRotation = z.infer<typeof OktaClientSecretRotationSchema>;
|
||||
|
||||
export type TOktaClientSecretRotationInput = z.infer<typeof CreateOktaClientSecretRotationSchema>;
|
||||
|
||||
export type TOktaClientSecretRotationListItem = z.infer<typeof OktaClientSecretRotationListItemSchema>;
|
||||
|
||||
export type TOktaClientSecretRotationWithConnection = TOktaClientSecretRotation & {
|
||||
connection: TOktaConnection;
|
||||
};
|
||||
|
||||
export type TOktaClientSecretRotationGeneratedCredentials = z.infer<
|
||||
typeof OktaClientSecretRotationGeneratedCredentialsSchema
|
||||
>;
|
||||
|
||||
export interface TOktaClientSecretRotationParameters {
|
||||
clientId: string;
|
||||
secretId: string;
|
||||
}
|
||||
|
||||
export interface TOktaClientSecretRotationSecretsMapping {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
secretId: string;
|
||||
}
|
||||
|
||||
export interface TOktaClientSecret {
|
||||
id: string;
|
||||
client_secret: string;
|
||||
}
|
||||
@@ -6,7 +6,8 @@ export enum SecretRotation {
|
||||
Auth0ClientSecret = "auth0-client-secret",
|
||||
AzureClientSecret = "azure-client-secret",
|
||||
AwsIamUserSecret = "aws-iam-user-secret",
|
||||
LdapPassword = "ldap-password"
|
||||
LdapPassword = "ldap-password",
|
||||
OktaClientSecret = "okta-client-secret"
|
||||
}
|
||||
|
||||
export enum SecretRotationStatus {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./azure-client-secret"
|
||||
import { LDAP_PASSWORD_ROTATION_LIST_OPTION, TLdapPasswordRotation } from "./ldap-password";
|
||||
import { MSSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mssql-credentials";
|
||||
import { MYSQL_CREDENTIALS_ROTATION_LIST_OPTION } from "./mysql-credentials";
|
||||
import { OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION } from "./okta-client-secret";
|
||||
import { ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION } from "./oracledb-credentials";
|
||||
import { POSTGRES_CREDENTIALS_ROTATION_LIST_OPTION } from "./postgres-credentials";
|
||||
import { SecretRotation, SecretRotationStatus } from "./secret-rotation-v2-enums";
|
||||
@@ -30,7 +31,8 @@ const SECRET_ROTATION_LIST_OPTIONS: Record<SecretRotation, TSecretRotationV2List
|
||||
[SecretRotation.Auth0ClientSecret]: AUTH0_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.AzureClientSecret]: AZURE_CLIENT_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.AwsIamUserSecret]: AWS_IAM_USER_SECRET_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION
|
||||
[SecretRotation.LdapPassword]: LDAP_PASSWORD_ROTATION_LIST_OPTION,
|
||||
[SecretRotation.OktaClientSecret]: OKTA_CLIENT_SECRET_ROTATION_LIST_OPTION
|
||||
};
|
||||
|
||||
export const listSecretRotationOptions = () => {
|
||||
|
||||
@@ -9,7 +9,8 @@ export const SECRET_ROTATION_NAME_MAP: Record<SecretRotation, string> = {
|
||||
[SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret",
|
||||
[SecretRotation.AzureClientSecret]: "Azure Client Secret",
|
||||
[SecretRotation.AwsIamUserSecret]: "AWS IAM User Secret",
|
||||
[SecretRotation.LdapPassword]: "LDAP Password"
|
||||
[SecretRotation.LdapPassword]: "LDAP Password",
|
||||
[SecretRotation.OktaClientSecret]: "Okta Client Secret"
|
||||
};
|
||||
|
||||
export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnection> = {
|
||||
@@ -20,5 +21,6 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
|
||||
[SecretRotation.Auth0ClientSecret]: AppConnection.Auth0,
|
||||
[SecretRotation.AzureClientSecret]: AppConnection.AzureClientSecrets,
|
||||
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
|
||||
[SecretRotation.LdapPassword]: AppConnection.LDAP
|
||||
[SecretRotation.LdapPassword]: AppConnection.LDAP,
|
||||
[SecretRotation.OktaClientSecret]: AppConnection.Okta
|
||||
};
|
||||
|
||||
@@ -2,8 +2,9 @@ import { ForbiddenError, subject } from "@casl/ability";
|
||||
import { Knex } from "knex";
|
||||
import isEqual from "lodash.isequal";
|
||||
|
||||
import { SecretType, TableName } from "@app/db/schemas";
|
||||
import { ActionProjectType, SecretType, TableName } from "@app/db/schemas";
|
||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { hasSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
@@ -82,6 +83,7 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
|
||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||
|
||||
import { awsIamUserSecretRotationFactory } from "./aws-iam-user-secret/aws-iam-user-secret-rotation-fns";
|
||||
import { oktaClientSecretRotationFactory } from "./okta-client-secret/okta-client-secret-rotation-fns";
|
||||
import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
|
||||
export type TSecretRotationV2ServiceFactoryDep = {
|
||||
@@ -107,6 +109,7 @@ export type TSecretRotationV2ServiceFactoryDep = {
|
||||
queueService: Pick<TQueueServiceFactory, "queuePg">;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
|
||||
folderCommitService: Pick<TFolderCommitServiceFactory, "createCommit">;
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
};
|
||||
|
||||
export type TSecretRotationV2ServiceFactory = ReturnType<typeof secretRotationV2ServiceFactory>;
|
||||
@@ -126,7 +129,8 @@ const SECRET_ROTATION_FACTORY_MAP: Record<SecretRotation, TRotationFactoryImplem
|
||||
[SecretRotation.Auth0ClientSecret]: auth0ClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.AzureClientSecret]: azureClientSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.AwsIamUserSecret]: awsIamUserSecretRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation
|
||||
[SecretRotation.LdapPassword]: ldapPasswordRotationFactory as TRotationFactoryImplementation,
|
||||
[SecretRotation.OktaClientSecret]: oktaClientSecretRotationFactory as TRotationFactoryImplementation
|
||||
};
|
||||
|
||||
export const secretRotationV2ServiceFactory = ({
|
||||
@@ -148,7 +152,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
keyStore,
|
||||
queueService,
|
||||
folderCommitService,
|
||||
appConnectionDAL
|
||||
appConnectionDAL,
|
||||
gatewayService
|
||||
}: TSecretRotationV2ServiceFactoryDep) => {
|
||||
const $queueSendSecretRotationStatusNotification = async (secretRotation: TSecretRotationV2Raw) => {
|
||||
const appCfg = getConfig();
|
||||
@@ -218,7 +223,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -269,7 +274,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -315,7 +320,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -380,7 +385,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -424,7 +429,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -461,7 +466,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
rotationInterval: payload.rotationInterval
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
gatewayService
|
||||
);
|
||||
|
||||
// even though we have a db constraint we want to check before any rotation of credentials is attempted
|
||||
@@ -625,7 +631,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -775,7 +781,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -824,7 +830,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
gatewayService
|
||||
);
|
||||
|
||||
const generatedCredentials = await decryptSecretRotationCredentials({
|
||||
@@ -907,7 +914,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
connection: appConnection
|
||||
} as TSecretRotationV2WithConnection,
|
||||
appConnectionDAL,
|
||||
kmsService
|
||||
kmsService,
|
||||
gatewayService
|
||||
);
|
||||
|
||||
const updatedRotation = await rotationFactory.rotateCredentials(
|
||||
@@ -1105,7 +1113,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -1152,7 +1160,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -1204,7 +1212,7 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretManager,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -1320,7 +1328,8 @@ export const secretRotationV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
projectId,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AuditLogInfo } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||
import { TSqlCredentialsRotationGeneratedCredentials } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-types";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||
@@ -45,6 +46,13 @@ import {
|
||||
TMySqlCredentialsRotationListItem,
|
||||
TMySqlCredentialsRotationWithConnection
|
||||
} from "./mysql-credentials";
|
||||
import {
|
||||
TOktaClientSecretRotation,
|
||||
TOktaClientSecretRotationGeneratedCredentials,
|
||||
TOktaClientSecretRotationInput,
|
||||
TOktaClientSecretRotationListItem,
|
||||
TOktaClientSecretRotationWithConnection
|
||||
} from "./okta-client-secret";
|
||||
import {
|
||||
TOracleDBCredentialsRotation,
|
||||
TOracleDBCredentialsRotationInput,
|
||||
@@ -68,7 +76,8 @@ export type TSecretRotationV2 =
|
||||
| TAuth0ClientSecretRotation
|
||||
| TAzureClientSecretRotation
|
||||
| TLdapPasswordRotation
|
||||
| TAwsIamUserSecretRotation;
|
||||
| TAwsIamUserSecretRotation
|
||||
| TOktaClientSecretRotation;
|
||||
|
||||
export type TSecretRotationV2WithConnection =
|
||||
| TPostgresCredentialsRotationWithConnection
|
||||
@@ -78,14 +87,16 @@ export type TSecretRotationV2WithConnection =
|
||||
| TAuth0ClientSecretRotationWithConnection
|
||||
| TAzureClientSecretRotationWithConnection
|
||||
| TLdapPasswordRotationWithConnection
|
||||
| TAwsIamUserSecretRotationWithConnection;
|
||||
| TAwsIamUserSecretRotationWithConnection
|
||||
| TOktaClientSecretRotationWithConnection;
|
||||
|
||||
export type TSecretRotationV2GeneratedCredentials =
|
||||
| TSqlCredentialsRotationGeneratedCredentials
|
||||
| TAuth0ClientSecretRotationGeneratedCredentials
|
||||
| TAzureClientSecretRotationGeneratedCredentials
|
||||
| TLdapPasswordRotationGeneratedCredentials
|
||||
| TAwsIamUserSecretRotationGeneratedCredentials;
|
||||
| TAwsIamUserSecretRotationGeneratedCredentials
|
||||
| TOktaClientSecretRotationGeneratedCredentials;
|
||||
|
||||
export type TSecretRotationV2Input =
|
||||
| TPostgresCredentialsRotationInput
|
||||
@@ -95,7 +106,8 @@ export type TSecretRotationV2Input =
|
||||
| TAuth0ClientSecretRotationInput
|
||||
| TAzureClientSecretRotationInput
|
||||
| TLdapPasswordRotationInput
|
||||
| TAwsIamUserSecretRotationInput;
|
||||
| TAwsIamUserSecretRotationInput
|
||||
| TOktaClientSecretRotationInput;
|
||||
|
||||
export type TSecretRotationV2ListItem =
|
||||
| TPostgresCredentialsRotationListItem
|
||||
@@ -105,7 +117,8 @@ export type TSecretRotationV2ListItem =
|
||||
| TAuth0ClientSecretRotationListItem
|
||||
| TAzureClientSecretRotationListItem
|
||||
| TLdapPasswordRotationListItem
|
||||
| TAwsIamUserSecretRotationListItem;
|
||||
| TAwsIamUserSecretRotationListItem
|
||||
| TOktaClientSecretRotationListItem;
|
||||
|
||||
export type TSecretRotationV2TemporaryParameters = TLdapPasswordRotationInput["temporaryParameters"] | undefined;
|
||||
|
||||
@@ -239,7 +252,8 @@ export type TRotationFactory<
|
||||
> = (
|
||||
secretRotation: T,
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">,
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">
|
||||
) => {
|
||||
issueCredentials: TRotationFactoryIssueCredentials<C, P>;
|
||||
revokeCredentials: TRotationFactoryRevokeCredentials<C>;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { AzureClientSecretRotationSchema } from "@app/ee/services/secret-rotatio
|
||||
import { LdapPasswordRotationSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { MySqlCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { OktaClientSecretRotationSchema } from "@app/ee/services/secret-rotation-v2/okta-client-secret";
|
||||
import { OracleDBCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||
import { PostgresCredentialsRotationSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
|
||||
@@ -17,5 +18,6 @@ export const SecretRotationV2Schema = z.discriminatedUnion("type", [
|
||||
Auth0ClientSecretRotationSchema,
|
||||
AzureClientSecretRotationSchema,
|
||||
LdapPasswordRotationSchema,
|
||||
AwsIamUserSecretRotationSchema
|
||||
AwsIamUserSecretRotationSchema,
|
||||
OktaClientSecretRotationSchema
|
||||
]);
|
||||
|
||||
+45
-35
@@ -1,3 +1,5 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import {
|
||||
TRotationFactory,
|
||||
TRotationFactoryGetSecretsPayload,
|
||||
@@ -5,7 +7,10 @@ import {
|
||||
TRotationFactoryRevokeCredentials,
|
||||
TRotationFactoryRotateCredentials
|
||||
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
|
||||
import { getSqlConnectionClient, SQL_CONNECTION_ALTER_LOGIN_STATEMENT } from "@app/services/app-connection/shared/sql";
|
||||
import {
|
||||
executeWithPotentialGateway,
|
||||
SQL_CONNECTION_ALTER_LOGIN_STATEMENT
|
||||
} from "@app/services/app-connection/shared/sql";
|
||||
|
||||
import { generatePassword } from "../utils";
|
||||
import {
|
||||
@@ -30,7 +35,7 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat
|
||||
export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
TSqlCredentialsRotationWithConnection,
|
||||
TSqlCredentialsRotationGeneratedCredentials
|
||||
> = (secretRotation) => {
|
||||
> = (secretRotation, _appConnectionDAL, _kmsService, gatewayService) => {
|
||||
const {
|
||||
connection,
|
||||
parameters: { username1, username2 },
|
||||
@@ -38,29 +43,38 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
secretsMapping
|
||||
} = secretRotation;
|
||||
|
||||
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||
const client = await getSqlConnectionClient({
|
||||
...connection,
|
||||
credentials: {
|
||||
...connection.credentials,
|
||||
...credentials
|
||||
}
|
||||
});
|
||||
const executeOperation = <T>(
|
||||
operation: (client: Knex) => Promise<T>,
|
||||
credentialsOverride?: TSqlCredentialsRotationGeneratedCredentials[number]
|
||||
) => {
|
||||
const finalCredentials = {
|
||||
...connection.credentials,
|
||||
...credentialsOverride
|
||||
};
|
||||
|
||||
return executeWithPotentialGateway(
|
||||
{
|
||||
...connection,
|
||||
credentials: finalCredentials
|
||||
},
|
||||
gatewayService,
|
||||
(client) => operation(client)
|
||||
);
|
||||
};
|
||||
|
||||
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
|
||||
try {
|
||||
await client.raw("SELECT 1");
|
||||
await executeOperation(async (client) => {
|
||||
await client.raw("SELECT 1");
|
||||
}, credentials);
|
||||
} catch (error) {
|
||||
throw new Error(redactPasswords(error, [credentials]));
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
// For SQL, since we get existing users, we change both their passwords
|
||||
// on issue to invalidate their existing passwords
|
||||
const credentialsSet = [
|
||||
@@ -69,15 +83,15 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
];
|
||||
|
||||
try {
|
||||
await client.transaction(async (tx) => {
|
||||
for await (const credentials of credentialsSet) {
|
||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
}
|
||||
await executeOperation(async (client) => {
|
||||
await client.transaction(async (tx) => {
|
||||
for await (const credentials of credentialsSet) {
|
||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(redactPasswords(error, credentialsSet));
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
for await (const credentials of credentialsSet) {
|
||||
@@ -91,21 +105,19 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
credentialsToRevoke,
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() }));
|
||||
|
||||
try {
|
||||
await client.transaction(async (tx) => {
|
||||
for await (const credentials of revokedCredentials) {
|
||||
// invalidate previous passwords
|
||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
}
|
||||
await executeOperation(async (client) => {
|
||||
await client.transaction(async (tx) => {
|
||||
for await (const credentials of revokedCredentials) {
|
||||
// invalidate previous passwords
|
||||
await tx.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(redactPasswords(error, revokedCredentials));
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
return callback();
|
||||
@@ -115,17 +127,15 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
|
||||
_,
|
||||
callback
|
||||
) => {
|
||||
const client = await getSqlConnectionClient(connection);
|
||||
|
||||
// generate new password for the next active user
|
||||
const credentials = { username: activeIndex === 0 ? username2 : username1, password: generatePassword() };
|
||||
|
||||
try {
|
||||
await client.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
await executeOperation(async (client) => {
|
||||
await client.raw(...SQL_CONNECTION_ALTER_LOGIN_STATEMENT[connection.app](credentials));
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(redactPasswords(error, [credentials]));
|
||||
} finally {
|
||||
await client.destroy();
|
||||
}
|
||||
|
||||
await $validateCredentials(credentials);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
import Ajv from "ajv";
|
||||
|
||||
import { ProjectVersion, TableName } from "@app/db/schemas";
|
||||
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
||||
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
@@ -66,7 +66,8 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@@ -97,7 +98,8 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@@ -213,7 +215,8 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Read,
|
||||
@@ -263,7 +266,8 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Edit,
|
||||
@@ -283,7 +287,8 @@ export const secretRotationServiceFactory = ({
|
||||
actorId,
|
||||
projectId: doc.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionSecretRotationActions.Delete,
|
||||
|
||||
@@ -567,14 +567,18 @@ export const secretScanningV2QueueServiceFactory = async ({
|
||||
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
|
||||
const project = await projectDAL.findById(projectId);
|
||||
|
||||
const projectAdmins = projectMembers.filter((member) =>
|
||||
member.roles.some((role) => role.role === ProjectMembershipRole.Admin)
|
||||
);
|
||||
const recipients = projectMembers.filter((member) => {
|
||||
const isAdmin = member.roles.some((role) => role.role === ProjectMembershipRole.Admin);
|
||||
const isCompleted = payload.status === SecretScanningScanStatus.Completed;
|
||||
// We assume that the committer is one of the project members
|
||||
const isCommitter = isCompleted && payload.authorEmail === member.user.email;
|
||||
return isAdmin || isCommitter;
|
||||
});
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
await smtpService.sendMail({
|
||||
recipients: projectAdmins.map((member) => member.user.email!).filter(Boolean),
|
||||
recipients: recipients.map((member) => member.user.email!).filter(Boolean),
|
||||
template:
|
||||
payload.status === SecretScanningScanStatus.Completed
|
||||
? SmtpTemplates.SecretScanningV2SecretsDetected
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import { join } from "path";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
@@ -94,7 +95,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -156,7 +157,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -201,7 +202,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -235,7 +236,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: payload.projectId
|
||||
});
|
||||
|
||||
@@ -348,7 +349,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -401,6 +402,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -474,7 +476,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -538,7 +540,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -583,7 +585,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -626,7 +628,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -669,7 +671,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: dataSource.projectId
|
||||
});
|
||||
|
||||
@@ -702,7 +704,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -736,7 +738,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -776,7 +778,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId: finding.projectId
|
||||
});
|
||||
|
||||
@@ -807,7 +809,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
@@ -842,7 +844,7 @@ export const secretScanningV2ServiceFactory = ({
|
||||
actorId: actor.id,
|
||||
actorAuthMethod: actor.authMethod,
|
||||
actorOrgId: actor.orgId,
|
||||
|
||||
actionProjectType: ActionProjectType.SecretScanning,
|
||||
projectId
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
||||
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
@@ -103,7 +103,8 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
@@ -139,7 +140,8 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
|
||||
@@ -167,7 +169,8 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId: snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||
@@ -391,7 +394,8 @@ export const secretSnapshotServiceFactory = ({
|
||||
actorId,
|
||||
projectId: snapshot.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Create,
|
||||
|
||||
+9
-4
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
@@ -58,7 +59,8 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -130,7 +132,8 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -198,7 +201,8 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certificateTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -224,7 +228,8 @@ export const sshCertificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
projectId: certTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
|
||||
@@ -79,7 +80,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -171,7 +173,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -267,7 +270,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -290,7 +294,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -316,7 +321,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -354,7 +360,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
@@ -393,7 +400,8 @@ export const sshHostGroupServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshHostGroup.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
@@ -62,7 +63,8 @@ export const createSshLoginMappings = async ({
|
||||
userId: user.id,
|
||||
projectId,
|
||||
authMethod: actorAuthMethod,
|
||||
userOrgId: actorOrgId
|
||||
userOrgId: actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
@@ -111,7 +112,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId);
|
||||
@@ -144,7 +146,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -273,7 +276,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -334,7 +338,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -362,7 +367,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -401,7 +407,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
const internalPrincipals = await convertActorToPrincipals({
|
||||
@@ -520,7 +527,8 @@ export const sshHostServiceFactory = ({
|
||||
actorId,
|
||||
projectId: host.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||
@@ -72,7 +73,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -107,7 +109,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -175,7 +178,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -213,7 +217,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -254,7 +259,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshCertificateTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -375,7 +381,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: sshCertificateTemplate.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
@@ -472,7 +479,8 @@ export const sshCertificateAuthorityServiceFactory = ({
|
||||
actorId,
|
||||
projectId: ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SSH
|
||||
});
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
@@ -35,7 +36,8 @@ export const trustedIpServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||
const trustedIps = await trustedIpDAL.find({
|
||||
@@ -59,7 +61,8 @@ export const trustedIpServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
@@ -104,7 +107,8 @@ export const trustedIpServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
@@ -149,7 +153,8 @@ export const trustedIpServiceFactory = ({
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user