mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 06:25:55 +00:00
feat(secret-scanning): BitBucket data source
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
|||||||
|
import { registerSecretScanningEndpoints } from "@app/ee/routes/v2/secret-scanning-v2-routers/secret-scanning-v2-endpoints";
|
||||||
|
import {
|
||||||
|
BitBucketDataSourceSchema,
|
||||||
|
CreateBitBucketDataSourceSchema,
|
||||||
|
UpdateBitBucketDataSourceSchema
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
|
||||||
|
export const registerBitBucketSecretScanningRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSecretScanningEndpoints({
|
||||||
|
type: SecretScanningDataSource.BitBucket,
|
||||||
|
server,
|
||||||
|
responseSchema: BitBucketDataSourceSchema,
|
||||||
|
createSchema: CreateBitBucketDataSourceSchema,
|
||||||
|
updateSchema: UpdateBitBucketDataSourceSchema
|
||||||
|
});
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
|
||||||
|
import { registerBitBucketSecretScanningRouter } from "./bitbucket-secret-scanning-router";
|
||||||
import { registerGitHubSecretScanningRouter } from "./github-secret-scanning-router";
|
import { registerGitHubSecretScanningRouter } from "./github-secret-scanning-router";
|
||||||
|
|
||||||
export * from "./secret-scanning-v2-router";
|
export * from "./secret-scanning-v2-router";
|
||||||
@@ -8,5 +9,6 @@ export const SECRET_SCANNING_REGISTER_ROUTER_MAP: Record<
|
|||||||
SecretScanningDataSource,
|
SecretScanningDataSource,
|
||||||
(server: FastifyZodProvider) => Promise<void>
|
(server: FastifyZodProvider) => Promise<void>
|
||||||
> = {
|
> = {
|
||||||
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter
|
[SecretScanningDataSource.GitHub]: registerGitHubSecretScanningRouter,
|
||||||
|
[SecretScanningDataSource.BitBucket]: registerBitBucketSecretScanningRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { SecretScanningConfigsSchema } from "@app/db/schemas";
|
import { SecretScanningConfigsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { BitBucketDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github";
|
import { GitHubDataSourceListItemSchema } from "@app/ee/services/secret-scanning-v2/github";
|
||||||
import {
|
import {
|
||||||
SecretScanningFindingStatus,
|
SecretScanningFindingStatus,
|
||||||
@@ -21,7 +22,10 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [GitHubDataSourceListItemSchema]);
|
const SecretScanningDataSourceOptionsSchema = z.discriminatedUnion("type", [
|
||||||
|
GitHubDataSourceListItemSchema,
|
||||||
|
BitBucketDataSourceListItemSchema
|
||||||
|
]);
|
||||||
|
|
||||||
export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => {
|
export const registerSecretScanningV2Router = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
import { TSecretScanningDataSourceListItem } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION: TSecretScanningDataSourceListItem = {
|
||||||
|
name: "BitBucket",
|
||||||
|
type: SecretScanningDataSource.BitBucket,
|
||||||
|
connection: AppConnection.BitBucket
|
||||||
|
};
|
||||||
+252
@@ -0,0 +1,252 @@
|
|||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
import { scanContentAndGetFindings } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns";
|
||||||
|
import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||||
|
import {
|
||||||
|
SecretScanningDataSource,
|
||||||
|
SecretScanningFindingSeverity,
|
||||||
|
SecretScanningResource
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
import {
|
||||||
|
cloneRepository,
|
||||||
|
convertPatchLineToFileLineNumber,
|
||||||
|
replaceNonChangesWithNewlines
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-fns";
|
||||||
|
import {
|
||||||
|
TSecretScanningFactoryGetDiffScanFindingsPayload,
|
||||||
|
TSecretScanningFactoryGetDiffScanResourcePayload,
|
||||||
|
TSecretScanningFactoryGetFullScanPath,
|
||||||
|
TSecretScanningFactoryInitialize,
|
||||||
|
TSecretScanningFactoryListRawResources,
|
||||||
|
TSecretScanningFactoryPostInitialization
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-types";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { titleCaseToCamelCase } from "@app/lib/fn";
|
||||||
|
import { GitHubRepositoryRegex } from "@app/lib/regex";
|
||||||
|
import {
|
||||||
|
getBitBucketUser,
|
||||||
|
listBitBucketRepositories,
|
||||||
|
TBitBucketConnection
|
||||||
|
} from "@app/services/app-connection/bitbucket";
|
||||||
|
|
||||||
|
import { TBitBucketDataSourceWithConnection, TQueueBitBucketResourceDiffScan } from "./bitbucket-secret-scanning-types";
|
||||||
|
|
||||||
|
export const BitBucketSecretScanningFactory = () => {
|
||||||
|
const initialize: TSecretScanningFactoryInitialize<TBitBucketConnection> = async (
|
||||||
|
{ connection, secretScanningV2DAL },
|
||||||
|
callback
|
||||||
|
) => {
|
||||||
|
// TODO(andrey): Swap for something proper
|
||||||
|
const externalId = connection.credentials.email;
|
||||||
|
|
||||||
|
const existingDataSource = await secretScanningV2DAL.dataSources.findOne({
|
||||||
|
externalId,
|
||||||
|
type: SecretScanningDataSource.BitBucket
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingDataSource)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `A Data Source already exists for this BitBucket Radar Connection in the Project with ID "${existingDataSource.projectId}"`
|
||||||
|
});
|
||||||
|
|
||||||
|
return callback({
|
||||||
|
externalId
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const postInitialization: TSecretScanningFactoryPostInitialization<TBitBucketConnection> = async () => {
|
||||||
|
// no post-initialization required
|
||||||
|
};
|
||||||
|
|
||||||
|
const listRawResources: TSecretScanningFactoryListRawResources<TBitBucketDataSourceWithConnection> = async (
|
||||||
|
dataSource
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
config: { includeRepos }
|
||||||
|
} = dataSource;
|
||||||
|
|
||||||
|
const repos = await listBitBucketRepositories(connection);
|
||||||
|
|
||||||
|
const filteredRepos: typeof repos = [];
|
||||||
|
if (includeRepos.includes("*")) {
|
||||||
|
filteredRepos.push(...repos);
|
||||||
|
} else {
|
||||||
|
filteredRepos.push(...repos.filter((repo) => includeRepos.includes(repo.full_name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredRepos.map(({ slug, full_name }) => ({
|
||||||
|
name: full_name,
|
||||||
|
externalId: slug.toString(),
|
||||||
|
type: SecretScanningResource.Repository
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(andrey): Finish
|
||||||
|
const getFullScanPath: TSecretScanningFactoryGetFullScanPath<TBitBucketDataSourceWithConnection> = async ({
|
||||||
|
dataSource,
|
||||||
|
resourceName,
|
||||||
|
tempFolder
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
connection: {
|
||||||
|
credentials: { apiToken, email }
|
||||||
|
}
|
||||||
|
} = dataSource;
|
||||||
|
|
||||||
|
const repoPath = join(tempFolder, "repo.git");
|
||||||
|
|
||||||
|
if (!GitHubRepositoryRegex.test(resourceName)) {
|
||||||
|
throw new Error("Invalid BitBucket repository name");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { username } = await getBitBucketUser({ email, apiToken });
|
||||||
|
|
||||||
|
await cloneRepository({
|
||||||
|
cloneUrl: `https://${encodeURIComponent(username)}:${apiToken}@bitbucket.org/${resourceName}.git`,
|
||||||
|
repoPath
|
||||||
|
});
|
||||||
|
|
||||||
|
return repoPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDiffScanResourcePayload: TSecretScanningFactoryGetDiffScanResourcePayload<
|
||||||
|
TQueueBitBucketResourceDiffScan["payload"]
|
||||||
|
> = ({ repository }) => {
|
||||||
|
return {
|
||||||
|
name: repository.full_name,
|
||||||
|
externalId: repository.id.toString(),
|
||||||
|
type: SecretScanningResource.Repository
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDiffScanFindingsPayload: TSecretScanningFactoryGetDiffScanFindingsPayload<
|
||||||
|
TBitBucketDataSourceWithConnection,
|
||||||
|
TQueueBitBucketResourceDiffScan["payload"]
|
||||||
|
> = async ({ dataSource, payload, resourceName, configPath }) => {
|
||||||
|
const {
|
||||||
|
connection: {
|
||||||
|
credentials: { apiToken, email }
|
||||||
|
}
|
||||||
|
} = dataSource;
|
||||||
|
|
||||||
|
console.log("getDiffScanFindingsPayload");
|
||||||
|
|
||||||
|
const { commits, repository } = payload;
|
||||||
|
|
||||||
|
const allFindings: SecretMatch[] = [];
|
||||||
|
|
||||||
|
const authHeader = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||||
|
|
||||||
|
for (const commit of commits) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: diffstat } = await request.get<{
|
||||||
|
values: {
|
||||||
|
status: "added" | "modified" | "removed" | "renamed";
|
||||||
|
new?: { path: string };
|
||||||
|
old?: { path: string };
|
||||||
|
}[];
|
||||||
|
}>(`https://api.bitbucket.org/2.0/repositories/${repository.full_name}/diffstat/${commit.id}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: authHeader,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!diffstat.values) continue;
|
||||||
|
|
||||||
|
for (const file of diffstat.values) {
|
||||||
|
if ((file.status === "added" || file.status === "modified") && file.new?.path) {
|
||||||
|
const filePath = file.new.path;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: patch } = await request.get<string>(
|
||||||
|
`https://api.bitbucket.org/2.0/repositories/${repository.full_name}/diff/${commit.id}`,
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
path: filePath
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
Authorization: authHeader
|
||||||
|
},
|
||||||
|
responseType: "text"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(1);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
if (!patch) continue;
|
||||||
|
console.log(2);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const findings = await scanContentAndGetFindings(replaceNonChangesWithNewlines(`\n${patch}`), configPath);
|
||||||
|
console.log(3);
|
||||||
|
console.log(findings);
|
||||||
|
|
||||||
|
const adjustedFindings = findings.map((finding) => {
|
||||||
|
const startLine = convertPatchLineToFileLineNumber(patch, finding.StartLine);
|
||||||
|
const endLine =
|
||||||
|
finding.StartLine === finding.EndLine
|
||||||
|
? startLine
|
||||||
|
: convertPatchLineToFileLineNumber(patch, finding.EndLine);
|
||||||
|
const startColumn = finding.StartColumn - 1; // subtract 1 for +
|
||||||
|
const endColumn = finding.EndColumn - 1; // subtract 1 for +
|
||||||
|
|
||||||
|
console.log("finding");
|
||||||
|
console.log(finding.Link);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...finding,
|
||||||
|
StartLine: startLine,
|
||||||
|
EndLine: endLine,
|
||||||
|
StartColumn: startColumn,
|
||||||
|
EndColumn: endColumn,
|
||||||
|
File: filePath,
|
||||||
|
Commit: commit.id,
|
||||||
|
Author: commit.author.name,
|
||||||
|
Email: commit.author.email ?? "",
|
||||||
|
Message: commit.message,
|
||||||
|
Fingerprint: `${commit.id}:${filePath}:${finding.RuleID}:${startLine}:${startColumn}`,
|
||||||
|
Date: commit.timestamp,
|
||||||
|
Link: `https://bitbucket.org/${resourceName}/src/${commit.id}/${filePath}#lines-${startLine}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("adjusted");
|
||||||
|
console.log(adjustedFindings);
|
||||||
|
|
||||||
|
allFindings.push(...adjustedFindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("HEREEE");
|
||||||
|
console.log(allFindings);
|
||||||
|
|
||||||
|
return allFindings.map(
|
||||||
|
({
|
||||||
|
// discard match and secret as we don't want to store
|
||||||
|
Match,
|
||||||
|
Secret,
|
||||||
|
...finding
|
||||||
|
}) => ({
|
||||||
|
details: titleCaseToCamelCase(finding),
|
||||||
|
fingerprint: finding.Fingerprint,
|
||||||
|
severity: SecretScanningFindingSeverity.High,
|
||||||
|
rule: finding.RuleID
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialize,
|
||||||
|
postInitialization,
|
||||||
|
listRawResources,
|
||||||
|
getFullScanPath,
|
||||||
|
getDiffScanResourcePayload,
|
||||||
|
getDiffScanFindingsPayload
|
||||||
|
};
|
||||||
|
};
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SecretScanningDataSource,
|
||||||
|
SecretScanningResource
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
import {
|
||||||
|
BaseCreateSecretScanningDataSourceSchema,
|
||||||
|
BaseSecretScanningDataSourceSchema,
|
||||||
|
BaseSecretScanningFindingSchema,
|
||||||
|
BaseUpdateSecretScanningDataSourceSchema,
|
||||||
|
GitRepositoryScanFindingDetailsSchema
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-schemas";
|
||||||
|
import { SecretScanningDataSources } from "@app/lib/api-docs";
|
||||||
|
import { GitHubRepositoryRegex } from "@app/lib/regex";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
export const BitBucketDataSourceConfigSchema = z.object({
|
||||||
|
includeRepos: z
|
||||||
|
.array(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(256)
|
||||||
|
.refine((value) => value === "*" || GitHubRepositoryRegex.test(value), "Invalid repository name format")
|
||||||
|
)
|
||||||
|
.nonempty("One or more repositories required")
|
||||||
|
.max(100, "Cannot configure more than 100 repositories")
|
||||||
|
.default(["*"])
|
||||||
|
.describe(SecretScanningDataSources.CONFIG.BITBUCKET.includeRepos)
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BitBucketDataSourceSchema = BaseSecretScanningDataSourceSchema({
|
||||||
|
type: SecretScanningDataSource.BitBucket,
|
||||||
|
isConnectionRequired: true
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
config: BitBucketDataSourceConfigSchema
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
JSON.stringify({
|
||||||
|
title: "BitBucket"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const CreateBitBucketDataSourceSchema = BaseCreateSecretScanningDataSourceSchema({
|
||||||
|
type: SecretScanningDataSource.BitBucket,
|
||||||
|
isConnectionRequired: true
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
config: BitBucketDataSourceConfigSchema
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
JSON.stringify({
|
||||||
|
title: "BitBucket"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UpdateBitBucketDataSourceSchema = BaseUpdateSecretScanningDataSourceSchema(
|
||||||
|
SecretScanningDataSource.BitBucket
|
||||||
|
)
|
||||||
|
.extend({
|
||||||
|
config: BitBucketDataSourceConfigSchema.optional()
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
JSON.stringify({
|
||||||
|
title: "BitBucket"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BitBucketDataSourceListItemSchema = z
|
||||||
|
.object({
|
||||||
|
name: z.literal("BitBucket"),
|
||||||
|
connection: z.literal(AppConnection.BitBucket),
|
||||||
|
type: z.literal(SecretScanningDataSource.BitBucket)
|
||||||
|
})
|
||||||
|
.describe(
|
||||||
|
JSON.stringify({
|
||||||
|
title: "BitBucket"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const BitBucketFindingSchema = BaseSecretScanningFindingSchema.extend({
|
||||||
|
resourceType: z.literal(SecretScanningResource.Repository),
|
||||||
|
dataSourceType: z.literal(SecretScanningDataSource.BitBucket),
|
||||||
|
details: GitRepositoryScanFindingDetailsSchema
|
||||||
|
});
|
||||||
+87
@@ -0,0 +1,87 @@
|
|||||||
|
import { PushEvent } from "@octokit/webhooks-types";
|
||||||
|
|
||||||
|
import { TSecretScanningV2DALFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-dal";
|
||||||
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
import { TSecretScanningV2QueueServiceFactory } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-queue";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
|
||||||
|
import { TBitBucketDataSource } from "./bitbucket-secret-scanning-types";
|
||||||
|
|
||||||
|
export const bitBucketSecretScanningService = (
|
||||||
|
secretScanningV2DAL: TSecretScanningV2DALFactory,
|
||||||
|
secretScanningV2Queue: Pick<TSecretScanningV2QueueServiceFactory, "queueResourceDiffScan">
|
||||||
|
) => {
|
||||||
|
const handleInstallationDeletedEvent = async (installationId: number) => {
|
||||||
|
const dataSource = await secretScanningV2DAL.dataSources.findOne({
|
||||||
|
externalId: String(installationId),
|
||||||
|
type: SecretScanningDataSource.BitBucket
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dataSource) {
|
||||||
|
logger.error(
|
||||||
|
`secretScanningV2RemoveEvent: BitBucket - Could not find data source [installationId=${installationId}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`secretScanningV2RemoveEvent: BitBucket - installation deleted [installationId=${installationId}] [dataSourceId=${dataSource.id}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
await secretScanningV2DAL.dataSources.updateById(dataSource.id, {
|
||||||
|
isDisconnected: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePushEvent = async (payload: PushEvent) => {
|
||||||
|
const { commits, repository, installation } = payload;
|
||||||
|
|
||||||
|
if (!commits || !repository || !installation) {
|
||||||
|
logger.warn(
|
||||||
|
`secretScanningV2PushEvent: BitBucket - Insufficient data [commits=${commits?.length ?? 0}] [repository=${repository.name}] [installationId=${installation?.id}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataSource = (await secretScanningV2DAL.dataSources.findOne({
|
||||||
|
externalId: String(installation.id),
|
||||||
|
type: SecretScanningDataSource.BitBucket
|
||||||
|
})) as TBitBucketDataSource | undefined;
|
||||||
|
|
||||||
|
if (!dataSource) {
|
||||||
|
logger.error(
|
||||||
|
`secretScanningV2PushEvent: BitBucket - Could not find data source [installationId=${installation.id}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
isAutoScanEnabled,
|
||||||
|
config: { includeRepos }
|
||||||
|
} = dataSource;
|
||||||
|
|
||||||
|
if (!isAutoScanEnabled) {
|
||||||
|
logger.info(
|
||||||
|
`secretScanningV2PushEvent: BitBucket - ignoring due to auto scan disabled [dataSourceId=${dataSource.id}] [installationId=${installation.id}]`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeRepos.includes("*") || includeRepos.includes(repository.full_name)) {
|
||||||
|
await secretScanningV2Queue.queueResourceDiffScan({
|
||||||
|
dataSourceType: SecretScanningDataSource.BitBucket,
|
||||||
|
payload,
|
||||||
|
dataSourceId: dataSource.id
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.info(
|
||||||
|
`secretScanningV2PushEvent: BitBucket - ignoring due to repository not being present in config [installationId=${installation.id}] [dataSourceId=${dataSource.id}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePushEvent,
|
||||||
|
handleInstallationDeletedEvent
|
||||||
|
};
|
||||||
|
};
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import { PushEvent } from "@octokit/webhooks-types";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
import { TBitBucketConnection } from "@app/services/app-connection/bitbucket";
|
||||||
|
|
||||||
|
import {
|
||||||
|
BitBucketDataSourceListItemSchema,
|
||||||
|
BitBucketDataSourceSchema,
|
||||||
|
BitBucketFindingSchema,
|
||||||
|
CreateBitBucketDataSourceSchema
|
||||||
|
} from "./bitbucket-secret-scanning-schemas";
|
||||||
|
|
||||||
|
export type TBitBucketDataSource = z.infer<typeof BitBucketDataSourceSchema>;
|
||||||
|
|
||||||
|
export type TBitBucketDataSourceInput = z.infer<typeof CreateBitBucketDataSourceSchema>;
|
||||||
|
|
||||||
|
export type TBitBucketDataSourceListItem = z.infer<typeof BitBucketDataSourceListItemSchema>;
|
||||||
|
|
||||||
|
export type TBitBucketFinding = z.infer<typeof BitBucketFindingSchema>;
|
||||||
|
|
||||||
|
export type TBitBucketDataSourceWithConnection = TBitBucketDataSource & {
|
||||||
|
connection: TBitBucketConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TQueueBitBucketResourceDiffScan = {
|
||||||
|
dataSourceType: SecretScanningDataSource.BitBucket;
|
||||||
|
payload: PushEvent;
|
||||||
|
dataSourceId: string;
|
||||||
|
resourceId: string;
|
||||||
|
scanId: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./bitbucket-secret-scanning-constants";
|
||||||
|
export * from "./bitbucket-secret-scanning-schemas";
|
||||||
|
export * from "./bitbucket-secret-scanning-types";
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum SecretScanningDataSource {
|
export enum SecretScanningDataSource {
|
||||||
GitHub = "github"
|
GitHub = "github",
|
||||||
|
BitBucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretScanningScanStatus {
|
export enum SecretScanningScanStatus {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { BitBucketSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/bitbucket/bitbucket-secret-scanning-factory";
|
||||||
import { GitHubSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/github/github-secret-scanning-factory";
|
import { GitHubSecretScanningFactory } from "@app/ee/services/secret-scanning-v2/github/github-secret-scanning-factory";
|
||||||
|
|
||||||
import { SecretScanningDataSource } from "./secret-scanning-v2-enums";
|
import { SecretScanningDataSource } from "./secret-scanning-v2-enums";
|
||||||
@@ -15,5 +16,6 @@ type TSecretScanningFactoryImplementation = TSecretScanningFactory<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
export const SECRET_SCANNING_FACTORY_MAP: Record<SecretScanningDataSource, TSecretScanningFactoryImplementation> = {
|
export const SECRET_SCANNING_FACTORY_MAP: Record<SecretScanningDataSource, TSecretScanningFactoryImplementation> = {
|
||||||
[SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation
|
[SecretScanningDataSource.GitHub]: GitHubSecretScanningFactory as TSecretScanningFactoryImplementation,
|
||||||
|
[SecretScanningDataSource.BitBucket]: BitBucketSecretScanningFactory as TSecretScanningFactoryImplementation
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import RE2 from "re2";
|
|||||||
|
|
||||||
import { readFindingsFile } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns";
|
import { readFindingsFile } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-fns";
|
||||||
import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
import { SecretMatch } from "@app/ee/services/secret-scanning/secret-scanning-queue/secret-scanning-queue-types";
|
||||||
|
import { BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
import { GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/github";
|
import { GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION } from "@app/ee/services/secret-scanning-v2/github";
|
||||||
import { titleCaseToCamelCase } from "@app/lib/fn";
|
import { titleCaseToCamelCase } from "@app/lib/fn";
|
||||||
|
|
||||||
@@ -11,7 +12,8 @@ import { SecretScanningDataSource, SecretScanningFindingSeverity } from "./secre
|
|||||||
import { TCloneRepository, TGetFindingsPayload, TSecretScanningDataSourceListItem } from "./secret-scanning-v2-types";
|
import { TCloneRepository, TGetFindingsPayload, TSecretScanningDataSourceListItem } from "./secret-scanning-v2-types";
|
||||||
|
|
||||||
const SECRET_SCANNING_SOURCE_LIST_OPTIONS: Record<SecretScanningDataSource, TSecretScanningDataSourceListItem> = {
|
const SECRET_SCANNING_SOURCE_LIST_OPTIONS: Record<SecretScanningDataSource, TSecretScanningDataSourceListItem> = {
|
||||||
[SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION
|
[SecretScanningDataSource.GitHub]: GITHUB_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION,
|
||||||
|
[SecretScanningDataSource.BitBucket]: BITBUCKET_SECRET_SCANNING_DATA_SOURCE_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretScanningDataSourceOptions = () => {
|
export const listSecretScanningDataSourceOptions = () => {
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import { SecretScanningDataSource } from "@app/ee/services/secret-scanning-v2/se
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
export const SECRET_SCANNING_DATA_SOURCE_NAME_MAP: Record<SecretScanningDataSource, string> = {
|
export const SECRET_SCANNING_DATA_SOURCE_NAME_MAP: Record<SecretScanningDataSource, string> = {
|
||||||
[SecretScanningDataSource.GitHub]: "GitHub"
|
[SecretScanningDataSource.GitHub]: "GitHub",
|
||||||
|
[SecretScanningDataSource.BitBucket]: "BitBucket"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record<SecretScanningDataSource, AppConnection> = {
|
export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record<SecretScanningDataSource, AppConnection> = {
|
||||||
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar
|
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar,
|
||||||
|
[SecretScanningDataSource.BitBucket]: AppConnection.BitBucket
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AUTO_SYNC_DESCRIPTION_HELPER: Record<SecretScanningDataSource, { verb: string; noun: string }> = {
|
export const AUTO_SYNC_DESCRIPTION_HELPER: Record<SecretScanningDataSource, { verb: string; noun: string }> = {
|
||||||
[SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" }
|
[SecretScanningDataSource.GitHub]: { verb: "push", noun: "repositories" },
|
||||||
|
// TODO(andrey): May need change
|
||||||
|
[SecretScanningDataSource.BitBucket]: { verb: "push", noun: "repositories" }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import { TAppConnection } from "@app/services/app-connection/app-connection-type
|
|||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { bitBucketSecretScanningService } from "./bitbucket/bitbucket-secret-scanning-service";
|
||||||
import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal";
|
import { TSecretScanningV2DALFactory } from "./secret-scanning-v2-dal";
|
||||||
import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue";
|
import { TSecretScanningV2QueueServiceFactory } from "./secret-scanning-v2-queue";
|
||||||
|
|
||||||
@@ -869,6 +870,7 @@ export const secretScanningV2ServiceFactory = ({
|
|||||||
updateSecretScanningFindingById,
|
updateSecretScanningFindingById,
|
||||||
findSecretScanningConfigByProjectId,
|
findSecretScanningConfigByProjectId,
|
||||||
upsertSecretScanningConfig,
|
upsertSecretScanningConfig,
|
||||||
github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue)
|
github: githubSecretScanningService(secretScanningV2DAL, secretScanningV2Queue),
|
||||||
|
bitbucket: bitBucketSecretScanningService(secretScanningV2DAL, secretScanningV2Queue)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,14 @@ import {
|
|||||||
TSecretScanningResources,
|
TSecretScanningResources,
|
||||||
TSecretScanningScans
|
TSecretScanningScans
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TBitBucketDataSource,
|
||||||
|
TBitBucketDataSourceInput,
|
||||||
|
TBitBucketDataSourceListItem,
|
||||||
|
TBitBucketDataSourceWithConnection,
|
||||||
|
TBitBucketFinding,
|
||||||
|
TQueueBitBucketResourceDiffScan
|
||||||
|
} from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
import {
|
import {
|
||||||
TGitHubDataSource,
|
TGitHubDataSource,
|
||||||
TGitHubDataSourceInput,
|
TGitHubDataSourceInput,
|
||||||
@@ -19,7 +27,7 @@ import {
|
|||||||
SecretScanningScanStatus
|
SecretScanningScanStatus
|
||||||
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
} from "@app/ee/services/secret-scanning-v2/secret-scanning-v2-enums";
|
||||||
|
|
||||||
export type TSecretScanningDataSource = TGitHubDataSource;
|
export type TSecretScanningDataSource = TGitHubDataSource | TBitBucketDataSource;
|
||||||
|
|
||||||
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
||||||
lastScannedAt?: Date | null;
|
lastScannedAt?: Date | null;
|
||||||
@@ -41,13 +49,15 @@ export type TSecretScanningScanWithDetails = TSecretScanningScans & {
|
|||||||
resourceName: string;
|
resourceName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretScanningDataSourceWithConnection = TGitHubDataSourceWithConnection;
|
export type TSecretScanningDataSourceWithConnection =
|
||||||
|
| TGitHubDataSourceWithConnection
|
||||||
|
| TBitBucketDataSourceWithConnection;
|
||||||
|
|
||||||
export type TSecretScanningDataSourceInput = TGitHubDataSourceInput;
|
export type TSecretScanningDataSourceInput = TGitHubDataSourceInput | TBitBucketDataSourceInput;
|
||||||
|
|
||||||
export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem;
|
export type TSecretScanningDataSourceListItem = TGitHubDataSourceListItem | TBitBucketDataSourceListItem;
|
||||||
|
|
||||||
export type TSecretScanningFinding = TGitHubFinding;
|
export type TSecretScanningFinding = TGitHubFinding | TBitBucketFinding;
|
||||||
|
|
||||||
export type TListSecretScanningDataSourcesByProjectId = {
|
export type TListSecretScanningDataSourcesByProjectId = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -99,7 +109,7 @@ export type TQueueSecretScanningDataSourceFullScan = {
|
|||||||
scanId: string;
|
scanId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan;
|
export type TQueueSecretScanningResourceDiffScan = TQueueGitHubResourceDiffScan | TQueueBitBucketResourceDiffScan;
|
||||||
|
|
||||||
export type TQueueSecretScanningSendNotification = {
|
export type TQueueSecretScanningSendNotification = {
|
||||||
dataSource: TSecretScanningDataSources;
|
dataSource: TSecretScanningDataSources;
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BitBucketDataSourceSchema, BitBucketFindingSchema } from "@app/ee/services/secret-scanning-v2/bitbucket";
|
||||||
import { GitHubDataSourceSchema, GitHubFindingSchema } from "@app/ee/services/secret-scanning-v2/github";
|
import { GitHubDataSourceSchema, GitHubFindingSchema } from "@app/ee/services/secret-scanning-v2/github";
|
||||||
|
|
||||||
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [GitHubDataSourceSchema]);
|
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [
|
||||||
|
GitHubDataSourceSchema,
|
||||||
|
BitBucketDataSourceSchema
|
||||||
|
]);
|
||||||
|
|
||||||
export const SecretScanningFindingSchema = z.discriminatedUnion("resourceType", [GitHubFindingSchema]);
|
export const SecretScanningFindingSchema = z.discriminatedUnion("dataSourceType", [
|
||||||
|
GitHubFindingSchema,
|
||||||
|
BitBucketFindingSchema
|
||||||
|
]);
|
||||||
|
|||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import { MultiValue } from "react-select";
|
||||||
|
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { FilterableSelect, FormControl, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
TBitBucketRepo,
|
||||||
|
useBitBucketConnectionListRepositories
|
||||||
|
} from "@app/hooks/api/appConnections/bitbucket";
|
||||||
|
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
|
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||||
|
import { SecretScanningDataSourceConnectionField } from "../SecretScanningDataSourceConnectionField";
|
||||||
|
|
||||||
|
enum ScanMethod {
|
||||||
|
AllRepositories = "all-repositories",
|
||||||
|
SelectRepositories = "select-repositories"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BitBucketDataSourceConfigFields = () => {
|
||||||
|
const { control, watch, setValue } = useFormContext<
|
||||||
|
TSecretScanningDataSourceForm & {
|
||||||
|
type: SecretScanningDataSource.BitBucket;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ control, name: "connection.id" });
|
||||||
|
const isUpdate = Boolean(watch("id"));
|
||||||
|
|
||||||
|
const { data: repositories, isPending: areRepositoriesLoading } =
|
||||||
|
useBitBucketConnectionListRepositories(connectionId, { enabled: Boolean(connectionId) });
|
||||||
|
|
||||||
|
const includeRepos = watch("config.includeRepos");
|
||||||
|
|
||||||
|
const scanMethod =
|
||||||
|
!includeRepos || includeRepos[0] === "*"
|
||||||
|
? ScanMethod.AllRepositories
|
||||||
|
: ScanMethod.SelectRepositories;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!includeRepos) {
|
||||||
|
setValue("config.includeRepos", ["*"]);
|
||||||
|
}
|
||||||
|
}, [includeRepos, setValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretScanningDataSourceConnectionField
|
||||||
|
isUpdate={isUpdate}
|
||||||
|
onChange={() => {
|
||||||
|
if (scanMethod === ScanMethod.SelectRepositories) {
|
||||||
|
setValue("config.includeRepos", []);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormControl label="Scan Repositories">
|
||||||
|
<Select
|
||||||
|
value={scanMethod}
|
||||||
|
onValueChange={(val) => {
|
||||||
|
setValue("config.includeRepos", val === ScanMethod.AllRepositories ? ["*"] : []);
|
||||||
|
}}
|
||||||
|
className="w-full border border-mineshaft-500 capitalize"
|
||||||
|
position="popper"
|
||||||
|
dropdownContainerClassName="max-w-none"
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
>
|
||||||
|
{Object.values(ScanMethod).map((method) => {
|
||||||
|
return (
|
||||||
|
<SelectItem className="capitalize" value={method} key={method}>
|
||||||
|
{method.replace("-", " ")}
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{scanMethod === ScanMethod.SelectRepositories && (
|
||||||
|
<Controller
|
||||||
|
name="config.includeRepos"
|
||||||
|
defaultValue={["*"]}
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Include Repositories"
|
||||||
|
helperText={
|
||||||
|
<Tooltip
|
||||||
|
className="max-w-md"
|
||||||
|
content={<>Ensure that your connection has the correct permissions.</>}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span>Don't see the repository you're looking for?</span>{" "}
|
||||||
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
menuPlacement="top"
|
||||||
|
isLoading={areRepositoriesLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
isMulti
|
||||||
|
value={repositories?.filter((repository) => value.includes(repository.name))}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
onChange(
|
||||||
|
newValue ? (newValue as MultiValue<TBitBucketRepo>).map((p) => p.name) : null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={repositories}
|
||||||
|
placeholder="Select repositories..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.name}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
-1
@@ -5,10 +5,12 @@ import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2";
|
|||||||
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||||
|
import { BitBucketDataSourceConfigFields } from "./BitBucketDataSourceConfigFields";
|
||||||
import { GitHubDataSourceConfigFields } from "./GitHubDataSourceConfigFields";
|
import { GitHubDataSourceConfigFields } from "./GitHubDataSourceConfigFields";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
||||||
[SecretScanningDataSource.GitHub]: GitHubDataSourceConfigFields
|
[SecretScanningDataSource.GitHub]: GitHubDataSourceConfigFields,
|
||||||
|
[SecretScanningDataSource.BitBucket]: BitBucketDataSourceConfigFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretScanningDataSourceConfigFields = () => {
|
export const SecretScanningDataSourceConfigFields = () => {
|
||||||
|
|||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
|
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||||
|
import { SecretScanningDataSourceConfigReviewSection } from "./shared";
|
||||||
|
|
||||||
|
export const BitBucketDataSourceReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<
|
||||||
|
TSecretScanningDataSourceForm & {
|
||||||
|
type: SecretScanningDataSource.BitBucket;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
const [{ includeRepos }, connection] = watch(["config", "connection"]);
|
||||||
|
const shouldScanAll = includeRepos[0] === "*";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SecretScanningDataSourceConfigReviewSection>
|
||||||
|
{connection && <GenericFieldLabel label="Connection">{connection.name}</GenericFieldLabel>}
|
||||||
|
<GenericFieldLabel label="Scan Repositories">
|
||||||
|
{shouldScanAll ? "All" : includeRepos.join(", ")}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
</SecretScanningDataSourceConfigReviewSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
-1
@@ -4,10 +4,12 @@ import { GenericFieldLabel } from "@app/components/v2";
|
|||||||
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||||
|
import { BitBucketDataSourceReviewFields } from "./BitBucketDataSourceReviewFields";
|
||||||
import { GitHubDataSourceReviewFields } from "./GitHubDataSourceReviewFields";
|
import { GitHubDataSourceReviewFields } from "./GitHubDataSourceReviewFields";
|
||||||
|
|
||||||
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
||||||
[SecretScanningDataSource.GitHub]: GitHubDataSourceReviewFields
|
[SecretScanningDataSource.GitHub]: GitHubDataSourceReviewFields,
|
||||||
|
[SecretScanningDataSource.BitBucket]: BitBucketDataSourceReviewFields
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretScanningDataSourceReviewFields = () => {
|
export const SecretScanningDataSourceReviewFields = () => {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
|
import { BaseSecretScanningDataSourceSchema } from "./base-secret-scanning-data-source-schema";
|
||||||
|
|
||||||
|
export const BitBucketDataSourceSchema = z
|
||||||
|
.object({
|
||||||
|
type: z.literal(SecretScanningDataSource.BitBucket),
|
||||||
|
config: z.object({
|
||||||
|
includeRepos: z
|
||||||
|
.string()
|
||||||
|
.array()
|
||||||
|
.min(1, "One or more repositories required")
|
||||||
|
.max(100, "Cannot configure more than 100 repositories")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.merge(BaseSecretScanningDataSourceSchema({ isConnectionRequired: true }));
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BitBucketDataSourceSchema } from "./bitbucket-data-source-schema";
|
||||||
import { GitHubDataSourceSchema } from "./github-data-source-schema";
|
import { GitHubDataSourceSchema } from "./github-data-source-schema";
|
||||||
|
|
||||||
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [
|
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [
|
||||||
GitHubDataSourceSchema
|
GitHubDataSourceSchema,
|
||||||
|
BitBucketDataSourceSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TSecretScanningDataSourceForm = z.infer<typeof SecretScanningDataSourceSchema>;
|
export type TSecretScanningDataSourceForm = z.infer<typeof SecretScanningDataSourceSchema>;
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ export const SECRET_SCANNING_DATA_SOURCE_MAP: Record<
|
|||||||
name: "GitHub",
|
name: "GitHub",
|
||||||
image: "GitHub.png",
|
image: "GitHub.png",
|
||||||
size: 45
|
size: 45
|
||||||
|
},
|
||||||
|
[SecretScanningDataSource.BitBucket]: {
|
||||||
|
name: "BitBucket",
|
||||||
|
image: "BitBucket.png",
|
||||||
|
size: 45
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +31,8 @@ export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record<
|
|||||||
SecretScanningDataSource,
|
SecretScanningDataSource,
|
||||||
AppConnection
|
AppConnection
|
||||||
> = {
|
> = {
|
||||||
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar
|
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar,
|
||||||
|
[SecretScanningDataSource.BitBucket]: AppConnection.BitBucket
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RESOURCE_DESCRIPTION_HELPER: Record<
|
export const RESOURCE_DESCRIPTION_HELPER: Record<
|
||||||
@@ -45,6 +51,13 @@ export const RESOURCE_DESCRIPTION_HELPER: Record<
|
|||||||
singularNoun: "repository",
|
singularNoun: "repository",
|
||||||
pluralTitle: "Repositories",
|
pluralTitle: "Repositories",
|
||||||
singularTitle: "Repository"
|
singularTitle: "Repository"
|
||||||
|
},
|
||||||
|
[SecretScanningDataSource.BitBucket]: {
|
||||||
|
verb: "push",
|
||||||
|
pluralNoun: "repositories",
|
||||||
|
singularNoun: "repository",
|
||||||
|
pluralTitle: "Repositories",
|
||||||
|
singularTitle: "Repository"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum SecretScanningDataSource {
|
export enum SecretScanningDataSource {
|
||||||
GitHub = "github"
|
GitHub = "github",
|
||||||
|
BitBucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretScanningScanStatus {
|
export enum SecretScanningScanStatus {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
|
||||||
|
import { SecretScanningDataSource } from "../enums";
|
||||||
|
import { TSecretScanningDataSourceBase } from "./shared";
|
||||||
|
|
||||||
|
export type TBitBucketDataSource = TSecretScanningDataSourceBase & {
|
||||||
|
type: SecretScanningDataSource.BitBucket;
|
||||||
|
config: {
|
||||||
|
includeRepos: string[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitBucketDataSourceOption = {
|
||||||
|
name: string;
|
||||||
|
type: SecretScanningDataSource.BitBucket;
|
||||||
|
connection: AppConnection.BitBucket;
|
||||||
|
};
|
||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
SecretScanningScanStatus,
|
SecretScanningScanStatus,
|
||||||
SecretScanningScanType
|
SecretScanningScanType
|
||||||
} from "../enums";
|
} from "../enums";
|
||||||
|
import { TBitBucketDataSource, TBitBucketDataSourceOption } from "./bitbucket-data-source";
|
||||||
import { TGitHubDataSource, TGitHubDataSourceOption } from "./github-data-source";
|
import { TGitHubDataSource, TGitHubDataSourceOption } from "./github-data-source";
|
||||||
|
|
||||||
export type TSecretScanningDataSource = TGitHubDataSource;
|
export type TSecretScanningDataSource = TGitHubDataSource | TBitBucketDataSource;
|
||||||
|
|
||||||
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
||||||
lastScannedAt: string | null;
|
lastScannedAt: string | null;
|
||||||
@@ -23,7 +24,7 @@ export type TListSecretScanningDataSources = {
|
|||||||
dataSources: TSecretScanningDataSourceWithDetails[];
|
dataSources: TSecretScanningDataSourceWithDetails[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretScanningDataSourceOption = TGitHubDataSourceOption;
|
export type TSecretScanningDataSourceOption = TGitHubDataSourceOption | TBitBucketDataSourceOption;
|
||||||
|
|
||||||
export type TListSecretScanningDataSourceOptions = {
|
export type TListSecretScanningDataSourceOptions = {
|
||||||
dataSourceOptions: TSecretScanningDataSourceOption[];
|
dataSourceOptions: TSecretScanningDataSourceOption[];
|
||||||
|
|||||||
@@ -1,41 +1,37 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { BreadcrumbTypes } from '@app/components/v2'
|
import { BreadcrumbTypes } from "@app/components/v2";
|
||||||
import { workspaceKeys } from '@app/hooks/api'
|
import { workspaceKeys } from "@app/hooks/api";
|
||||||
import {
|
import { fetchUserProjectPermissions, roleQueryKeys } from "@app/hooks/api/roles/queries";
|
||||||
fetchUserProjectPermissions,
|
import { fetchWorkspaceById } from "@app/hooks/api/workspace/queries";
|
||||||
roleQueryKeys,
|
import { ProjectLayout } from "@app/layouts/ProjectLayout";
|
||||||
} from '@app/hooks/api/roles/queries'
|
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
|
||||||
import { fetchWorkspaceById } from '@app/hooks/api/workspace/queries'
|
|
||||||
import { ProjectLayout } from '@app/layouts/ProjectLayout'
|
|
||||||
import { ProjectSelect } from '@app/layouts/ProjectLayout/components/ProjectSelect'
|
|
||||||
|
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout',
|
"/_authenticate/_inject-org-details/_org-layout/projects/$projectId/_project-layout"
|
||||||
)({
|
)({
|
||||||
component: ProjectLayout,
|
component: ProjectLayout,
|
||||||
beforeLoad: async ({ params, context }) => {
|
beforeLoad: async ({ params, context }) => {
|
||||||
const project = await context.queryClient.ensureQueryData({
|
const project = await context.queryClient.ensureQueryData({
|
||||||
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
queryKey: workspaceKeys.getWorkspaceById(params.projectId),
|
||||||
queryFn: () => fetchWorkspaceById(params.projectId),
|
queryFn: () => fetchWorkspaceById(params.projectId)
|
||||||
})
|
});
|
||||||
|
|
||||||
await context.queryClient.ensureQueryData({
|
await context.queryClient.ensureQueryData({
|
||||||
queryKey: roleQueryKeys.getUserProjectPermissions({
|
queryKey: roleQueryKeys.getUserProjectPermissions({
|
||||||
workspaceId: params.projectId,
|
workspaceId: params.projectId
|
||||||
}),
|
}),
|
||||||
queryFn: () =>
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: params.projectId })
|
||||||
fetchUserProjectPermissions({ workspaceId: params.projectId }),
|
});
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
project,
|
project,
|
||||||
breadcrumbs: [
|
breadcrumbs: [
|
||||||
{
|
{
|
||||||
type: BreadcrumbTypes.Component,
|
type: BreadcrumbTypes.Component,
|
||||||
component: ProjectSelect,
|
component: ProjectSelect
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
}
|
};
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
|
|
||||||
// this is done as part of migration for multi product inside project
|
// this is done as part of migration for multi product inside project
|
||||||
export const Route = createFileRoute(
|
export const Route = createFileRoute(
|
||||||
'/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/approval',
|
"/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId/approval"
|
||||||
)({
|
)({
|
||||||
beforeLoad: ({ params, search }) => {
|
beforeLoad: ({ params, search }) => {
|
||||||
throw redirect({
|
throw redirect({
|
||||||
to: '/projects/$projectId/secret-manager/approval',
|
to: "/projects/$projectId/secret-manager/approval",
|
||||||
params,
|
params,
|
||||||
search,
|
search
|
||||||
})
|
});
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { TBitBucketDataSource } from "@app/hooks/api/secretScanningV2/types/bitbucket-data-source";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
dataSource: TBitBucketDataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitBucketDataSourceConfigDisplay = ({ dataSource }: Props) => {
|
||||||
|
const {
|
||||||
|
config: { includeRepos }
|
||||||
|
} = dataSource;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GenericFieldLabel label="Scan Repositories">
|
||||||
|
{includeRepos.includes("*") ? "All" : includeRepos.join(", ")}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
);
|
||||||
|
};
|
||||||
+3
@@ -4,6 +4,7 @@ import {
|
|||||||
} from "@app/hooks/api/secretScanningV2";
|
} from "@app/hooks/api/secretScanningV2";
|
||||||
|
|
||||||
import { GitHubDataSourceConfigDisplay } from "./GitHubDataSourceConfigDisplay";
|
import { GitHubDataSourceConfigDisplay } from "./GitHubDataSourceConfigDisplay";
|
||||||
|
import { BitBucketDataSourceConfigDisplay } from "./BitBucketDataSourceConfigDisplay";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
dataSource: TSecretScanningDataSource;
|
dataSource: TSecretScanningDataSource;
|
||||||
@@ -13,6 +14,8 @@ export const DataSourceConfigDisplay = ({ dataSource }: Props) => {
|
|||||||
switch (dataSource.type) {
|
switch (dataSource.type) {
|
||||||
case SecretScanningDataSource.GitHub:
|
case SecretScanningDataSource.GitHub:
|
||||||
return <GitHubDataSourceConfigDisplay dataSource={dataSource} />;
|
return <GitHubDataSourceConfigDisplay dataSource={dataSource} />;
|
||||||
|
case SecretScanningDataSource.BitBucket:
|
||||||
|
return <BitBucketDataSourceConfigDisplay dataSource={dataSource} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled dataSource type ${(dataSource as TSecretScanningDataSource).type}`
|
`Unhandled dataSource type ${(dataSource as TSecretScanningDataSource).type}`
|
||||||
|
|||||||
Reference in New Issue
Block a user