mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
feat(providers): add GitHub provider support with credential types (#8405)
This commit is contained in:
+1
-1
@@ -8,7 +8,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
- Lighthouse banner [(#8259)](https://github.com/prowler-cloud/prowler/pull/8259)
|
||||
- Integration with Amazon S3, enabling storage and retrieval of scan data via S3 buckets [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
|
||||
|
||||
- Github provider support [(#8405)](https://github.com/prowler-cloud/prowler/pull/8405)
|
||||
___
|
||||
|
||||
## [v1.9.3] (Prowler v5.9.3)
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
AddViaServiceAccountForm,
|
||||
SelectViaGCP,
|
||||
} from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
|
||||
import { getProviderFormType } from "@/lib/provider-helpers";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
@@ -16,30 +18,27 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function AddCredentialsPage({ searchParams }: Props) {
|
||||
return (
|
||||
<>
|
||||
{searchParams.type === "aws" && !searchParams.via && (
|
||||
<SelectViaAWS initialVia={searchParams.via} />
|
||||
)}
|
||||
const { type: providerType, via } = searchParams;
|
||||
const formType = getProviderFormType(providerType, via);
|
||||
|
||||
{searchParams.type === "gcp" && !searchParams.via && (
|
||||
<SelectViaGCP initialVia={searchParams.via} />
|
||||
)}
|
||||
switch (formType) {
|
||||
case "selector":
|
||||
if (providerType === "aws") return <SelectViaAWS initialVia={via} />;
|
||||
if (providerType === "gcp") return <SelectViaGCP initialVia={via} />;
|
||||
if (providerType === "github")
|
||||
return <SelectViaGitHub initialVia={via} />;
|
||||
return null;
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
<AddViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
case "credentials":
|
||||
return <AddViaCredentialsForm searchParams={searchParams} />;
|
||||
|
||||
{searchParams.type === "aws" && searchParams.via === "role" && (
|
||||
<AddViaRoleForm searchParams={searchParams} />
|
||||
)}
|
||||
case "role":
|
||||
return <AddViaRoleForm searchParams={searchParams} />;
|
||||
|
||||
{searchParams.type === "gcp" &&
|
||||
searchParams.via === "service-account" && (
|
||||
<AddViaServiceAccountForm searchParams={searchParams} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
case "service-account":
|
||||
return <AddViaServiceAccountForm searchParams={searchParams} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
UpdateViaRoleForm,
|
||||
} from "@/components/providers/workflow/forms";
|
||||
import { UpdateViaServiceAccountForm } from "@/components/providers/workflow/forms/update-via-service-account-key-form";
|
||||
import { getProviderFormType } from "@/lib/provider-helpers";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
@@ -18,30 +19,25 @@ interface Props {
|
||||
}
|
||||
|
||||
export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
return (
|
||||
<>
|
||||
{(searchParams.type === "aws" || searchParams.type === "gcp") &&
|
||||
!searchParams.via && (
|
||||
<CredentialsUpdateInfo
|
||||
providerType={searchParams.type}
|
||||
initialVia={searchParams.via}
|
||||
/>
|
||||
)}
|
||||
const { type: providerType, via } = searchParams;
|
||||
const formType = getProviderFormType(providerType, via);
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
<UpdateViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
switch (formType) {
|
||||
case "selector":
|
||||
return (
|
||||
<CredentialsUpdateInfo providerType={providerType} initialVia={via} />
|
||||
);
|
||||
|
||||
{searchParams.type === "aws" && searchParams.via === "role" && (
|
||||
<UpdateViaRoleForm searchParams={searchParams} />
|
||||
)}
|
||||
case "credentials":
|
||||
return <UpdateViaCredentialsForm searchParams={searchParams} />;
|
||||
|
||||
{searchParams.type === "gcp" &&
|
||||
searchParams.via === "service-account" && (
|
||||
<UpdateViaServiceAccountForm searchParams={searchParams} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
case "role":
|
||||
return <UpdateViaRoleForm searchParams={searchParams} />;
|
||||
|
||||
case "service-account":
|
||||
return <UpdateViaServiceAccountForm searchParams={searchParams} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
export const GitHubProviderBadge: React.FC<IconSvgProps> = ({
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
...props
|
||||
}) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height={size || height}
|
||||
role="presentation"
|
||||
viewBox="0 0 98 96"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from "./aws-provider-badge";
|
||||
export * from "./azure-provider-badge";
|
||||
export * from "./gcp-provider-badge";
|
||||
export * from "./github-provider-badge";
|
||||
export * from "./ks8-provider-badge";
|
||||
export * from "./m365-provider-badge";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
|
||||
import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface UpdateCredentialsInfoProps {
|
||||
@@ -20,6 +21,9 @@ export const CredentialsUpdateInfo = ({
|
||||
if (providerType === "gcp") {
|
||||
return <SelectViaGCP initialVia={initialVia} />;
|
||||
}
|
||||
if (providerType === "github") {
|
||||
return <SelectViaGitHub initialVia={initialVia} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
@@ -71,6 +72,12 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
|
||||
<span className="ml-2">Kubernetes</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio description="GitHub" value="github">
|
||||
<div className="flex items-center">
|
||||
<GitHubProviderBadge size={26} />
|
||||
<span className="ml-2">GitHub</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { useCredentialsForm } from "@/hooks/use-credentials-form";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { requiresBackButton } from "@/lib/provider-helpers";
|
||||
import {
|
||||
AWSCredentials,
|
||||
AWSCredentialsRole,
|
||||
@@ -25,6 +26,7 @@ import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credential
|
||||
import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type";
|
||||
import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
|
||||
import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
|
||||
import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form";
|
||||
import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
|
||||
import { M365CredentialsForm } from "./via-credentials/m365-credentials-form";
|
||||
|
||||
@@ -121,26 +123,29 @@ export const BaseCredentialsForm = ({
|
||||
control={form.control as unknown as Control<KubernetesCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "github" && (
|
||||
<GitHubCredentialsForm
|
||||
control={form.control}
|
||||
credentialsType={searchParamsObj.get("via") || undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
{showBackButton &&
|
||||
(searchParamsObj.get("via") === "credentials" ||
|
||||
searchParamsObj.get("via") === "role" ||
|
||||
searchParamsObj.get("via") === "service-account") && (
|
||||
<CustomButton
|
||||
type="button"
|
||||
ariaLabel="Back"
|
||||
className="w-1/2 bg-transparent"
|
||||
variant="faded"
|
||||
size="lg"
|
||||
radius="lg"
|
||||
onPress={handleBackStep}
|
||||
startContent={!isLoading && <ChevronLeftIcon size={24} />}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
<span>Back</span>
|
||||
</CustomButton>
|
||||
)}
|
||||
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
|
||||
<CustomButton
|
||||
type="button"
|
||||
ariaLabel="Back"
|
||||
className="w-1/2 bg-transparent"
|
||||
variant="faded"
|
||||
size="lg"
|
||||
radius="lg"
|
||||
onPress={handleBackStep}
|
||||
startContent={!isLoading && <ChevronLeftIcon size={24} />}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
<span>Back</span>
|
||||
</CustomButton>
|
||||
)}
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel="Save"
|
||||
|
||||
@@ -7,18 +7,19 @@ import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { addProvider } from "@/actions/providers/providers";
|
||||
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton, CustomInput } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { addProviderFormSchema, ApiError, ProviderType } from "@/types";
|
||||
|
||||
import { addProvider } from "../../../../actions/providers/providers";
|
||||
import { addProviderFormSchema, ApiError } from "../../../../types";
|
||||
import { RadioGroupProvider } from "../../radio-group-provider";
|
||||
import { ProviderTitleDocs } from "../provider-title-docs";
|
||||
|
||||
export type FormValues = z.infer<typeof addProviderFormSchema>;
|
||||
|
||||
// Helper function for labels and placeholders
|
||||
const getProviderFieldDetails = (providerType?: string) => {
|
||||
const getProviderFieldDetails = (providerType?: ProviderType) => {
|
||||
switch (providerType) {
|
||||
case "aws":
|
||||
return {
|
||||
@@ -45,6 +46,11 @@ const getProviderFieldDetails = (providerType?: string) => {
|
||||
label: "Domain ID",
|
||||
placeholder: "e.g. your-domain.onmicrosoft.com",
|
||||
};
|
||||
case "github":
|
||||
return {
|
||||
label: "Username",
|
||||
placeholder: "e.g. your-github-username",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Provider UID",
|
||||
@@ -142,6 +148,10 @@ export const ConnectAccountForm = () => {
|
||||
|
||||
const handleBackStep = () => {
|
||||
setPrevStep((prev) => prev - 1);
|
||||
//Deselect the providerType if the user is going back to the first step
|
||||
if (prevStep === 2) {
|
||||
form.setValue("providerType", undefined as unknown as ProviderType);
|
||||
}
|
||||
// Reset the providerUid and providerAlias fields when going back
|
||||
form.setValue("providerUid", "");
|
||||
form.setValue("providerAlias", "");
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubAppForm = ({ control }: { control: Control<any> }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect via GitHub App
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide your GitHub App ID and private key.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.GITHUB_APP_ID}
|
||||
type="text"
|
||||
label="GitHub App ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter your GitHub App ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_ID]
|
||||
}
|
||||
/>
|
||||
<CustomTextarea
|
||||
control={control}
|
||||
name={ProviderCredentialFields.GITHUB_APP_KEY}
|
||||
label="GitHub App Private Key"
|
||||
labelPlacement="inside"
|
||||
placeholder="Paste your GitHub App private key here"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
minRows={4}
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubOAuthAppForm = ({ control }: { control: Control<any> }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect via OAuth App
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide your GitHub OAuth App token.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
|
||||
type="password"
|
||||
label="OAuth App Token"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter your GitHub OAuth App token"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.OAUTH_APP_TOKEN]
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubPersonalAccessTokenForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<any>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect via Personal Access Token
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide your GitHub personal access token.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
|
||||
type="password"
|
||||
label="Personal Access Token"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter your GitHub personal access token"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[
|
||||
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
|
||||
]
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export * from "./github-app-form";
|
||||
export * from "./github-oauth-app-form";
|
||||
export * from "./github-personal-access-token-form";
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./credentials-type";
|
||||
export * from "./radio-group-github-via-credentials-type-form";
|
||||
export * from "./select-via-github";
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupGitHubViaCredentialsFormProps = {
|
||||
control: Control<any>;
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
onChange?: (value: string) => void;
|
||||
};
|
||||
|
||||
export const RadioGroupGitHubViaCredentialsTypeForm = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
onChange,
|
||||
}: RadioGroupGitHubViaCredentialsFormProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name="githubCredentialsType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-sm text-default-500">
|
||||
Personal Access Token
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Use a personal access token for authentication"
|
||||
value="personal_access_token"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Personal Access Token</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
|
||||
<span className="text-sm text-default-500">OAuth App</span>
|
||||
<CustomRadio
|
||||
description="Use OAuth App token for authentication"
|
||||
value="oauth_app"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">OAuth App Token</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
|
||||
<span className="text-sm text-default-500">GitHub App</span>
|
||||
<CustomRadio
|
||||
description="Use GitHub App ID and private key for authentication"
|
||||
value="github_app"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">GitHub App</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-system-error dark:text-system-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Form } from "@/components/ui/form";
|
||||
|
||||
import { RadioGroupGitHubViaCredentialsTypeForm } from "./radio-group-github-via-credentials-type-form";
|
||||
|
||||
interface SelectViaGitHubProps {
|
||||
initialVia?: string;
|
||||
}
|
||||
|
||||
export const SelectViaGitHub = ({ initialVia }: SelectViaGitHubProps) => {
|
||||
const router = useRouter();
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
githubCredentialsType: initialVia || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSelectionChange = (value: string) => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("via", value);
|
||||
router.push(url.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RadioGroupGitHubViaCredentialsTypeForm
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.githubCredentialsType}
|
||||
errorMessage={form.formState.errors.githubCredentialsType?.message}
|
||||
onChange={handleSelectionChange}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import {
|
||||
GitHubAppForm,
|
||||
GitHubOAuthAppForm,
|
||||
GitHubPersonalAccessTokenForm,
|
||||
} from "../select-credentials-type/github";
|
||||
|
||||
interface GitHubCredentialsFormProps {
|
||||
control: Control<any>;
|
||||
credentialsType?: string;
|
||||
}
|
||||
|
||||
export const GitHubCredentialsForm = ({
|
||||
control,
|
||||
credentialsType,
|
||||
}: GitHubCredentialsFormProps) => {
|
||||
switch (credentialsType) {
|
||||
case "personal_access_token":
|
||||
return <GitHubPersonalAccessTokenForm control={control} />;
|
||||
case "oauth_app":
|
||||
return <GitHubOAuthAppForm control={control} />;
|
||||
case "github_app":
|
||||
return <GitHubAppForm control={control} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./azure-credentials-form";
|
||||
export * from "./github-credentials-form";
|
||||
export * from "./k8s-credentials-form";
|
||||
export * from "./m365-credentials-form";
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AWSProviderBadge,
|
||||
AzureProviderBadge,
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
} from "@/components/icons/providers-badge";
|
||||
@@ -21,6 +22,8 @@ export const getProviderLogo = (provider: ProviderType) => {
|
||||
return <KS8ProviderBadge width={35} height={35} />;
|
||||
case "m365":
|
||||
return <M365ProviderBadge width={35} height={35} />;
|
||||
case "github":
|
||||
return <GitHubProviderBadge width={35} height={35} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -38,6 +41,8 @@ export const getProviderName = (provider: ProviderType): string => {
|
||||
return "Kubernetes";
|
||||
case "m365":
|
||||
return "Microsoft 365";
|
||||
case "github":
|
||||
return "GitHub";
|
||||
default:
|
||||
return "Unknown Provider";
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ export const useCredentialsForm = ({
|
||||
if (providerType === "gcp" && via === "service-account") {
|
||||
return addCredentialsServiceAccountFormSchema(providerType);
|
||||
}
|
||||
// For GitHub, we need to pass the via parameter to determine which fields are required
|
||||
if (providerType === "github") {
|
||||
return addCredentialsFormSchema(providerType, via);
|
||||
}
|
||||
return addCredentialsFormSchema(providerType);
|
||||
};
|
||||
|
||||
@@ -117,6 +121,28 @@ export const useCredentialsForm = ({
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.KUBECONFIG_CONTENT]: "",
|
||||
};
|
||||
case "github":
|
||||
// GitHub credentials based on via parameter
|
||||
if (via === "personal_access_token") {
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: "",
|
||||
};
|
||||
}
|
||||
if (via === "oauth_app") {
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.OAUTH_APP_TOKEN]: "",
|
||||
};
|
||||
}
|
||||
if (via === "github_app") {
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.GITHUB_APP_ID]: "",
|
||||
[ProviderCredentialFields.GITHUB_APP_KEY]: "",
|
||||
};
|
||||
}
|
||||
return baseDefaults;
|
||||
default:
|
||||
return baseDefaults;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ export const getProviderHelpText = (provider: string) => {
|
||||
text: "Need help connecting your Kubernetes cluster?",
|
||||
link: "https://goto.prowler.com/provider-k8s",
|
||||
};
|
||||
case "github":
|
||||
return {
|
||||
text: "Need help connecting your GitHub account?",
|
||||
link: "https://goto.prowler.com/provider-github",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: "How to setup a provider?",
|
||||
|
||||
@@ -147,6 +147,56 @@ export const buildKubernetesSecret = (formData: FormData) => {
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
export const buildGitHubSecret = (formData: FormData) => {
|
||||
// Check which authentication method is being used
|
||||
const hasPersonalToken =
|
||||
formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== null &&
|
||||
formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== "";
|
||||
const hasOAuthToken =
|
||||
formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== null &&
|
||||
formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== "";
|
||||
const hasGitHubApp =
|
||||
formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== null &&
|
||||
formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== "";
|
||||
|
||||
if (hasPersonalToken) {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
}
|
||||
|
||||
if (hasOAuthToken) {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.OAUTH_APP_TOKEN]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.OAUTH_APP_TOKEN,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
}
|
||||
|
||||
if (hasGitHubApp) {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.GITHUB_APP_ID]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.GITHUB_APP_ID,
|
||||
),
|
||||
[ProviderCredentialFields.GITHUB_APP_KEY]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.GITHUB_APP_KEY,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
}
|
||||
|
||||
// If no credentials are provided, return empty object
|
||||
return {};
|
||||
};
|
||||
|
||||
// Main function to build secret configuration
|
||||
export const buildSecretConfig = (
|
||||
formData: FormData,
|
||||
@@ -177,6 +227,10 @@ export const buildSecretConfig = (
|
||||
secretType: "static",
|
||||
secret: buildKubernetesSecret(formData),
|
||||
}),
|
||||
github: () => ({
|
||||
secretType: "static",
|
||||
secret: buildGitHubSecret(formData),
|
||||
}),
|
||||
};
|
||||
|
||||
const builder = secretBuilders[providerType];
|
||||
|
||||
@@ -8,6 +8,7 @@ export const ProviderCredentialFields = {
|
||||
CREDENTIALS_TYPE: "credentials_type",
|
||||
CREDENTIALS_TYPE_AWS: "aws-sdk-default",
|
||||
CREDENTIALS_TYPE_ACCESS_SECRET_KEY: "access-secret-key",
|
||||
|
||||
// Base fields for all providers
|
||||
PROVIDER_ID: "providerId",
|
||||
PROVIDER_TYPE: "providerType",
|
||||
@@ -35,6 +36,12 @@ export const ProviderCredentialFields = {
|
||||
|
||||
// Kubernetes fields
|
||||
KUBECONFIG_CONTENT: "kubeconfig_content",
|
||||
|
||||
// GitHub fields
|
||||
PERSONAL_ACCESS_TOKEN: "personal_access_token",
|
||||
OAUTH_APP_TOKEN: "oauth_app_token",
|
||||
GITHUB_APP_ID: "github_app_id",
|
||||
GITHUB_APP_KEY: "github_app_key_content",
|
||||
} as const;
|
||||
|
||||
// Type for credential field values
|
||||
@@ -59,6 +66,10 @@ export const ErrorPointers = {
|
||||
SESSION_DURATION: "/data/attributes/secret/session_duration",
|
||||
ROLE_SESSION_NAME: "/data/attributes/secret/role_session_name",
|
||||
SERVICE_ACCOUNT_KEY: "/data/attributes/secret/service_account_key",
|
||||
PERSONAL_ACCESS_TOKEN: "/data/attributes/secret/personal_access_token",
|
||||
OAUTH_APP_TOKEN: "/data/attributes/secret/oauth_app_token",
|
||||
GITHUB_APP_ID: "/data/attributes/secret/github_app_id",
|
||||
GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content",
|
||||
} as const;
|
||||
|
||||
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
ProviderEntity,
|
||||
ProviderProps,
|
||||
ProvidersApiResponse,
|
||||
ProviderType,
|
||||
} from "@/types/providers";
|
||||
|
||||
export const extractProviderUIDs = (
|
||||
@@ -38,3 +39,67 @@ export const createProviderDetailsMapping = (
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to determine which form type to show
|
||||
export type ProviderFormType =
|
||||
| "selector"
|
||||
| "credentials"
|
||||
| "role"
|
||||
| "service-account"
|
||||
| null;
|
||||
|
||||
export const getProviderFormType = (
|
||||
providerType: ProviderType,
|
||||
via?: string,
|
||||
): ProviderFormType => {
|
||||
// Providers that need credential type selection
|
||||
const needsSelector = ["aws", "gcp", "github"].includes(providerType);
|
||||
|
||||
// Show selector if no via parameter and provider needs it
|
||||
if (needsSelector && !via) {
|
||||
return "selector";
|
||||
}
|
||||
|
||||
// AWS specific forms
|
||||
if (providerType === "aws") {
|
||||
if (via === "role") return "role";
|
||||
if (via === "credentials") return "credentials";
|
||||
}
|
||||
|
||||
// GCP specific forms
|
||||
if (providerType === "gcp") {
|
||||
if (via === "service-account") return "service-account";
|
||||
if (via === "credentials") return "credentials";
|
||||
}
|
||||
|
||||
// GitHub credential types
|
||||
if (
|
||||
providerType === "github" &&
|
||||
["personal_access_token", "oauth_app", "github_app"].includes(via || "")
|
||||
) {
|
||||
return "credentials";
|
||||
}
|
||||
|
||||
// Other providers go directly to credentials form
|
||||
if (!needsSelector) {
|
||||
return "credentials";
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// Helper to check if back button should be shown based on via parameter
|
||||
export const requiresBackButton = (via?: string | null): boolean => {
|
||||
if (!via) return false;
|
||||
|
||||
const validViaTypes = [
|
||||
"credentials",
|
||||
"role",
|
||||
"service-account",
|
||||
"personal_access_token",
|
||||
"oauth_app",
|
||||
"github_app",
|
||||
];
|
||||
|
||||
return validViaTypes.includes(via);
|
||||
};
|
||||
|
||||
+63
-4
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
|
||||
|
||||
import { ProviderType } from "./providers";
|
||||
import { PROVIDER_TYPES, ProviderType } from "./providers";
|
||||
|
||||
export const addRoleFormSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
@@ -71,7 +71,7 @@ export const awsCredentialsTypeSchema = z.object({
|
||||
|
||||
export const addProviderFormSchema = z
|
||||
.object({
|
||||
providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
|
||||
providerType: z.enum(PROVIDER_TYPES, {
|
||||
required_error: "Please select a provider type",
|
||||
}),
|
||||
})
|
||||
@@ -105,10 +105,18 @@ export const addProviderFormSchema = z
|
||||
providerUid: z.string(),
|
||||
awsCredentialsType: z.string().optional(),
|
||||
}),
|
||||
z.object({
|
||||
providerType: z.literal("github"),
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
export const addCredentialsFormSchema = (providerType: string) =>
|
||||
export const addCredentialsFormSchema = (
|
||||
providerType: ProviderType,
|
||||
via?: string | null,
|
||||
) =>
|
||||
z
|
||||
.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
@@ -167,7 +175,22 @@ export const addCredentialsFormSchema = (providerType: string) =>
|
||||
[ProviderCredentialFields.USER]: z.string().optional(),
|
||||
[ProviderCredentialFields.PASSWORD]: z.string().optional(),
|
||||
}
|
||||
: {}),
|
||||
: providerType === "github"
|
||||
? {
|
||||
[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: z
|
||||
.string()
|
||||
.optional(),
|
||||
[ProviderCredentialFields.OAUTH_APP_TOKEN]: z
|
||||
.string()
|
||||
.optional(),
|
||||
[ProviderCredentialFields.GITHUB_APP_ID]: z
|
||||
.string()
|
||||
.optional(),
|
||||
[ProviderCredentialFields.GITHUB_APP_KEY]: z
|
||||
.string()
|
||||
.optional(),
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
.superRefine((data: Record<string, any>, ctx) => {
|
||||
if (providerType === "m365") {
|
||||
@@ -190,6 +213,42 @@ export const addCredentialsFormSchema = (providerType: string) =>
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (providerType === "github") {
|
||||
// For GitHub, validation depends on the 'via' parameter
|
||||
if (via === "personal_access_token") {
|
||||
if (!data[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Personal Access Token is required",
|
||||
path: [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN],
|
||||
});
|
||||
}
|
||||
} else if (via === "oauth_app") {
|
||||
if (!data[ProviderCredentialFields.OAUTH_APP_TOKEN]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "OAuth App Token is required",
|
||||
path: [ProviderCredentialFields.OAUTH_APP_TOKEN],
|
||||
});
|
||||
}
|
||||
} else if (via === "github_app") {
|
||||
if (!data[ProviderCredentialFields.GITHUB_APP_ID]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "GitHub App ID is required",
|
||||
path: [ProviderCredentialFields.GITHUB_APP_ID],
|
||||
});
|
||||
}
|
||||
if (!data[ProviderCredentialFields.GITHUB_APP_KEY]) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "GitHub App Private Key is required",
|
||||
path: [ProviderCredentialFields.GITHUB_APP_KEY],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const addCredentialsRoleFormSchema = (providerType: string) =>
|
||||
|
||||
+10
-1
@@ -1,4 +1,13 @@
|
||||
export type ProviderType = "aws" | "azure" | "m365" | "gcp" | "kubernetes";
|
||||
export const PROVIDER_TYPES = [
|
||||
"aws",
|
||||
"azure",
|
||||
"gcp",
|
||||
"kubernetes",
|
||||
"m365",
|
||||
"github",
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
|
||||
export interface ProviderProps {
|
||||
id: string;
|
||||
|
||||
Reference in New Issue
Block a user