mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 06:29:25 +00:00
feature: gitlab secret scanning
This commit is contained in:
+274
@@ -0,0 +1,274 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { MultiValue, SingleValue } 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 {
|
||||
TGitLabGroup,
|
||||
TGitLabProject,
|
||||
useGitLabConnectionListGroups,
|
||||
useGitLabConnectionListProjects
|
||||
} from "@app/hooks/api/appConnections/gitlab";
|
||||
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { GitLabDataSourceScope } from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||
import { SecretScanningDataSourceConnectionField } from "../SecretScanningDataSourceConnectionField";
|
||||
|
||||
enum ScanMethod {
|
||||
AllProjects = "all-projects",
|
||||
SelectProjects = "select-projects"
|
||||
}
|
||||
|
||||
export const GitLabDataSourceConfigFields = () => {
|
||||
const { control, watch, setValue, formState } = useFormContext<
|
||||
TSecretScanningDataSourceForm & {
|
||||
type: SecretScanningDataSource.GitLab;
|
||||
}
|
||||
>();
|
||||
|
||||
console.log(formState);
|
||||
|
||||
const connectionId = useWatch({ control, name: "connection.id" });
|
||||
const isUpdate = Boolean(watch("id"));
|
||||
|
||||
const scope = watch("config.scope");
|
||||
const groupName = watch("config.groupName");
|
||||
const includeProjects = watch("config.includeProjects");
|
||||
|
||||
const { data: projects, isPending: isProjectsPending } = useGitLabConnectionListProjects(
|
||||
connectionId,
|
||||
{ enabled: Boolean(connectionId) }
|
||||
);
|
||||
|
||||
const { data: groups, isPending: isGroupsPending } = useGitLabConnectionListGroups(connectionId, {
|
||||
enabled: Boolean(connectionId) && scope === GitLabDataSourceScope.Group
|
||||
});
|
||||
|
||||
const scanMethod =
|
||||
!includeProjects || includeProjects.includes("*")
|
||||
? ScanMethod.AllProjects
|
||||
: ScanMethod.SelectProjects;
|
||||
|
||||
useEffect(() => {
|
||||
if (!includeProjects) {
|
||||
setValue("config.includeProjects", ["*"]);
|
||||
}
|
||||
}, [includeProjects]);
|
||||
|
||||
const clearAllFields = () => {
|
||||
setValue("config.includeProjects", []);
|
||||
setValue("config.projectName", "");
|
||||
setValue("config.groupName", "");
|
||||
// @ts-expect-error rhf doesn't like this but we need to reset
|
||||
setValue("config.projectId", undefined);
|
||||
// @ts-expect-error rhf doesn't like this but we need to reset
|
||||
setValue("config.groupId", undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretScanningDataSourceConnectionField isUpdate={isUpdate} onChange={clearAllFields} />
|
||||
<Controller
|
||||
name="config.scope"
|
||||
control={control}
|
||||
defaultValue={GitLabDataSourceScope.Project}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Scope"
|
||||
helperText={isUpdate ? "Cannot be updated" : undefined}
|
||||
tooltipText={
|
||||
<div className="flex flex-col gap-3">
|
||||
<p>Specify the GitLab scope scanning should be performed at:</p>
|
||||
<ul className="flex list-disc flex-col gap-3 pl-4">
|
||||
<li>
|
||||
<p className="text-mineshaft-300">
|
||||
<span className="font-medium text-bunker-200">Project</span>: Scan an
|
||||
individual GitLab project.
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p className="text-mineshaft-300">
|
||||
<span className="font-medium text-bunker-200">Group</span>: Scan one or more
|
||||
projects belonging to a GitLab group.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Select
|
||||
value={value}
|
||||
onValueChange={(v) => {
|
||||
onChange(v);
|
||||
clearAllFields();
|
||||
}}
|
||||
className="w-full border border-mineshaft-500 capitalize"
|
||||
position="popper"
|
||||
isDisabled={isUpdate}
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{Object.values(GitLabDataSourceScope).map((method) => {
|
||||
return (
|
||||
<SelectItem className="capitalize" value={method} key={method}>
|
||||
{method}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{scope === GitLabDataSourceScope.Project ? (
|
||||
<Controller
|
||||
name="config.projectId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Project"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={<>Ensure that your connection has the correct permissions.</>}
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the project you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isProjectsPending && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={projects?.find((project) => value === Number.parseInt(project.id, 10))}
|
||||
onChange={(newValue) => {
|
||||
const project = newValue as SingleValue<TGitLabProject>;
|
||||
|
||||
onChange(project ? Number.parseInt(project.id, 10) : null);
|
||||
setValue("config.projectName", project?.name ?? "");
|
||||
}}
|
||||
options={projects}
|
||||
placeholder="Select project..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Controller
|
||||
name="config.groupId"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Group"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={<>Ensure that your connection has the correct permissions.</>}
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the group you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
isLoading={isGroupsPending && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={groups?.find((group) => value === Number.parseInt(group.id, 10))}
|
||||
onChange={(newValue) => {
|
||||
const group = newValue as SingleValue<TGitLabGroup>;
|
||||
|
||||
onChange(group ? Number.parseInt(group.id, 10) : null);
|
||||
setValue("config.groupName", group?.name ?? "");
|
||||
setValue("config.includeProjects", ["*"]);
|
||||
}}
|
||||
options={groups}
|
||||
placeholder="Select group..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<FormControl label="Scan Projects">
|
||||
<Select
|
||||
value={scanMethod}
|
||||
onValueChange={(val) => {
|
||||
setValue("config.includeProjects", val === ScanMethod.AllProjects ? ["*"] : []);
|
||||
}}
|
||||
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.SelectProjects && (
|
||||
<Controller
|
||||
name="config.includeProjects"
|
||||
defaultValue={["*"]}
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="Include Projects"
|
||||
helperText={
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={<>Ensure that your connection has the correct permissions.</>}
|
||||
>
|
||||
<div>
|
||||
<span>Don't see the project you're looking for?</span>{" "}
|
||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isProjectsPending && Boolean(connectionId)}
|
||||
isDisabled={!connectionId || !groupName}
|
||||
isMulti
|
||||
value={projects?.filter((project) => value.includes(project.name))}
|
||||
onChange={(newValue) => {
|
||||
onChange(
|
||||
newValue
|
||||
? (newValue as MultiValue<TGitLabProject>).map((p) => p.name)
|
||||
: null
|
||||
);
|
||||
}}
|
||||
options={projects?.filter((project) => project.name.startsWith(groupName))}
|
||||
placeholder="Select projects..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-1
@@ -7,10 +7,12 @@ import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||
import { BitbucketDataSourceConfigFields } from "./BitbucketDataSourceConfigFields";
|
||||
import { GitHubDataSourceConfigFields } from "./GitHubDataSourceConfigFields";
|
||||
import { GitLabDataSourceConfigFields } from "./GitLabDataSourceConfigFields";
|
||||
|
||||
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
||||
[SecretScanningDataSource.GitHub]: GitHubDataSourceConfigFields,
|
||||
[SecretScanningDataSource.Bitbucket]: BitbucketDataSourceConfigFields
|
||||
[SecretScanningDataSource.Bitbucket]: BitbucketDataSourceConfigFields,
|
||||
[SecretScanningDataSource.GitLab]: GitLabDataSourceConfigFields
|
||||
};
|
||||
|
||||
export const SecretScanningDataSourceConfigFields = () => {
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { GenericFieldLabel } from "@app/components/v2";
|
||||
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { GitLabDataSourceScope } from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||
import { SecretScanningDataSourceConfigReviewSection } from "./shared";
|
||||
|
||||
export const GitLabDataSourceReviewFields = () => {
|
||||
const { watch } = useFormContext<
|
||||
TSecretScanningDataSourceForm & {
|
||||
type: SecretScanningDataSource.GitLab;
|
||||
}
|
||||
>();
|
||||
|
||||
const [config, connection] = watch(["config", "connection"]);
|
||||
|
||||
if (config.scope === GitLabDataSourceScope.Project) {
|
||||
const { projectName, projectId } = config;
|
||||
return (
|
||||
<SecretScanningDataSourceConfigReviewSection>
|
||||
{connection && <GenericFieldLabel label="Connection">{connection.name}</GenericFieldLabel>}
|
||||
<GenericFieldLabel label="Scope" className="capitalize">
|
||||
{config.scope}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Project">{projectName || projectId}</GenericFieldLabel>
|
||||
</SecretScanningDataSourceConfigReviewSection>
|
||||
);
|
||||
}
|
||||
|
||||
// group-scope
|
||||
|
||||
const { includeProjects, groupName, groupId } = config;
|
||||
const shouldScanAll = includeProjects.includes("*");
|
||||
|
||||
return (
|
||||
<SecretScanningDataSourceConfigReviewSection>
|
||||
{connection && <GenericFieldLabel label="Connection">{connection.name}</GenericFieldLabel>}
|
||||
<GenericFieldLabel label="Scope" className="capitalize">
|
||||
{config.scope}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Group">{groupName || groupId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Scan Projects">
|
||||
{shouldScanAll ? "All" : includeProjects.join(", ")}
|
||||
</GenericFieldLabel>
|
||||
</SecretScanningDataSourceConfigReviewSection>
|
||||
);
|
||||
};
|
||||
+3
-1
@@ -6,10 +6,12 @@ import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { TSecretScanningDataSourceForm } from "../schemas";
|
||||
import { BitbucketDataSourceReviewFields } from "./BitbucketDataSourceReviewFields";
|
||||
import { GitHubDataSourceReviewFields } from "./GitHubDataSourceReviewFields";
|
||||
import { GitLabDataSourceReviewFields } from "./GitLabDataSourceReviewFields";
|
||||
|
||||
const COMPONENT_MAP: Record<SecretScanningDataSource, React.FC> = {
|
||||
[SecretScanningDataSource.GitHub]: GitHubDataSourceReviewFields,
|
||||
[SecretScanningDataSource.Bitbucket]: BitbucketDataSourceReviewFields
|
||||
[SecretScanningDataSource.Bitbucket]: BitbucketDataSourceReviewFields,
|
||||
[SecretScanningDataSource.GitLab]: GitLabDataSourceReviewFields
|
||||
};
|
||||
|
||||
export const SecretScanningDataSourceReviewFields = () => {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { SecretScanningDataSource } from "@app/hooks/api/secretScanningV2";
|
||||
import { GitLabDataSourceScope } from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
import { BaseSecretScanningDataSourceSchema } from "./base-secret-scanning-data-source-schema";
|
||||
|
||||
export const GitLabDataSourceSchema = z
|
||||
.object({
|
||||
type: z.literal(SecretScanningDataSource.GitLab),
|
||||
config: z.discriminatedUnion("scope", [
|
||||
z.object({
|
||||
scope: z.literal(GitLabDataSourceScope.Group),
|
||||
groupId: z.number(),
|
||||
groupName: z.string(),
|
||||
includeProjects: z
|
||||
.array(z.string().min(1).max(256))
|
||||
.min(1, "One or more projects required")
|
||||
.max(100, "Cannot configure more than 100 projects")
|
||||
.default(["*"])
|
||||
}),
|
||||
z.object({
|
||||
scope: z.literal(GitLabDataSourceScope.Project),
|
||||
projectId: z.number(),
|
||||
projectName: z.string()
|
||||
})
|
||||
])
|
||||
})
|
||||
.merge(BaseSecretScanningDataSourceSchema({ isConnectionRequired: true }));
|
||||
@@ -2,10 +2,12 @@ import { z } from "zod";
|
||||
|
||||
import { BitbucketDataSourceSchema } from "./bitbucket-data-source-schema";
|
||||
import { GitHubDataSourceSchema } from "./github-data-source-schema";
|
||||
import { GitLabDataSourceSchema } from "./gitlab-data-source-schema";
|
||||
|
||||
export const SecretScanningDataSourceSchema = z.discriminatedUnion("type", [
|
||||
GitHubDataSourceSchema,
|
||||
BitbucketDataSourceSchema
|
||||
BitbucketDataSourceSchema,
|
||||
GitLabDataSourceSchema
|
||||
]);
|
||||
|
||||
export type TSecretScanningDataSourceForm = z.infer<typeof SecretScanningDataSourceSchema>;
|
||||
|
||||
+4
-4
@@ -16,8 +16,8 @@ import {
|
||||
import {
|
||||
TGitLabGroup,
|
||||
TGitLabProject,
|
||||
useGitlabConnectionListGroups,
|
||||
useGitlabConnectionListProjects
|
||||
useGitLabConnectionListGroups,
|
||||
useGitLabConnectionListProjects
|
||||
} from "@app/hooks/api/appConnections/gitlab";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { GitLabSyncScope } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
||||
@@ -70,11 +70,11 @@ export const GitLabSyncFields = () => {
|
||||
const scope = useWatch({ name: "destinationConfig.scope", control });
|
||||
const shouldMaskSecrets = useWatch({ name: "destinationConfig.shouldMaskSecrets", control });
|
||||
|
||||
const { data: groups, isLoading: isGroupsLoading } = useGitlabConnectionListGroups(connectionId, {
|
||||
const { data: groups, isLoading: isGroupsLoading } = useGitLabConnectionListGroups(connectionId, {
|
||||
enabled: Boolean(connectionId) && scope === GitLabSyncScope.Group
|
||||
});
|
||||
|
||||
const { data: projects, isLoading: isProjectsLoading } = useGitlabConnectionListProjects(
|
||||
const { data: projects, isLoading: isProjectsLoading } = useGitLabConnectionListProjects(
|
||||
connectionId,
|
||||
{
|
||||
enabled: Boolean(connectionId)
|
||||
|
||||
@@ -94,7 +94,7 @@ export const APP_CONNECTION_MAP: Record<
|
||||
[AppConnection.Heroku]: { name: "Heroku", image: "Heroku.png" },
|
||||
[AppConnection.Render]: { name: "Render", image: "Render.png" },
|
||||
[AppConnection.Flyio]: { name: "Fly.io", image: "Flyio.svg" },
|
||||
[AppConnection.Gitlab]: { name: "GitLab", image: "GitLab.png" },
|
||||
[AppConnection.GitLab]: { name: "GitLab", image: "GitLab.png" },
|
||||
[AppConnection.Cloudflare]: { name: "Cloudflare", image: "Cloudflare.png" },
|
||||
[AppConnection.Zabbix]: { name: "Zabbix", image: "Zabbix.png" },
|
||||
[AppConnection.Railway]: { name: "Railway", image: "Railway.png" },
|
||||
|
||||
@@ -24,6 +24,11 @@ export const SECRET_SCANNING_DATA_SOURCE_MAP: Record<
|
||||
name: "Bitbucket",
|
||||
image: "Bitbucket.png",
|
||||
size: 45
|
||||
},
|
||||
[SecretScanningDataSource.GitLab]: {
|
||||
name: "GitLab",
|
||||
image: "GitLab.png",
|
||||
size: 45
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,7 +37,8 @@ export const SECRET_SCANNING_DATA_SOURCE_CONNECTION_MAP: Record<
|
||||
AppConnection
|
||||
> = {
|
||||
[SecretScanningDataSource.GitHub]: AppConnection.GitHubRadar,
|
||||
[SecretScanningDataSource.Bitbucket]: AppConnection.Bitbucket
|
||||
[SecretScanningDataSource.Bitbucket]: AppConnection.Bitbucket,
|
||||
[SecretScanningDataSource.GitLab]: AppConnection.GitLab
|
||||
};
|
||||
|
||||
export const RESOURCE_DESCRIPTION_HELPER: Record<
|
||||
@@ -58,6 +64,13 @@ export const RESOURCE_DESCRIPTION_HELPER: Record<
|
||||
singularNoun: "repository",
|
||||
pluralTitle: "Repositories",
|
||||
singularTitle: "Repository"
|
||||
},
|
||||
[SecretScanningDataSource.GitLab]: {
|
||||
verb: "push",
|
||||
pluralNoun: "projects",
|
||||
singularNoun: "project",
|
||||
pluralTitle: "Projects",
|
||||
singularTitle: "project"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[SecretSync.Heroku]: AppConnection.Heroku,
|
||||
[SecretSync.Render]: AppConnection.Render,
|
||||
[SecretSync.Flyio]: AppConnection.Flyio,
|
||||
[SecretSync.GitLab]: AppConnection.Gitlab,
|
||||
[SecretSync.GitLab]: AppConnection.GitLab,
|
||||
[SecretSync.CloudflarePages]: AppConnection.Cloudflare,
|
||||
[SecretSync.CloudflareWorkers]: AppConnection.Cloudflare,
|
||||
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||
|
||||
@@ -26,7 +26,7 @@ export enum AppConnection {
|
||||
Heroku = "heroku",
|
||||
Render = "render",
|
||||
Flyio = "flyio",
|
||||
Gitlab = "gitlab",
|
||||
GitLab = "gitlab",
|
||||
Cloudflare = "cloudflare",
|
||||
Bitbucket = "bitbucket",
|
||||
Zabbix = "zabbix",
|
||||
|
||||
@@ -13,7 +13,7 @@ const gitlabConnectionKeys = {
|
||||
[...gitlabConnectionKeys.all, "groups", connectionId] as const
|
||||
};
|
||||
|
||||
export const useGitlabConnectionListProjects = (
|
||||
export const useGitLabConnectionListProjects = (
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
@@ -38,7 +38,7 @@ export const useGitlabConnectionListProjects = (
|
||||
});
|
||||
};
|
||||
|
||||
export const useGitlabConnectionListGroups = (
|
||||
export const useGitLabConnectionListGroups = (
|
||||
connectionId: string,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
|
||||
@@ -124,7 +124,7 @@ export type TFlyioConnectionOption = TAppConnectionOptionBase & {
|
||||
};
|
||||
|
||||
export type TGitlabConnectionOption = TAppConnectionOptionBase & {
|
||||
app: AppConnection.Gitlab;
|
||||
app: AppConnection.GitLab;
|
||||
oauthClientId?: string;
|
||||
};
|
||||
|
||||
@@ -223,7 +223,7 @@ export type TAppConnectionOptionMap = {
|
||||
[AppConnection.Heroku]: THerokuConnectionOption;
|
||||
[AppConnection.Render]: TRenderConnectionOption;
|
||||
[AppConnection.Flyio]: TFlyioConnectionOption;
|
||||
[AppConnection.Gitlab]: TGitlabConnectionOption;
|
||||
[AppConnection.GitLab]: TGitlabConnectionOption;
|
||||
[AppConnection.Cloudflare]: TCloudflareConnectionOption;
|
||||
[AppConnection.Bitbucket]: TBitbucketConnectionOption;
|
||||
[AppConnection.Zabbix]: TZabbixConnectionOption;
|
||||
|
||||
@@ -8,7 +8,7 @@ export enum GitLabConnectionMethod {
|
||||
OAuth = "oauth"
|
||||
}
|
||||
|
||||
export type TGitLabConnection = TRootAppConnection & { app: AppConnection.Gitlab } & (
|
||||
export type TGitLabConnection = TRootAppConnection & { app: AppConnection.GitLab } & (
|
||||
| {
|
||||
method: GitLabConnectionMethod.AccessToken;
|
||||
credentials: {
|
||||
|
||||
@@ -173,7 +173,7 @@ export type TAppConnectionMap = {
|
||||
[AppConnection.Heroku]: THerokuConnection;
|
||||
[AppConnection.Render]: TRenderConnection;
|
||||
[AppConnection.Flyio]: TFlyioConnection;
|
||||
[AppConnection.Gitlab]: TGitLabConnection;
|
||||
[AppConnection.GitLab]: TGitLabConnection;
|
||||
[AppConnection.Cloudflare]: TCloudflareConnection;
|
||||
[AppConnection.Bitbucket]: TBitbucketConnection;
|
||||
[AppConnection.Zabbix]: TZabbixConnection;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export enum SecretScanningDataSource {
|
||||
GitHub = "github",
|
||||
Bitbucket = "bitbucket"
|
||||
Bitbucket = "bitbucket",
|
||||
GitLab = "gitlab"
|
||||
}
|
||||
|
||||
export enum SecretScanningScanStatus {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
|
||||
import { SecretScanningDataSource } from "../enums";
|
||||
import { TSecretScanningDataSourceBase } from "./shared";
|
||||
|
||||
export enum GitLabDataSourceScope {
|
||||
Project = "project",
|
||||
Group = "group"
|
||||
}
|
||||
|
||||
export type TGitLabDataSource = TSecretScanningDataSourceBase & {
|
||||
type: SecretScanningDataSource.GitLab;
|
||||
config:
|
||||
| {
|
||||
groupId: number;
|
||||
groupName?: string;
|
||||
includeProjects: string[];
|
||||
scope: GitLabDataSourceScope.Group;
|
||||
}
|
||||
| {
|
||||
projectName?: string;
|
||||
projectId: number;
|
||||
scope: GitLabDataSourceScope.Project;
|
||||
};
|
||||
};
|
||||
|
||||
export type TGitLabDataSourceOption = {
|
||||
name: string;
|
||||
type: SecretScanningDataSource.GitLab;
|
||||
connection: AppConnection.GitLab;
|
||||
};
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
TGitLabDataSource,
|
||||
TGitLabDataSourceOption
|
||||
} from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import {
|
||||
@@ -11,7 +15,10 @@ import {
|
||||
import { TBitbucketDataSource, TBitbucketDataSourceOption } from "./bitbucket-data-source";
|
||||
import { TGitHubDataSource, TGitHubDataSourceOption } from "./github-data-source";
|
||||
|
||||
export type TSecretScanningDataSource = TGitHubDataSource | TBitbucketDataSource;
|
||||
export type TSecretScanningDataSource =
|
||||
| TGitHubDataSource
|
||||
| TBitbucketDataSource
|
||||
| TGitLabDataSource;
|
||||
|
||||
export type TSecretScanningDataSourceWithDetails = TSecretScanningDataSource & {
|
||||
lastScannedAt: string | null;
|
||||
@@ -24,7 +31,10 @@ export type TListSecretScanningDataSources = {
|
||||
dataSources: TSecretScanningDataSourceWithDetails[];
|
||||
};
|
||||
|
||||
export type TSecretScanningDataSourceOption = TGitHubDataSourceOption | TBitbucketDataSourceOption;
|
||||
export type TSecretScanningDataSourceOption =
|
||||
| TGitHubDataSourceOption
|
||||
| TBitbucketDataSourceOption
|
||||
| TGitLabDataSourceOption;
|
||||
|
||||
export type TListSecretScanningDataSourceOptions = {
|
||||
dataSourceOptions: TSecretScanningDataSourceOption[];
|
||||
|
||||
@@ -29,7 +29,7 @@ export type TGitLabSync = TRootSecretSync & {
|
||||
shouldHideSecrets?: boolean;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.Gitlab;
|
||||
app: AppConnection.GitLab;
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
+2
-2
@@ -137,7 +137,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
return <RenderConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Gitlab:
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} />;
|
||||
@@ -245,7 +245,7 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
return <RenderConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Flyio:
|
||||
return <FlyioConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Gitlab:
|
||||
case AppConnection.GitLab:
|
||||
return <GitLabConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Cloudflare:
|
||||
return <CloudflareConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
|
||||
+5
-5
@@ -38,7 +38,7 @@ type Props = {
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.Gitlab),
|
||||
app: z.literal(AppConnection.GitLab),
|
||||
method: z.literal(GitLabConnectionMethod.AccessToken),
|
||||
credentials: z.object({
|
||||
accessToken: z.string().min(1, "Access token is required"),
|
||||
@@ -54,7 +54,7 @@ const formSchema = z.discriminatedUnion("method", [
|
||||
})
|
||||
}),
|
||||
genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.Gitlab),
|
||||
app: z.literal(AppConnection.GitLab),
|
||||
method: z.literal(GitLabConnectionMethod.OAuth),
|
||||
credentials: z.object({
|
||||
code: z.string().min(1, "Code is required"),
|
||||
@@ -79,7 +79,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
||||
const {
|
||||
option: { oauthClientId },
|
||||
isLoading
|
||||
} = useGetAppConnectionOption(AppConnection.Gitlab);
|
||||
} = useGetAppConnectionOption(AppConnection.GitLab);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
@@ -88,7 +88,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
||||
? { ...appConnection, credentials: { code: "custom" } }
|
||||
: (appConnection ??
|
||||
({
|
||||
app: AppConnection.Gitlab,
|
||||
app: AppConnection.GitLab,
|
||||
method: GitLabConnectionMethod.AccessToken,
|
||||
credentials: {
|
||||
accessToken: "",
|
||||
@@ -207,7 +207,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`The method you would like to use to connect with ${
|
||||
APP_CONNECTION_MAP[AppConnection.Gitlab].name
|
||||
APP_CONNECTION_MAP[AppConnection.GitLab].name
|
||||
}. This field cannot be changed after creation.`}
|
||||
errorText={
|
||||
!isLoading && isMissingConfig && selectedMethod === GitLabConnectionMethod.OAuth
|
||||
|
||||
+7
-7
@@ -65,7 +65,7 @@ type AzureDevOpsFormData = BaseFormData &
|
||||
type FormDataMap = {
|
||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
|
||||
[AppConnection.Gitlab]: GitLabFormData & { app: AppConnection.Gitlab };
|
||||
[AppConnection.GitLab]: GitLabFormData & { app: AppConnection.GitLab };
|
||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||
app: AppConnection.AzureAppConfiguration;
|
||||
@@ -81,7 +81,7 @@ type FormDataMap = {
|
||||
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
||||
[AppConnection.GitHub]: "githubConnectionFormData",
|
||||
[AppConnection.GitHubRadar]: "githubRadarConnectionFormData",
|
||||
[AppConnection.Gitlab]: "gitlabConnectionFormData",
|
||||
[AppConnection.GitLab]: "gitlabConnectionFormData",
|
||||
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
||||
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
|
||||
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData",
|
||||
@@ -141,17 +141,17 @@ export const OAuthCallbackPage = () => {
|
||||
};
|
||||
|
||||
const handleGitlab = useCallback(async () => {
|
||||
const formData = getFormData(AppConnection.Gitlab);
|
||||
const formData = getFormData(AppConnection.GitLab);
|
||||
if (formData === null) return null;
|
||||
|
||||
clearState(AppConnection.Gitlab);
|
||||
clearState(AppConnection.GitLab);
|
||||
|
||||
const { connectionId, name, description, returnUrl, isUpdate } = formData;
|
||||
|
||||
try {
|
||||
if (isUpdate && connectionId) {
|
||||
await updateAppConnection.mutateAsync({
|
||||
app: AppConnection.Gitlab,
|
||||
app: AppConnection.GitLab,
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string
|
||||
@@ -159,7 +159,7 @@ export const OAuthCallbackPage = () => {
|
||||
});
|
||||
} else {
|
||||
await createAppConnection.mutateAsync({
|
||||
app: AppConnection.Gitlab,
|
||||
app: AppConnection.GitLab,
|
||||
name,
|
||||
description,
|
||||
method: GitLabConnectionMethod.OAuth,
|
||||
@@ -521,7 +521,7 @@ export const OAuthCallbackPage = () => {
|
||||
data = await handleGithub();
|
||||
} else if (appConnection === AppConnection.GitHubRadar) {
|
||||
data = await handleGithubRadar();
|
||||
} else if (appConnection === AppConnection.Gitlab) {
|
||||
} else if (appConnection === AppConnection.GitLab) {
|
||||
data = await handleGitlab();
|
||||
} else if (appConnection === AppConnection.AzureKeyVault) {
|
||||
data = await handleAzureKeyVault();
|
||||
|
||||
+3
@@ -5,6 +5,7 @@ import {
|
||||
|
||||
import { BitbucketDataSourceConfigDisplay } from "./BitbucketDataSourceConfigDisplay";
|
||||
import { GitHubDataSourceConfigDisplay } from "./GitHubDataSourceConfigDisplay";
|
||||
import { GitLabDataSourceConfigDisplay } from "./GitLabDataSourceConfigDisplay";
|
||||
|
||||
type Props = {
|
||||
dataSource: TSecretScanningDataSource;
|
||||
@@ -16,6 +17,8 @@ export const DataSourceConfigDisplay = ({ dataSource }: Props) => {
|
||||
return <GitHubDataSourceConfigDisplay dataSource={dataSource} />;
|
||||
case SecretScanningDataSource.Bitbucket:
|
||||
return <BitbucketDataSourceConfigDisplay dataSource={dataSource} />;
|
||||
case SecretScanningDataSource.GitLab:
|
||||
return <GitLabDataSourceConfigDisplay dataSource={dataSource} />;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled dataSource type ${(dataSource as TSecretScanningDataSource).type}`
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { GenericFieldLabel } from "@app/components/v2";
|
||||
import {
|
||||
GitLabDataSourceScope,
|
||||
TGitLabDataSource
|
||||
} from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
type Props = {
|
||||
dataSource: TGitLabDataSource;
|
||||
};
|
||||
|
||||
export const GitLabDataSourceConfigDisplay = ({ dataSource }: Props) => {
|
||||
const { config } = dataSource;
|
||||
|
||||
if (config.scope === GitLabDataSourceScope.Project) {
|
||||
const { projectName, projectId } = config;
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Scope" className="capitalize">
|
||||
{config.scope}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Project">{projectName || projectId}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// group-scope
|
||||
|
||||
const { includeProjects, groupId, groupName } = config;
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Scope" className="capitalize">
|
||||
{config.scope}
|
||||
</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Group">{groupName || groupId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Scan Projects">
|
||||
{includeProjects.includes("*") ? "All" : includeProjects.join(", ")}
|
||||
</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+24
-4
@@ -35,12 +35,14 @@ import {
|
||||
import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
SecretScanningFindingStatus,
|
||||
SecretScanningScanStatus,
|
||||
TSecretScanningDataSource,
|
||||
TSecretScanningResourceWithDetails,
|
||||
useTriggerSecretScanningDataSource
|
||||
} from "@app/hooks/api/secretScanningV2";
|
||||
import { GitLabDataSourceScope } from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
type Props = {
|
||||
resource: TSecretScanningResourceWithDetails;
|
||||
@@ -51,12 +53,30 @@ export const SecretScanningResourceRow = ({ resource, dataSource }: Props) => {
|
||||
const { id, name, lastScannedAt, lastScanStatus, unresolvedFindings, lastScanStatusMessage } =
|
||||
resource;
|
||||
|
||||
const {
|
||||
config: { includeRepos }
|
||||
} = dataSource;
|
||||
let isActive: boolean;
|
||||
|
||||
switch (dataSource.type) {
|
||||
case SecretScanningDataSource.Bitbucket:
|
||||
case SecretScanningDataSource.GitHub:
|
||||
isActive =
|
||||
dataSource.config.includeRepos.includes("*") ||
|
||||
dataSource.config.includeRepos.includes(name);
|
||||
break;
|
||||
case SecretScanningDataSource.GitLab: {
|
||||
if (dataSource.config.scope === GitLabDataSourceScope.Project) {
|
||||
isActive = true; // always active
|
||||
} else {
|
||||
isActive =
|
||||
dataSource.config.includeProjects.includes("*") ||
|
||||
dataSource.config.includeProjects.includes(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Unhandled Data Source Type: Active Status");
|
||||
}
|
||||
|
||||
// scott: will need to be differentiated by type once other data sources are available
|
||||
const isActive = includeRepos.includes("*") || includeRepos.includes(name);
|
||||
|
||||
const triggerDataSourceScan = useTriggerSecretScanningDataSource();
|
||||
|
||||
|
||||
+24
-5
@@ -38,10 +38,12 @@ import { RESOURCE_DESCRIPTION_HELPER } from "@app/helpers/secretScanningV2";
|
||||
import { usePagination, useResetPageHelper } from "@app/hooks";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
import {
|
||||
SecretScanningDataSource,
|
||||
SecretScanningScanStatus,
|
||||
TSecretScanningDataSource,
|
||||
useListSecretScanningResources
|
||||
} from "@app/hooks/api/secretScanningV2";
|
||||
import { GitLabDataSourceScope } from "@app/hooks/api/secretScanningV2/types/gitlab-data-source";
|
||||
|
||||
import { SecretScanningResourceRow } from "./SecretScanningResourceRow";
|
||||
|
||||
@@ -104,12 +106,29 @@ export const SecretScanningResourcesTable = ({ dataSource }: Props) => {
|
||||
resources
|
||||
.filter((resource) => {
|
||||
const { name } = resource;
|
||||
const {
|
||||
config: { includeRepos }
|
||||
} = dataSource;
|
||||
|
||||
// scott: will need to be differentiated by type once other data sources are available
|
||||
const isActive = includeRepos.includes("*") || includeRepos.includes(name);
|
||||
let isActive: boolean;
|
||||
|
||||
switch (dataSource.type) {
|
||||
case SecretScanningDataSource.Bitbucket:
|
||||
case SecretScanningDataSource.GitHub:
|
||||
isActive =
|
||||
dataSource.config.includeRepos.includes("*") ||
|
||||
dataSource.config.includeRepos.includes(name);
|
||||
break;
|
||||
case SecretScanningDataSource.GitLab: {
|
||||
if (dataSource.config.scope === GitLabDataSourceScope.Project) {
|
||||
isActive = true; // always active
|
||||
} else {
|
||||
isActive =
|
||||
dataSource.config.includeProjects.includes("*") ||
|
||||
dataSource.config.includeProjects.includes(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error("Unhandled Data Source Type: Active Filter");
|
||||
}
|
||||
|
||||
if (filters.status.length === 1) {
|
||||
if (filters.status.includes(ResourceStatus.Active) && !isActive) {
|
||||
|
||||
Reference in New Issue
Block a user