diff --git a/ui/app/(prowler)/_overview/_components/accounts-selector.tsx b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx index 8536ce3fa4..1e8284f091 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, @@ -34,6 +35,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 a649bc3843..e5f571dcdf 100644 --- a/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx +++ b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx @@ -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, @@ -107,6 +112,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/findings/table/provider-icon-cell.tsx b/ui/components/findings/table/provider-icon-cell.tsx index 31c4c64475..6d9a72dfc2 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, @@ -21,6 +22,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..9690066843 --- /dev/null +++ b/ui/components/icons/providers-badge/image-provider-badge.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const ImageProviderBadge: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/ui/components/icons/providers-badge/index.ts b/ui/components/icons/providers-badge/index.ts index dbc86a840a..45871e6ed7 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"; @@ -21,6 +22,7 @@ export { GCPProviderBadge, GitHubProviderBadge, IacProviderBadge, + ImageProviderBadge, KS8ProviderBadge, M365ProviderBadge, MongoDBAtlasProviderBadge, @@ -37,6 +39,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 475df042db..a73d49d8e0 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, @@ -64,6 +65,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 ddfeb1ead2..d40bccd6c1 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, @@ -45,6 +46,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 { OpenStackCredentialsForm } from "./via-credentials/openstack-credentials-form"; @@ -180,6 +182,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 15c4d7f8d1..51b8de087f 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 2b44d85485..4f5b55846a 100644 --- a/ui/components/providers/workflow/forms/via-credentials/index.ts +++ b/ui/components/providers/workflow/forms/via-credentials/index.ts @@ -1,6 +1,7 @@ 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"; export * from "./openstack-credentials-form"; diff --git a/ui/components/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx index 7a0e649455..76aaf6a00b 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, @@ -29,6 +30,8 @@ export const getProviderLogo = (provider: ProviderType) => { return ; case "iac": return ; + case "image": + return ; case "oraclecloud": return ; case "mongodbatlas": @@ -58,6 +61,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 8c55eb35c5..cc1c1c8069 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 4d25fd68f9..8d95f44e9f 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -278,6 +278,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 @@ -371,6 +397,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 637565e312..2f112bcad5 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", @@ -101,6 +108,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 ea2813a8b4..cb97d3518e 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; @@ -348,6 +357,7 @@ export type CredentialsFormSchema = | GCPServiceAccountKey | KubernetesCredentials | IacCredentials + | ImageCredentials | M365Credentials | OCICredentials | MongoDBAtlasCredentials diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 36c4e724d0..7a348b7983 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(), @@ -225,44 +230,53 @@ export const addCredentialsFormSchema = ( .string() .optional(), } - : providerType === "oraclecloud" + : providerType === "image" ? { - [ProviderCredentialFields.OCI_USER]: z + [ProviderCredentialFields.REGISTRY_USERNAME]: z .string() - .min(1, "User OCID is required"), - [ProviderCredentialFields.OCI_FINGERPRINT]: z + .optional(), + [ProviderCredentialFields.REGISTRY_PASSWORD]: z .string() - .min(1, "Fingerprint is required"), - [ProviderCredentialFields.OCI_KEY_CONTENT]: z + .optional(), + [ProviderCredentialFields.REGISTRY_TOKEN]: z .string() - .min(1, "Private Key Content is required"), - [ProviderCredentialFields.OCI_TENANCY]: z + .optional(), + [ProviderCredentialFields.IMAGE_FILTER]: z .string() - .min(1, "Tenancy OCID is required"), - [ProviderCredentialFields.OCI_REGION]: z + .optional(), + [ProviderCredentialFields.TAG_FILTER]: z .string() - .min(1, "Region is required"), - [ProviderCredentialFields.OCI_PASS_PHRASE]: z - .union([z.string(), z.literal("")]) .optional(), } - : providerType === "mongodbatlas" + : providerType === "oraclecloud" ? { - [ProviderCredentialFields.ATLAS_PUBLIC_KEY]: z + [ProviderCredentialFields.OCI_USER]: z .string() - .min(1, "Atlas Public Key is required"), - [ProviderCredentialFields.ATLAS_PRIVATE_KEY]: z + .min(1, "User OCID is required"), + [ProviderCredentialFields.OCI_FINGERPRINT]: z .string() - .min(1, "Atlas Private Key is required"), + .min(1, "Fingerprint is required"), + [ProviderCredentialFields.OCI_KEY_CONTENT]: z + .string() + .min(1, "Private Key Content is required"), + [ProviderCredentialFields.OCI_TENANCY]: z + .string() + .min(1, "Tenancy OCID is required"), + [ProviderCredentialFields.OCI_REGION]: z + .string() + .min(1, "Region is required"), + [ProviderCredentialFields.OCI_PASS_PHRASE]: z + .union([z.string(), z.literal("")]) + .optional(), } - : providerType === "alibabacloud" + : providerType === "mongodbatlas" ? { - [ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: - z.string().min(1, "Access Key ID is required"), - [ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: - z - .string() - .min(1, "Access Key Secret is required"), + [ProviderCredentialFields.ATLAS_PUBLIC_KEY]: z + .string() + .min(1, "Atlas Public Key is required"), + [ProviderCredentialFields.ATLAS_PRIVATE_KEY]: z + .string() + .min(1, "Atlas Private Key is required"), } : providerType === "openstack" ? { diff --git a/ui/types/providers.ts b/ui/types/providers.ts index 6b164c7b83..3bec6054b4 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", "openstack", @@ -23,6 +24,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", openstack: "OpenStack",