chore(ui): add Github provider

This commit is contained in:
MrCloudSec
2025-07-17 16:17:27 +08:00
parent 0cfe41e452
commit afd89cfb2c
33 changed files with 743 additions and 48 deletions
@@ -9,6 +9,7 @@ import {
AddViaServiceAccountForm,
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 Props {
@@ -26,9 +27,20 @@ export default function AddCredentialsPage({ searchParams }: Props) {
<SelectViaGCP initialVia={searchParams.via} />
)}
{searchParams.type === "github" && !searchParams.via && (
<SelectViaGitHub initialVia={searchParams.via} />
)}
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
(searchParams.type === "github" &&
searchParams.via === "personal_access_token") ||
(searchParams.type === "github" &&
searchParams.via === "oauth_app_token") ||
(searchParams.type === "github" && searchParams.via === "github_app") ||
(searchParams.type !== "aws" &&
searchParams.type !== "gcp" &&
searchParams.type !== "github")) && (
<AddViaCredentialsForm searchParams={searchParams} />
)}
@@ -1,6 +1,6 @@
import React from "react";
import { CredentialsUpdateInfo } from "@/components/providers";
import { CredentialsUpdateInfo } from "@/components/providers/credentials-update-info";
import {
UpdateViaCredentialsForm,
UpdateViaRoleForm,
@@ -20,7 +20,9 @@ interface Props {
export default function UpdateCredentialsPage({ searchParams }: Props) {
return (
<>
{(searchParams.type === "aws" || searchParams.type === "gcp") &&
{(searchParams.type === "aws" ||
searchParams.type === "gcp" ||
searchParams.type === "github") &&
!searchParams.via && (
<CredentialsUpdateInfo
providerType={searchParams.type}
@@ -30,7 +32,14 @@ export default function UpdateCredentialsPage({ searchParams }: Props) {
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
(searchParams.type === "github" &&
searchParams.via === "personal_access_token") ||
(searchParams.type === "github" &&
searchParams.via === "oauth_app_token") ||
(searchParams.type === "github" && searchParams.via === "github_app") ||
(searchParams.type !== "aws" &&
searchParams.type !== "gcp" &&
searchParams.type !== "github")) && (
<UpdateViaCredentialsForm searchParams={searchParams} />
)}
@@ -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="#24292f"
/>
</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 && (
@@ -14,6 +14,7 @@ import {
AzureCredentials,
GCPDefaultCredentials,
GCPServiceAccountKey,
GitHubCredentials,
KubernetesCredentials,
M365Credentials,
ProviderType,
@@ -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,6 +123,12 @@ export const BaseCredentialsForm = ({
control={form.control as unknown as Control<KubernetesCredentials>}
/>
)}
{providerType === "github" && (
<GitHubCredentialsForm
control={form.control as unknown as Control<GitHubCredentials>}
via={searchParamsObj.get("via") || undefined}
/>
)}
<div className="flex w-full justify-end sm:space-x-6">
{showBackButton &&
@@ -45,6 +45,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",
@@ -0,0 +1,51 @@
import { Control } from "react-hook-form";
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { GitHubCredentials } from "@/types";
export const GitHubAppForm = ({
control,
}: {
control: Control<GitHubCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
GitHub App
</div>
<div className="text-sm text-default-500">
Use GitHub App credentials for advanced integration. This requires
both the App ID and Private Key.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.GITHUB_APP_ID}
type="number"
label="GitHub App ID"
labelPlacement="inside"
placeholder="Enter the GitHub App ID"
variant="bordered"
isRequired={true}
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 content here"
variant="bordered"
minRows={8}
isRequired={true}
isInvalid={
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
}
/>
</>
);
};
@@ -0,0 +1,38 @@
import { Control } from "react-hook-form";
import { CustomInput } from "@/components/ui/custom";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { GitHubCredentials } from "@/types";
export const GitHubOAuthAppTokenForm = ({
control,
}: {
control: Control<GitHubCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
OAuth App Token
</div>
<div className="text-sm text-default-500">
Use an OAuth app token for application-level authentication. This is
suitable for applications that need broader access.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
type="password"
label="OAuth App Token"
labelPlacement="inside"
placeholder="Enter the OAuth App Token"
variant="bordered"
isRequired={true}
isInvalid={
!!control._formState.errors[ProviderCredentialFields.OAUTH_APP_TOKEN]
}
/>
</>
);
};
@@ -0,0 +1,40 @@
import { Control } from "react-hook-form";
import { CustomInput } from "@/components/ui/custom";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { GitHubCredentials } from "@/types";
export const GitHubPersonalAccessTokenForm = ({
control,
}: {
control: Control<GitHubCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Personal Access Token
</div>
<div className="text-sm text-default-500">
Use a personal access token for individual user authentication. This
is the simplest method for personal use.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
type="password"
label="Personal Access Token"
labelPlacement="inside"
placeholder="Enter the Personal Access Token"
variant="bordered"
isRequired={true}
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
]
}
/>
</>
);
};
@@ -0,0 +1,3 @@
export * from "./github-app-form";
export * from "./github-oauth-app-token-form";
export * from "./github-personal-access-token-form";
@@ -0,0 +1,2 @@
export * from "./radio-group-github-via-credentials-type-form";
export * from "./select-via-github";
@@ -0,0 +1,80 @@
"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">
Authentication Methods
</span>
<CustomRadio
description="Use a personal access token for individual user authentication"
value="personal_access_token"
>
<div className="flex items-center">
<span className="ml-2">Personal Access Token</span>
</div>
</CustomRadio>
<CustomRadio
description="Use an OAuth app token for application-level authentication"
value="oauth_app_token"
>
<div className="flex items-center">
<span className="ml-2">OAuth App Token</span>
</div>
</CustomRadio>
<CustomRadio
description="Use GitHub App credentials (requires App ID and Private Key)"
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>
)}
</>
)}
/>
);
};
@@ -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,239 @@
import { Control } from "react-hook-form";
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { GitHubCredentials } from "@/types";
export const GitHubCredentialsForm = ({
control,
via,
}: {
control: Control<GitHubCredentials>;
via?: string;
}) => {
const renderPersonalAccessTokenFields = () => (
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
Personal Access Token
</h3>
<p className="text-xs text-default-500">
Use a personal access token for individual user authentication
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
type="password"
label="Personal Access Token"
labelPlacement="inside"
placeholder="Enter the Personal Access Token"
variant="bordered"
isRequired
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
]
}
/>
</div>
);
const renderOAuthAppTokenFields = () => (
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
OAuth App Token
</h3>
<p className="text-xs text-default-500">
Use an OAuth app token for application-level authentication
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
type="password"
label="OAuth App Token"
labelPlacement="inside"
placeholder="Enter the OAuth App Token"
variant="bordered"
isRequired
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.OAUTH_APP_TOKEN
]
}
/>
</div>
);
const renderGitHubAppFields = () => (
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
GitHub App
</h3>
<p className="text-xs text-default-500">
Use GitHub App credentials (both App ID and Private Key are
required)
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.GITHUB_APP_ID}
type="number"
label="GitHub App ID"
labelPlacement="inside"
placeholder="Enter the 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 content here"
variant="bordered"
minRows={10}
isRequired
isInvalid={
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
}
/>
</div>
);
const renderAllOptions = () => (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Connect via Credentials
</div>
<div className="text-sm text-default-500">
Choose one of the following authentication methods for your GitHub
credentials:
</div>
</div>
{/* Option 1: Personal Access Token */}
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
Option 1: Personal Access Token
</h3>
<p className="text-xs text-default-500">
Use a personal access token for individual user authentication
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
type="password"
label="Personal Access Token"
labelPlacement="inside"
placeholder="Enter the Personal Access Token"
variant="bordered"
isRequired={false}
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
]
}
/>
</div>
{/* Option 2: OAuth App Token */}
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
Option 2: OAuth App Token
</h3>
<p className="text-xs text-default-500">
Use an OAuth app token for application-level authentication
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
type="password"
label="OAuth App Token"
labelPlacement="inside"
placeholder="Enter the OAuth App Token"
variant="bordered"
isRequired={false}
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.OAUTH_APP_TOKEN
]
}
/>
</div>
{/* Option 3: GitHub App */}
<div className="space-y-3">
<div className="border-b border-divider pb-2">
<h3 className="text-sm font-semibold text-default-foreground">
Option 3: GitHub App
</h3>
<p className="text-xs text-default-500">
Use GitHub App credentials (both App ID and Private Key are
required)
</p>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.GITHUB_APP_ID}
type="number"
label="GitHub App ID"
labelPlacement="inside"
placeholder="Enter the GitHub App ID"
variant="bordered"
isRequired={false}
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 content here"
variant="bordered"
minRows={8}
isRequired={false}
isInvalid={
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
}
/>
</div>
</>
);
// If via parameter is provided, show only the selected method
if (via) {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Connect via Credentials
</div>
<div className="text-sm text-default-500">
Enter your GitHub credentials for the selected authentication method.
</div>
</div>
{via === "personal_access_token" && renderPersonalAccessTokenFields()}
{via === "oauth_app_token" && renderOAuthAppTokenFields()}
{via === "github_app" && renderGitHubAppFields()}
</>
);
}
// If no via parameter, show all options (fallback behavior)
return renderAllOptions();
};
@@ -66,6 +66,7 @@ export const SelectScanProvider = <
| "azure"
| "gcp"
| "kubernetes"
| "github"
}
entityAlias={selectedItem.alias}
entityId={selectedItem.uid}
@@ -91,6 +92,7 @@ export const SelectScanProvider = <
| "azure"
| "gcp"
| "kubernetes"
| "github"
}
entityAlias={item.alias}
entityId={item.uid}
+47 -30
View File
@@ -94,36 +94,53 @@ export const CustomInput = <T extends FieldValues>({
<FormField
control={control}
name={name}
render={({ field }) => (
<>
<FormControl>
<Input
id={name}
classNames={{
label:
"tracking-tight font-light !text-default-500 text-xs !z-0",
input: "text-default-500 text-small",
}}
isRequired={inputIsRequired}
label={inputLabel}
labelPlacement={labelPlacement}
placeholder={inputPlaceholder}
type={inputType}
variant={variant}
size={size}
isInvalid={isInvalid}
defaultValue={defaultValue}
endContent={endContent}
isDisabled={isDisabled}
isReadOnly={isReadOnly}
{...field}
/>
</FormControl>
{showFormMessage && (
<FormMessage className="max-w-full text-xs text-system-error dark:text-system-error" />
)}
</>
)}
render={({ field }) => {
// Handle number type conversion
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
if (type === "number") {
// Convert empty string to undefined, otherwise convert to number
const numValue = value === "" ? undefined : Number(value);
field.onChange(numValue);
} else {
field.onChange(value);
}
};
return (
<>
<FormControl>
<Input
id={name}
classNames={{
label:
"tracking-tight font-light !text-default-500 text-xs !z-0",
input: "text-default-500 text-small",
}}
isRequired={inputIsRequired}
label={inputLabel}
labelPlacement={labelPlacement}
placeholder={inputPlaceholder}
type={inputType}
variant={variant}
size={size}
isInvalid={isInvalid}
defaultValue={defaultValue}
endContent={endContent}
isDisabled={isDisabled}
isReadOnly={isReadOnly}
value={field.value ?? ""}
onChange={handleChange}
onBlur={field.onBlur}
name={field.name}
/>
</FormControl>
{showFormMessage && (
<FormMessage className="max-w-full text-xs text-system-error dark:text-system-error" />
)}
</>
);
}}
/>
);
};
@@ -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";
}
+5
View File
@@ -28,4 +28,9 @@ export const PROVIDER_CREDENTIALS_ERROR_MAPPING: Record<string, string> = {
[ErrorPointers.ROLE_SESSION_NAME]: ProviderCredentialFields.ROLE_SESSION_NAME,
[ErrorPointers.SERVICE_ACCOUNT_KEY]:
ProviderCredentialFields.SERVICE_ACCOUNT_KEY,
[ErrorPointers.PERSONAL_ACCESS_TOKEN]:
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN,
[ErrorPointers.OAUTH_APP_TOKEN]: ProviderCredentialFields.OAUTH_APP_TOKEN,
[ErrorPointers.GITHUB_APP_ID]: ProviderCredentialFields.GITHUB_APP_ID,
[ErrorPointers.GITHUB_APP_KEY]: ProviderCredentialFields.GITHUB_APP_KEY,
};
+5
View File
@@ -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?",
+2 -2
View File
@@ -10,7 +10,7 @@ You use Prowler tool's capabilities to answer the user's query.
## Prowler Capabilities
- Prowler is an Open Cloud Security tool
- Prowler scans misconfigurations in AWS, Azure, Microsoft 365, GCP, and Kubernetes
- Prowler scans misconfigurations in AWS, Azure, Microsoft 365, GCP, GitHub, and Kubernetes
- Prowler helps with continuous monitoring, security assessments and audits, incident response, compliance, hardening, and forensics readiness
- Supports multiple compliance frameworks including CIS, NIST 800, NIST CSF, CISA, FedRAMP, PCI-DSS, GDPR, HIPAA, FFIEC, SOC2, GXP, Well-Architected Security, ENS, and more. These compliance frameworks are not available for all providers.
@@ -279,7 +279,7 @@ const userInfoAgentPrompt = `You are Prowler's User Info Agent, specializing in
- Mentioning all keys in the function call is mandatory. Don't skip any keys.
- Don't add empty filters in the function call.`;
const providerAgentPrompt = `You are Prowler's Provider Agent, specializing in provider information within the Prowler tool. Prowler supports the following provider types: AWS, GCP, Azure, and other cloud platforms.
const providerAgentPrompt = `You are Prowler's Provider Agent, specializing in provider information within the Prowler tool. Prowler supports the following provider types: AWS, GCP, Azure, GitHub, and other cloud platforms.
## Available Tools
+1 -1
View File
@@ -19,7 +19,7 @@ export const getProviderChecksTool = tool(
{
name: "getProviderChecks",
description:
"Returns a list of available checks for a specific provider (aws, gcp, azure, kubernetes). Allows filtering by service, severity, and compliance framework ID. If no filters are provided, all checks will be returned.",
"Returns a list of available checks for a specific provider (aws, gcp, azure, kubernetes, github). Allows filtering by service, severity, and compliance framework ID. If no filters are provided, all checks will be returned.",
schema: checkSchema,
},
);
@@ -147,6 +147,28 @@ export const buildKubernetesSecret = (formData: FormData) => {
return filterEmptyValues(secret);
};
export const buildGitHubSecret = (formData: FormData) => {
const secret = {
[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: getFormValue(
formData,
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN,
),
[ProviderCredentialFields.OAUTH_APP_TOKEN]: getFormValue(
formData,
ProviderCredentialFields.OAUTH_APP_TOKEN,
),
[ProviderCredentialFields.GITHUB_APP_ID]: getFormValue(
formData,
ProviderCredentialFields.GITHUB_APP_ID,
),
[ProviderCredentialFields.GITHUB_APP_KEY]: getFormValue(
formData,
ProviderCredentialFields.GITHUB_APP_KEY,
),
};
return filterEmptyValues(secret);
};
// Main function to build secret configuration
export const buildSecretConfig = (
formData: FormData,
@@ -177,6 +199,10 @@ export const buildSecretConfig = (
secretType: "static",
secret: buildKubernetesSecret(formData),
}),
github: () => ({
secretType: "static",
secret: buildGitHubSecret(formData),
}),
};
const builder = secretBuilders[providerType];
@@ -35,6 +35,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 +65,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];
+10 -1
View File
@@ -235,13 +235,22 @@ export type KubernetesCredentials = {
[ProviderCredentialFields.PROVIDER_ID]: string;
};
export type GitHubCredentials = {
[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]?: string;
[ProviderCredentialFields.OAUTH_APP_TOKEN]?: string;
[ProviderCredentialFields.GITHUB_APP_ID]?: number;
[ProviderCredentialFields.GITHUB_APP_KEY]?: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
export type CredentialsFormSchema =
| AWSCredentials
| AzureCredentials
| GCPDefaultCredentials
| GCPServiceAccountKey
| KubernetesCredentials
| M365Credentials;
| M365Credentials
| GitHubCredentials;
export interface SearchParamsProps {
[key: string]: string | string[] | undefined;
+27 -4
View File
@@ -68,9 +68,12 @@ export const awsCredentialsTypeSchema = z.object({
export const addProviderFormSchema = z
.object({
providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
required_error: "Please select a provider type",
}),
providerType: z.enum(
["aws", "azure", "gcp", "kubernetes", "m365", "github"],
{
required_error: "Please select a provider type",
},
),
})
.and(
z.discriminatedUnion("providerType", [
@@ -102,6 +105,11 @@ 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(),
}),
]),
);
@@ -164,7 +172,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
.number()
.optional(),
[ProviderCredentialFields.GITHUB_APP_KEY]: z
.string()
.optional(),
}
: {}),
})
.superRefine((data: Record<string, any>, ctx) => {
if (providerType === "m365") {
+1 -1
View File
@@ -1,7 +1,7 @@
import { z } from "zod";
export const checkSchema = z.object({
providerType: z.enum(["aws", "gcp", "azure", "kubernetes", "m365"]),
providerType: z.enum(["aws", "gcp", "azure", "kubernetes", "m365", "github"]),
service: z.array(z.string()).optional(),
severity: z
.array(z.enum(["informational", "low", "medium", "high", "critical"]))
+1 -1
View File
@@ -89,7 +89,7 @@ export const getCompliancesOverviewSchema = z.object({
export const getComplianceFrameworksSchema = z.object({
providerType: z
.enum(["aws", "azure", "gcp", "kubernetes", "m365"])
.enum(["aws", "azure", "gcp", "kubernetes", "m365", "github"])
.describe("The provider type to get the compliance frameworks for."),
});
+8 -1
View File
@@ -2,7 +2,14 @@ import { z } from "zod";
// Get Providers Schema
const providerEnum = z.enum(["", "aws", "azure", "gcp", "kubernetes"]);
const providerEnum = z.enum([
"",
"aws",
"azure",
"gcp",
"kubernetes",
"github",
]);
const sortFieldsEnum = z.enum([
"",
+8 -1
View File
@@ -37,7 +37,14 @@ const resourceSortEnum = z.enum([
"-updated_at",
]);
const providerTypeEnum = z.enum(["", "aws", "gcp", "azure", "kubernetes"]);
const providerTypeEnum = z.enum([
"",
"aws",
"gcp",
"azure",
"kubernetes",
"github",
]);
export const getResourcesSchema = z.object({
page: z.number().optional().describe("The page number to fetch."),
+8 -1
View File
@@ -1,6 +1,13 @@
import { z } from "zod";
const providerTypeEnum = z.enum(["", "aws", "azure", "gcp", "kubernetes"]);
const providerTypeEnum = z.enum([
"",
"aws",
"azure",
"gcp",
"kubernetes",
"github",
]);
const stateEnum = z.enum([
"",
"available",
+7 -1
View File
@@ -1,4 +1,10 @@
export type ProviderType = "aws" | "azure" | "m365" | "gcp" | "kubernetes";
export type ProviderType =
| "aws"
| "azure"
| "m365"
| "gcp"
| "kubernetes"
| "github";
export interface ProviderProps {
id: string;