diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index ae3d1bfbba..aea3559539 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added +- Image (Container Registry) provider support in UI: badge icon, credentials form, and provider-type filtering - PDF report available for the CSA CCM compliance framework [(#10088)](https://github.com/prowler-cloud/prowler/pull/10088) - CSV and PDF download buttons in compliance views [(#10093)](https://github.com/prowler-cloud/prowler/pull/10093) diff --git a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx index 98bb9df9f5..6ceb9ea7f9 100644 --- a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx @@ -10,6 +10,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -33,6 +34,7 @@ const PROVIDER_ICON: Record = { m365: , github: , iac: , + image: , oraclecloud: , mongodbatlas: , alibabacloud: , diff --git a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx index d9457a499b..1604e3af5f 100644 --- a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx @@ -1,7 +1,7 @@ "use client"; import { useSearchParams } from "next/navigation"; -import { lazy, Suspense } from "react"; +import { type ComponentType, lazy, Suspense } from "react"; import { MultiSelect, @@ -48,6 +48,11 @@ const IacProviderBadge = lazy(() => default: m.IacProviderBadge, })), ); +const ImageProviderBadge = lazy(() => + import("@/components/icons/providers-badge").then((m) => ({ + default: m.ImageProviderBadge, + })), +); const OracleCloudProviderBadge = lazy(() => import("@/components/icons/providers-badge").then((m) => ({ default: m.OracleCloudProviderBadge, @@ -72,7 +77,7 @@ const IconPlaceholder = ({ width, height }: IconProps) => ( const PROVIDER_DATA: Record< ProviderType, - { label: string; icon: React.ComponentType } + { label: string; icon: ComponentType } > = { aws: { label: "Amazon Web Services", @@ -102,6 +107,10 @@ const PROVIDER_DATA: Record< label: "Infrastructure as Code", icon: IacProviderBadge, }, + image: { + label: "Container Registry", + icon: ImageProviderBadge, + }, oraclecloud: { label: "Oracle Cloud Infrastructure", icon: OracleCloudProviderBadge, diff --git a/ui/components/filters/custom-provider-inputs.tsx b/ui/components/filters/custom-provider-inputs.tsx index 47fe62e3aa..6452c30ddb 100644 --- a/ui/components/filters/custom-provider-inputs.tsx +++ b/ui/components/filters/custom-provider-inputs.tsx @@ -5,6 +5,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -83,6 +84,15 @@ export const CustomProviderInputIac = () => { ); }; +export const CustomProviderInputImage = () => { + return ( +
+ +

Container Registry

+
+ ); +}; + export const CustomProviderInputOracleCloud = () => { return (
diff --git a/ui/components/findings/table/provider-icon-cell.tsx b/ui/components/findings/table/provider-icon-cell.tsx index a04341b4d2..984a460e6f 100644 --- a/ui/components/findings/table/provider-icon-cell.tsx +++ b/ui/components/findings/table/provider-icon-cell.tsx @@ -5,6 +5,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -20,6 +21,7 @@ export const PROVIDER_ICONS = { m365: M365ProviderBadge, github: GitHubProviderBadge, iac: IacProviderBadge, + image: ImageProviderBadge, oraclecloud: OracleCloudProviderBadge, mongodbatlas: MongoDBAtlasProviderBadge, alibabacloud: AlibabaCloudProviderBadge, diff --git a/ui/components/icons/providers-badge/image-provider-badge.tsx b/ui/components/icons/providers-badge/image-provider-badge.tsx new file mode 100644 index 0000000000..dc5d27c7e1 --- /dev/null +++ b/ui/components/icons/providers-badge/image-provider-badge.tsx @@ -0,0 +1,36 @@ +import { FC } from "react"; + +import { IconSvgProps } from "@/types"; + +export const ImageProviderBadge: FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/ui/components/icons/providers-badge/index.ts b/ui/components/icons/providers-badge/index.ts index 58f5a0b260..50b7652bac 100644 --- a/ui/components/icons/providers-badge/index.ts +++ b/ui/components/icons/providers-badge/index.ts @@ -8,6 +8,7 @@ import { AzureProviderBadge } from "./azure-provider-badge"; import { GCPProviderBadge } from "./gcp-provider-badge"; import { GitHubProviderBadge } from "./github-provider-badge"; import { IacProviderBadge } from "./iac-provider-badge"; +import { ImageProviderBadge } from "./image-provider-badge"; import { KS8ProviderBadge } from "./ks8-provider-badge"; import { M365ProviderBadge } from "./m365-provider-badge"; import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge"; @@ -20,6 +21,7 @@ export { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -35,6 +37,7 @@ export const PROVIDER_ICONS: Record> = { "Microsoft 365": M365ProviderBadge, GitHub: GitHubProviderBadge, "Infrastructure as Code": IacProviderBadge, + "Container Registry": ImageProviderBadge, "Oracle Cloud Infrastructure": OracleCloudProviderBadge, "MongoDB Atlas": MongoDBAtlasProviderBadge, "Alibaba Cloud": AlibabaCloudProviderBadge, diff --git a/ui/components/providers/radio-group-provider.tsx b/ui/components/providers/radio-group-provider.tsx index de4f8596b6..e2e04be23e 100644 --- a/ui/components/providers/radio-group-provider.tsx +++ b/ui/components/providers/radio-group-provider.tsx @@ -15,6 +15,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -63,6 +64,11 @@ const PROVIDERS = [ label: "Infrastructure as Code", badge: IacProviderBadge, }, + { + value: "image", + label: "Container Registry", + badge: ImageProviderBadge, + }, { value: "oraclecloud", label: "Oracle Cloud Infrastructure", diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index d200a1e1c8..7054046ec1 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -20,6 +20,7 @@ import { GCPDefaultCredentials, GCPServiceAccountKey, IacCredentials, + ImageCredentials, KubernetesCredentials, M365CertificateCredentials, M365ClientSecretCredentials, @@ -44,6 +45,7 @@ import { import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; import { IacCredentialsForm } from "./via-credentials/iac-credentials-form"; +import { ImageCredentialsForm } from "./via-credentials/image-credentials-form"; import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form"; import { MongoDBAtlasCredentialsForm } from "./via-credentials/mongodbatlas-credentials-form"; import { OracleCloudCredentialsForm } from "./via-credentials/oraclecloud-credentials-form"; @@ -178,6 +180,11 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} + {providerType === "image" && ( + } + /> + )} {providerType === "oraclecloud" && ( } diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index b6b50ead52..65f07c4c2b 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -57,6 +57,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => { label: "Repository URL", placeholder: "e.g. https://github.com/user/repo", }; + case "image": + return { + label: "Registry URL", + placeholder: "e.g. https://registry.example.com", + }; case "oraclecloud": return { label: "Tenancy OCID", diff --git a/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx new file mode 100644 index 0000000000..737971a22e --- /dev/null +++ b/ui/components/providers/workflow/forms/via-credentials/image-credentials-form.tsx @@ -0,0 +1,83 @@ +import { Control } from "react-hook-form"; + +import { CustomInput } from "@/components/ui/custom"; +import { ImageCredentials } from "@/types"; + +export const ImageCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ Connect via Registry Credentials +
+
+ Provide registry credentials to authenticate with your container + registry (all fields are optional). +
+
+ + + + +
+
+ Scan Scope +
+
+ Limit which repositories and tags are scanned using regex patterns. +
+
+ + + + ); +}; diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts index 6c4c7dfeae..86d1771bfd 100644 --- a/ui/components/providers/workflow/forms/via-credentials/index.ts +++ b/ui/components/providers/workflow/forms/via-credentials/index.ts @@ -1,5 +1,6 @@ export * from "./azure-credentials-form"; export * from "./github-credentials-form"; export * from "./iac-credentials-form"; +export * from "./image-credentials-form"; export * from "./k8s-credentials-form"; export * from "./mongodbatlas-credentials-form"; diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx index 54520b9f86..1d03149fb2 100644 --- a/ui/components/ui/entities/get-provider-logo.tsx +++ b/ui/components/ui/entities/get-provider-logo.tsx @@ -5,6 +5,7 @@ import { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -28,6 +29,8 @@ export const getProviderLogo = (provider: ProviderType) => { return ; case "iac": return ; + case "image": + return ; case "oraclecloud": return ; case "mongodbatlas": @@ -55,6 +58,8 @@ export const getProviderName = (provider: ProviderType): string => { return "GitHub"; case "iac": return "Infrastructure as Code"; + case "image": + return "Container Registry"; case "oraclecloud": return "Oracle Cloud Infrastructure"; case "mongodbatlas": diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts index db3ab59231..4adc7361aa 100644 --- a/ui/lib/external-urls.ts +++ b/ui/lib/external-urls.ts @@ -43,6 +43,11 @@ export const getProviderHelpText = (provider: string) => { text: "Need help scanning your Infrastructure as Code repository?", link: "https://goto.prowler.com/provider-iac", }; + case "image": + return { + text: "Need help scanning your container registry?", + link: "https://goto.prowler.com/provider-image", + }; case "oraclecloud": return { text: "Need help connecting your Oracle Cloud account?", diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index b48492c804..68dd341ed9 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -264,6 +264,32 @@ export const buildIacSecret = (formData: FormData) => { return filterEmptyValues(secret); }; +export const buildImageSecret = (formData: FormData) => { + const secret = { + [ProviderCredentialFields.REGISTRY_USERNAME]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_USERNAME, + ), + [ProviderCredentialFields.REGISTRY_PASSWORD]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_PASSWORD, + ), + [ProviderCredentialFields.REGISTRY_TOKEN]: getFormValue( + formData, + ProviderCredentialFields.REGISTRY_TOKEN, + ), + [ProviderCredentialFields.IMAGE_FILTER]: getFormValue( + formData, + ProviderCredentialFields.IMAGE_FILTER, + ), + [ProviderCredentialFields.TAG_FILTER]: getFormValue( + formData, + ProviderCredentialFields.TAG_FILTER, + ), + }; + return filterEmptyValues(secret); +}; + /** * Utility function to safely encode a string to base64 * Handles UTF-8 characters properly without using deprecated APIs @@ -357,6 +383,10 @@ export const buildSecretConfig = ( secretType: "static", secret: buildIacSecret(formData), }), + image: () => ({ + secretType: "static", + secret: buildImageSecret(formData), + }), oraclecloud: () => ({ secretType: "static", secret: buildOracleCloudSecret(formData, providerUid), diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts index 8feb7434c7..1e43f734df 100644 --- a/ui/lib/provider-credentials/provider-credential-fields.ts +++ b/ui/lib/provider-credentials/provider-credential-fields.ts @@ -53,6 +53,13 @@ export const ProviderCredentialFields = { REPOSITORY_URL: "repository_url", ACCESS_TOKEN: "access_token", + // Image (Container Registry) fields + REGISTRY_USERNAME: "registry_username", + REGISTRY_PASSWORD: "registry_password", + REGISTRY_TOKEN: "registry_token", + IMAGE_FILTER: "image_filter", + TAG_FILTER: "tag_filter", + // OCI fields OCI_USER: "user", OCI_FINGERPRINT: "fingerprint", @@ -97,6 +104,11 @@ export const ErrorPointers = { GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content", REPOSITORY_URL: "/data/attributes/secret/repository_url", ACCESS_TOKEN: "/data/attributes/secret/access_token", + REGISTRY_USERNAME: "/data/attributes/secret/registry_username", + REGISTRY_PASSWORD: "/data/attributes/secret/registry_password", + REGISTRY_TOKEN: "/data/attributes/secret/registry_token", + IMAGE_FILTER: "/data/attributes/secret/image_filter", + TAG_FILTER: "/data/attributes/secret/tag_filter", CERTIFICATE_CONTENT: "/data/attributes/secret/certificate_content", OCI_USER: "/data/attributes/secret/user", OCI_FINGERPRINT: "/data/attributes/secret/fingerprint", diff --git a/ui/types/components.ts b/ui/types/components.ts index ad650e74da..5cc7fc90ec 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -304,6 +304,15 @@ export type IacCredentials = { [ProviderCredentialFields.PROVIDER_ID]: string; }; +export type ImageCredentials = { + [ProviderCredentialFields.REGISTRY_USERNAME]?: string; + [ProviderCredentialFields.REGISTRY_PASSWORD]?: string; + [ProviderCredentialFields.REGISTRY_TOKEN]?: string; + [ProviderCredentialFields.IMAGE_FILTER]?: string; + [ProviderCredentialFields.TAG_FILTER]?: string; + [ProviderCredentialFields.PROVIDER_ID]: string; +}; + export type OCICredentials = { [ProviderCredentialFields.OCI_USER]: string; [ProviderCredentialFields.OCI_FINGERPRINT]: string; @@ -342,6 +351,7 @@ export type CredentialsFormSchema = | GCPServiceAccountKey | KubernetesCredentials | IacCredentials + | ImageCredentials | M365Credentials | OCICredentials | MongoDBAtlasCredentials diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 5207e8b926..e476e3ce7b 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -115,6 +115,11 @@ export const addProviderFormSchema = z [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), providerUid: z.string(), }), + z.object({ + providerType: z.literal("image"), + [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), + providerUid: z.string(), + }), z.object({ providerType: z.literal("oraclecloud"), [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), @@ -259,7 +264,25 @@ export const addCredentialsFormSchema = ( .string() .min(1, "Access Key Secret is required"), } - : {}), + : providerType === "image" + ? { + [ProviderCredentialFields.REGISTRY_USERNAME]: z + .string() + .optional(), + [ProviderCredentialFields.REGISTRY_PASSWORD]: z + .string() + .optional(), + [ProviderCredentialFields.REGISTRY_TOKEN]: z + .string() + .optional(), + [ProviderCredentialFields.IMAGE_FILTER]: z + .string() + .optional(), + [ProviderCredentialFields.TAG_FILTER]: z + .string() + .optional(), + } + : {}), }) .superRefine((data: Record, ctx) => { if (providerType === "m365") { diff --git a/ui/types/providers.ts b/ui/types/providers.ts index e4a5d3aae7..9e8248b5d6 100644 --- a/ui/types/providers.ts +++ b/ui/types/providers.ts @@ -7,6 +7,7 @@ export const PROVIDER_TYPES = [ "mongodbatlas", "github", "iac", + "image", "oraclecloud", "alibabacloud", ] as const; @@ -22,6 +23,7 @@ export const PROVIDER_DISPLAY_NAMES: Record = { mongodbatlas: "MongoDB Atlas", github: "GitHub", iac: "Infrastructure as Code", + image: "Container Registry", oraclecloud: "Oracle Cloud Infrastructure", alibabacloud: "Alibaba Cloud", };