mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 14:27:23 +00:00
feature: add suport for project scoped app connections
This commit is contained in:
@@ -392,6 +392,8 @@ export enum EventType {
|
||||
CREATE_APP_CONNECTION = "create-app-connection",
|
||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||
GET_APP_CONNECTION_USAGE = "get-app-connection-usage",
|
||||
MIGRATE_APP_CONNECTION = "migrate-app-connection",
|
||||
CREATE_SHARED_SECRET = "create-shared-secret",
|
||||
CREATE_SECRET_REQUEST = "create-secret-request",
|
||||
DELETE_SHARED_SECRET = "delete-shared-secret",
|
||||
@@ -2781,14 +2783,31 @@ interface GetAppConnectionEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetAppConnectionUsageEvent {
|
||||
type: EventType.GET_APP_CONNECTION_USAGE;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface MigrateAppConnectionEvent {
|
||||
type: EventType.MIGRATE_APP_CONNECTION;
|
||||
metadata: {
|
||||
connectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateAppConnectionEvent {
|
||||
type: EventType.CREATE_APP_CONNECTION;
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials"> & { connectionId: string };
|
||||
metadata: Omit<TCreateAppConnectionDTO, "credentials" | "projectId"> & { connectionId: string };
|
||||
}
|
||||
|
||||
interface UpdateAppConnectionEvent {
|
||||
type: EventType.UPDATE_APP_CONNECTION;
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials"> & { connectionId: string; credentialsUpdated: boolean };
|
||||
metadata: Omit<TUpdateAppConnectionDTO, "credentials" | "projectId"> & {
|
||||
connectionId: string;
|
||||
credentialsUpdated: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteAppConnectionEvent {
|
||||
@@ -3697,6 +3716,8 @@ export type Event =
|
||||
| CreateAppConnectionEvent
|
||||
| UpdateAppConnectionEvent
|
||||
| DeleteAppConnectionEvent
|
||||
| GetAppConnectionUsageEvent
|
||||
| MigrateAppConnectionEvent
|
||||
| GetSshHostGroupEvent
|
||||
| CreateSshHostGroupEvent
|
||||
| UpdateSshHostGroupEvent
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability"
|
||||
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionAppConnectionActions,
|
||||
ProjectPermissionAuditLogsActions,
|
||||
ProjectPermissionCertificateActions,
|
||||
ProjectPermissionCmekActions,
|
||||
@@ -264,6 +265,17 @@ const buildAdminPermissionRules = () => {
|
||||
ProjectPermissionSub.SecretEvents
|
||||
);
|
||||
|
||||
can(
|
||||
[
|
||||
ProjectPermissionAppConnectionActions.Create,
|
||||
ProjectPermissionAppConnectionActions.Edit,
|
||||
ProjectPermissionAppConnectionActions.Delete,
|
||||
ProjectPermissionAppConnectionActions.Read,
|
||||
ProjectPermissionAppConnectionActions.Connect
|
||||
],
|
||||
ProjectPermissionSub.AppConnections
|
||||
);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
@@ -477,6 +489,8 @@ const buildMemberPermissionRules = () => {
|
||||
ProjectPermissionSub.SecretEvents
|
||||
);
|
||||
|
||||
can(ProjectPermissionAppConnectionActions.Connect, ProjectPermissionSub.AppConnections);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
||||
@@ -147,6 +147,14 @@ export enum ProjectPermissionSecretScanningDataSourceActions {
|
||||
ReadResources = "read-data-source-resources"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionAppConnectionActions {
|
||||
Read = "read-app-connections",
|
||||
Create = "create-app-connections",
|
||||
Edit = "edit-app-connections",
|
||||
Delete = "delete-app-connections",
|
||||
Connect = "connect-app-connections"
|
||||
}
|
||||
|
||||
export enum ProjectPermissionSecretScanningFindingActions {
|
||||
Read = "read-findings",
|
||||
Update = "update-findings"
|
||||
@@ -208,7 +216,8 @@ export enum ProjectPermissionSub {
|
||||
SecretScanningDataSources = "secret-scanning-data-sources",
|
||||
SecretScanningFindings = "secret-scanning-findings",
|
||||
SecretScanningConfigs = "secret-scanning-configs",
|
||||
SecretEvents = "secret-events"
|
||||
SecretEvents = "secret-events",
|
||||
AppConnections = "app-connections"
|
||||
}
|
||||
|
||||
export type SecretSubjectFields = {
|
||||
@@ -272,6 +281,10 @@ export type PkiSubscriberSubjectFields = {
|
||||
// (dangtony98): consider adding [commonName] as a subject field in the future
|
||||
};
|
||||
|
||||
export type AppConnectionSubjectFields = {
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretActions,
|
||||
@@ -365,6 +378,13 @@ export type ProjectPermissionSet =
|
||||
| [
|
||||
ProjectPermissionSecretEventActions,
|
||||
ProjectPermissionSub.SecretEvents | (ForcedSubject<ProjectPermissionSub.SecretEvents> & SecretEventSubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionAppConnectionActions,
|
||||
(
|
||||
| ProjectPermissionSub.AppConnections
|
||||
| (ForcedSubject<ProjectPermissionSub.AppConnections> & AppConnectionSubjectFields)
|
||||
)
|
||||
];
|
||||
|
||||
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||
@@ -580,6 +600,21 @@ const PkiTemplateConditionSchema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
const AppConnectionConditionSchema = z
|
||||
.object({
|
||||
connectionId: z.union([
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||
})
|
||||
.partial()
|
||||
])
|
||||
})
|
||||
.partial();
|
||||
|
||||
const GeneralPermissionSchema = [
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||
@@ -760,6 +795,16 @@ const GeneralPermissionSchema = [
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretScanningConfigActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.AppConnections).describe("The entity this permission pertains to."),
|
||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionAppConnectionActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
),
|
||||
conditions: AppConnectionConditionSchema.describe(
|
||||
"When specified, only matching conditions will be allowed to access given resource."
|
||||
).optional()
|
||||
})
|
||||
];
|
||||
|
||||
|
||||
+2
-1
@@ -175,7 +175,8 @@ export const ldapPasswordRotationFactory: TRotationFactory<
|
||||
const encryptedCredentials = await encryptAppConnectionCredentials({
|
||||
credentials: updatedCredentials,
|
||||
orgId,
|
||||
kmsService
|
||||
kmsService,
|
||||
projectId: connection.projectId
|
||||
});
|
||||
|
||||
await appConnectionDAL.updateById(connection.id, { encryptedCredentials });
|
||||
|
||||
@@ -52,6 +52,7 @@ const baseSecretRotationV2Query = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -106,6 +107,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
connectionUpdatedAt,
|
||||
connectionVersion,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
...el
|
||||
} = secretRotation;
|
||||
@@ -126,6 +128,7 @@ const expandSecretRotation = <T extends Awaited<ReturnType<typeof baseSecretRota
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
||||
},
|
||||
folder: {
|
||||
|
||||
@@ -89,7 +89,7 @@ import { TSecretRotationV2DALFactory } from "./secret-rotation-v2-dal";
|
||||
|
||||
export type TSecretRotationV2ServiceFactoryDep = {
|
||||
secretRotationV2DAL: TSecretRotationV2DALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
@@ -459,7 +459,11 @@ export const secretRotationV2ServiceFactory = ({
|
||||
const typeApp = SECRET_ROTATION_CONNECTION_MAP[payload.type];
|
||||
|
||||
// validates permission to connect and app is valid for rotation type
|
||||
const connection = await appConnectionService.connectAppConnectionById(typeApp, payload.connectionId, actor);
|
||||
const connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
typeApp,
|
||||
{ connectionId: payload.connectionId, projectId },
|
||||
actor
|
||||
);
|
||||
|
||||
const rotationFactory = SECRET_ROTATION_FACTORY_MAP[payload.type](
|
||||
{
|
||||
|
||||
@@ -50,6 +50,7 @@ const baseSecretScanningDataSourceQuery = ({
|
||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||
db.ref("projectId").withSchema(TableName.AppConnection).as("connectionProjectId"),
|
||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||
db
|
||||
@@ -84,6 +85,7 @@ const expandSecretScanningDataSource = <
|
||||
connectionVersion,
|
||||
connectionIsPlatformManagedCredentials,
|
||||
connectionGatewayId,
|
||||
connectionProjectId,
|
||||
...el
|
||||
} = dataSource;
|
||||
|
||||
@@ -103,7 +105,8 @@ const expandSecretScanningDataSource = <
|
||||
updatedAt: connectionUpdatedAt,
|
||||
version: connectionVersion,
|
||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||
gatewayId: connectionGatewayId
|
||||
gatewayId: connectionGatewayId,
|
||||
projectId: connectionProjectId
|
||||
}
|
||||
: undefined
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue
|
||||
|
||||
export type TSecretScanningV2ServiceFactoryDep = {
|
||||
secretScanningV2DAL: TSecretScanningV2DALFactory;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "connectAppConnectionById">;
|
||||
appConnectionService: Pick<TAppConnectionServiceFactory, "validateAppConnectionUsageById">;
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
@@ -252,9 +252,9 @@ export const secretScanningV2ServiceFactory = ({
|
||||
let connection: TAppConnection | null = null;
|
||||
if (payload.connectionId) {
|
||||
// validates permission to connect and app is valid for data source
|
||||
connection = await appConnectionService.connectAppConnectionById(
|
||||
connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[payload.type],
|
||||
payload.connectionId,
|
||||
{ connectionId: payload.connectionId, projectId: payload.projectId },
|
||||
actor
|
||||
);
|
||||
}
|
||||
@@ -373,9 +373,9 @@ export const secretScanningV2ServiceFactory = ({
|
||||
let connection: TAppConnection | null = null;
|
||||
if (dataSource.connectionId) {
|
||||
// validates permission to connect and app is valid for data source
|
||||
connection = await appConnectionService.connectAppConnectionById(
|
||||
connection = await appConnectionService.validateAppConnectionUsageById(
|
||||
SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP[dataSource.type],
|
||||
dataSource.connectionId,
|
||||
{ connectionId: dataSource.connectionId, projectId: dataSource.projectId },
|
||||
actor
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user