improvement: add teardown functionality to scanning factory and update generic types

This commit is contained in:
Scott Wilson
2025-07-03 17:28:52 -07:00
parent 3b767a4deb
commit b4593a2e11
5 changed files with 105 additions and 44 deletions
@@ -17,7 +17,8 @@ import {
TSecretScanningFactoryGetFullScanPath, TSecretScanningFactoryGetFullScanPath,
TSecretScanningFactoryInitialize, TSecretScanningFactoryInitialize,
TSecretScanningFactoryListRawResources, TSecretScanningFactoryListRawResources,
TSecretScanningFactoryPostInitialization TSecretScanningFactoryPostInitialization,
TSecretScanningFactoryTeardown
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
@@ -30,13 +31,19 @@ import {
} from "@app/services/app-connection/bitbucket"; } from "@app/services/app-connection/bitbucket";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { TBitbucketDataSourceWithConnection, TQueueBitbucketResourceDiffScan } from "./bitbucket-secret-scanning-types"; import {
TBitbucketDataSourceCredentials,
TBitbucketDataSourceInput,
TBitbucketDataSourceWithConnection,
TQueueBitbucketResourceDiffScan
} from "./bitbucket-secret-scanning-types";
export const BitbucketSecretScanningFactory = () => { export const BitbucketSecretScanningFactory = () => {
const initialize: TSecretScanningFactoryInitialize<TBitbucketConnection> = async ( const initialize: TSecretScanningFactoryInitialize<
{ connection, payload }, TBitbucketDataSourceInput,
callback TBitbucketConnection,
) => { TBitbucketDataSourceCredentials
> = async ({ connection, payload }, callback) => {
const { email, apiToken } = connection.credentials; const { email, apiToken } = connection.credentials;
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`; const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
@@ -61,12 +68,11 @@ export const BitbucketSecretScanningFactory = () => {
}); });
}; };
const postInitialization: TSecretScanningFactoryPostInitialization<TBitbucketConnection> = async ({ const postInitialization: TSecretScanningFactoryPostInitialization<
dataSourceId, TBitbucketDataSourceInput,
credentials, TBitbucketConnection,
connection, TBitbucketDataSourceCredentials
payload > = async ({ dataSourceId, credentials, connection, payload }) => {
}) => {
const { email, apiToken } = connection.credentials; const { email, apiToken } = connection.credentials;
const { webhookId } = credentials; const { webhookId } = credentials;
@@ -92,15 +98,22 @@ export const BitbucketSecretScanningFactory = () => {
); );
}; };
// TODO(andrey): Hook this up const teardown: TSecretScanningFactoryTeardown<
const postDeletion: any = async ({ credentials, connection, payload }) => { TBitbucketDataSourceWithConnection,
const { email, apiToken } = connection.credentials; TBitbucketDataSourceCredentials
> = async ({ credentials, dataSource }) => {
const {
connection: {
credentials: { email, apiToken }
},
config
} = dataSource;
const { webhookId } = credentials; const { webhookId } = credentials;
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`; const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
await request.delete( await request.delete(
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces/${payload.config.workspaceSlug}/hooks/${webhookId}`, `${IntegrationUrls.BITBUCKET_API_URL}/2.0/workspaces/${config.workspaceSlug}/hooks/${webhookId}`,
{ {
headers: { headers: {
Authorization: authHeader, Authorization: authHeader,
@@ -286,6 +299,7 @@ export const BitbucketSecretScanningFactory = () => {
listRawResources, listRawResources,
getFullScanPath, getFullScanPath,
getDiffScanResourcePayload, getDiffScanResourcePayload,
getDiffScanFindingsPayload getDiffScanFindingsPayload,
teardown
}; };
}; };
@@ -19,7 +19,8 @@ import {
TSecretScanningFactoryGetFullScanPath, TSecretScanningFactoryGetFullScanPath,
TSecretScanningFactoryInitialize, TSecretScanningFactoryInitialize,
TSecretScanningFactoryListRawResources, TSecretScanningFactoryListRawResources,
TSecretScanningFactoryPostInitialization TSecretScanningFactoryPostInitialization,
TSecretScanningFactoryTeardown
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types"; } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
@@ -27,10 +28,14 @@ import { titleCaseToCamelCase } from "@app/lib/fn";
import { GitHubRepositoryRegex } from "@app/lib/regex"; import { GitHubRepositoryRegex } from "@app/lib/regex";
import { listGitHubRadarRepositories, TGitHubRadarConnection } from "@app/services/app-connection/github-radar"; import { listGitHubRadarRepositories, TGitHubRadarConnection } from "@app/services/app-connection/github-radar";
import { TGitHubDataSourceWithConnection, TQueueGitHubResourceDiffScan } from "./github-secret-scanning-types"; import {
TGitHubDataSourceInput,
TGitHubDataSourceWithConnection,
TQueueGitHubResourceDiffScan
} from "./github-secret-scanning-types";
export const GitHubSecretScanningFactory = () => { export const GitHubSecretScanningFactory = () => {
const initialize: TSecretScanningFactoryInitialize<TGitHubRadarConnection> = async ( const initialize: TSecretScanningFactoryInitialize<TGitHubDataSourceInput, TGitHubRadarConnection> = async (
{ connection, secretScanningV2DAL }, { connection, secretScanningV2DAL },
callback callback
) => { ) => {
@@ -51,10 +56,17 @@ export const GitHubSecretScanningFactory = () => {
}); });
}; };
const postInitialization: TSecretScanningFactoryPostInitialization<TGitHubRadarConnection> = async () => { const postInitialization: TSecretScanningFactoryPostInitialization<
TGitHubDataSourceInput,
TGitHubRadarConnection
> = async () => {
// no post-initialization required // no post-initialization required
}; };
const teardown: TSecretScanningFactoryTeardown<TGitHubDataSourceWithConnection> = async () => {
// no termination required
};
const listRawResources: TSecretScanningFactoryListRawResources<TGitHubDataSourceWithConnection> = async ( const listRawResources: TSecretScanningFactoryListRawResources<TGitHubDataSourceWithConnection> = async (
dataSource dataSource
) => { ) => {
@@ -225,6 +237,7 @@ export const GitHubSecretScanningFactory = () => {
listRawResources, listRawResources,
getFullScanPath, getFullScanPath,
getDiffScanResourcePayload, getDiffScanResourcePayload,
getDiffScanFindingsPayload getDiffScanFindingsPayload,
teardown
}; };
}; };
@@ -5,14 +5,16 @@ import { SecretScanningDataSource } from "./secret-scanning-v2-enums";
import { import {
TQueueSecretScanningResourceDiffScan, TQueueSecretScanningResourceDiffScan,
TSecretScanningDataSourceCredentials, TSecretScanningDataSourceCredentials,
TSecretScanningDataSourceInput,
TSecretScanningDataSourceWithConnection, TSecretScanningDataSourceWithConnection,
TSecretScanningFactory TSecretScanningFactory
} from "./secret-scanning-v2-types"; } from "./secret-scanning-v2-types";
type TSecretScanningFactoryImplementation = TSecretScanningFactory< type TSecretScanningFactoryImplementation = TSecretScanningFactory<
TSecretScanningDataSourceWithConnection, TSecretScanningDataSourceWithConnection,
TSecretScanningDataSourceCredentials, TQueueSecretScanningResourceDiffScan["payload"],
TQueueSecretScanningResourceDiffScan["payload"] TSecretScanningDataSourceInput,
TSecretScanningDataSourceCredentials
>; >;
export const SECRET_SCANNING_FACTORY_MAP: Record<SecretScanningDataSource, TSecretScanningFactoryImplementation> = { export const SECRET_SCANNING_FACTORY_MAP: Record<SecretScanningDataSource, TSecretScanningFactoryImplementation> = {
@@ -30,6 +30,8 @@ import {
TFindSecretScanningDataSourceByNameDTO, TFindSecretScanningDataSourceByNameDTO,
TListSecretScanningDataSourcesByProjectId, TListSecretScanningDataSourcesByProjectId,
TSecretScanningDataSource, TSecretScanningDataSource,
TSecretScanningDataSourceCredentials,
TSecretScanningDataSourceInput,
TSecretScanningDataSourceWithConnection, TSecretScanningDataSourceWithConnection,
TSecretScanningDataSourceWithDetails, TSecretScanningDataSourceWithDetails,
TSecretScanningFinding, TSecretScanningFinding,
@@ -257,7 +259,7 @@ export const secretScanningV2ServiceFactory = ({
try { try {
const createdDataSource = await factory.initialize( const createdDataSource = await factory.initialize(
{ {
payload, payload: payload as TSecretScanningDataSourceInput,
connection: connection as TSecretScanningDataSourceWithConnection["connection"], connection: connection as TSecretScanningDataSourceWithConnection["connection"],
secretScanningV2DAL secretScanningV2DAL
}, },
@@ -288,7 +290,7 @@ export const secretScanningV2ServiceFactory = ({
); );
await factory.postInitialization({ await factory.postInitialization({
payload, payload: payload as TSecretScanningDataSourceInput,
connection: connection as TSecretScanningDataSourceWithConnection["connection"], connection: connection as TSecretScanningDataSourceWithConnection["connection"],
dataSourceId: dataSource.id, dataSourceId: dataSource.id,
credentials credentials
@@ -399,7 +401,6 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -413,7 +414,36 @@ export const secretScanningV2ServiceFactory = ({
message: `Secret Scanning Data Source with ID "${dataSourceId}" is not configured for ${SECRET_SCANNING_DATA_SOURCE_NAME_MAP[type]}` message: `Secret Scanning Data Source with ID "${dataSourceId}" is not configured for ${SECRET_SCANNING_DATA_SOURCE_NAME_MAP[type]}`
}); });
// TODO: clean up webhooks const factory = SECRET_SCANNING_FACTORY_MAP[type]();
let connection: TAppConnection | null = null;
if (dataSource.connection) {
connection = await decryptAppConnection(dataSource.connection, kmsService);
}
let credentials: TSecretScanningDataSourceCredentials | undefined;
if (dataSource.encryptedCredentials) {
const { decryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
projectId: dataSource.projectId
});
credentials = JSON.parse(
decryptor({
cipherTextBlob: dataSource.encryptedCredentials
}).toString()
) as TSecretScanningDataSourceCredentials;
}
await factory.teardown({
dataSource: {
...dataSource,
// @ts-expect-error currently we don't have a null connection data source
connection
},
credentials
});
await secretScanningV2DAL.dataSources.deleteById(dataSourceId); await secretScanningV2DAL.dataSources.deleteById(dataSourceId);
@@ -58,7 +58,7 @@ export type TSecretScanningDataSourceInput = TGitHubDataSourceInput | TBitbucket
export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem | TBitbucketDataSourceListItem; export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem | TBitbucketDataSourceListItem;
export type TDataSourceCredentialsSchema = TBitbucketDataSourceCredentials | undefined; export type TSecretScanningDataSourceCredentials = TBitbucketDataSourceCredentials | undefined;
export type TSecretScanningFinding = TGitHubFinding | TBitbucketFinding; export type TSecretScanningFinding = TGitHubFinding | TBitbucketFinding;
@@ -151,11 +151,12 @@ export type TSecretScanningDataSourceRaw = NonNullable<
>; >;
export type TSecretScanningFactoryInitialize< export type TSecretScanningFactoryInitialize<
P extends TSecretScanningDataSourceInput,
T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined, T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined,
C extends TSecretScanningDataSourceCredentials = TDataSourceCredentialsSchema C extends TSecretScanningDataSourceCredentials = undefined
> = ( > = (
params: { params: {
payload: TCreateSecretScanningDataSourceDTO; payload: P;
connection: T; connection: T;
secretScanningV2DAL: TSecretScanningV2DALFactory; secretScanningV2DAL: TSecretScanningV2DALFactory;
}, },
@@ -163,24 +164,27 @@ export type TSecretScanningFactoryInitialize<
) => Promise<TSecretScanningDataSourceRaw>; ) => Promise<TSecretScanningDataSourceRaw>;
export type TSecretScanningFactoryPostInitialization< export type TSecretScanningFactoryPostInitialization<
P extends TSecretScanningDataSourceInput,
T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined, T extends TSecretScanningDataSourceWithConnection["connection"] | undefined = undefined,
C extends TSecretScanningDataSourceCredentials = TDataSourceCredentialsSchema C extends TSecretScanningDataSourceCredentials = undefined
> = (params: { > = (params: { payload: P; connection: T; credentials: C; dataSourceId: string }) => Promise<void>;
payload: TCreateSecretScanningDataSourceDTO;
connection: T; export type TSecretScanningFactoryTeardown<
credentials: C; T extends TSecretScanningDataSourceWithConnection,
dataSourceId: string; C extends TSecretScanningDataSourceCredentials = undefined
}) => Promise<void>; > = (params: { dataSource: T; credentials: C }) => Promise<void>;
export type TSecretScanningFactory< export type TSecretScanningFactory<
T extends TSecretScanningDataSourceWithConnection, T extends TSecretScanningDataSourceWithConnection,
C extends TSecretScanningDataSourceCredentials, P extends TQueueSecretScanningResourceDiffScan["payload"],
P extends TQueueSecretScanningResourceDiffScan["payload"] I extends TSecretScanningDataSourceInput,
C extends TSecretScanningDataSourceCredentials | undefined = undefined
> = () => { > = () => {
listRawResources: TSecretScanningFactoryListRawResources<T>; listRawResources: TSecretScanningFactoryListRawResources<T>;
getFullScanPath: TSecretScanningFactoryGetFullScanPath<T>; getFullScanPath: TSecretScanningFactoryGetFullScanPath<T>;
initialize: TSecretScanningFactoryInitialize<T["connection"] | undefined, C>; initialize: TSecretScanningFactoryInitialize<I, T["connection"] | undefined, C>;
postInitialization: TSecretScanningFactoryPostInitialization<T["connection"] | undefined, C>; postInitialization: TSecretScanningFactoryPostInitialization<I, T["connection"] | undefined, C>;
teardown: TSecretScanningFactoryTeardown<T, C>;
getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload<P>; getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload<P>;
getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload<T, P>; getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload<T, P>;
}; };
@@ -198,5 +202,3 @@ export type TUpsertSecretScanningConfigDTO = {
projectId: string; projectId: string;
content: string | null; content: string | null;
}; };
export type TSecretScanningDataSourceCredentials = TDataSourceCredentialsSchema;