feat(ui): add image provider support to UI

This commit is contained in:
Andoni A. committed 2026-02-23 11:39:16 +01:00
1 parent 18bac5a890
commit 1ec79831d7
17 files changed
+265 -25

No files matched your search

@@ -10,6 +10,7 @@ import {
GCPProviderBadge,
GitHubProviderBadge,
IacProviderBadge,
ImageProviderBadge,
KS8ProviderBadge,
M365ProviderBadge,
MongoDBAtlasProviderBadge,
@@ -34,6 +35,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
m365: <M365ProviderBadge width={18} height={18} />,
github: <GitHubProviderBadge width={18} height={18} />,
iac: <IacProviderBadge width={18} height={18} />,
image: <ImageProviderBadge width={18} height={18} />,
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
mongodbatlas: <MongoDBAtlasProviderBadge width={18} height={18} />,
alibabacloud: <AlibabaCloudProviderBadge width={18} height={18} />,
@@ -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,
@@ -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,
@@ -0,0 +1,44 @@
import * as React from "react";
import { IconSvgProps } from "@/types";
export const ImageProviderBadge: 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 24 24"
width={size || width}
{...props}
>
<path
d="M12.89 1.45L21 5.75V18.25L12.89 22.55C12.33 22.84 11.67 22.84 11.11 22.55L3 18.25V5.75L11.11 1.45C11.67 1.16 12.33 1.16 12.89 1.45Z"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.5 6L12 10.5L20.5 6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12 22.5V10.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
@@ -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<string, FC<IconSvgProps>> = {
"Microsoft 365": M365ProviderBadge,
GitHub: GitHubProviderBadge,
"Infrastructure as Code": IacProviderBadge,
"Container Registry": ImageProviderBadge,
"Oracle Cloud Infrastructure": OracleCloudProviderBadge,
"MongoDB Atlas": MongoDBAtlasProviderBadge,
"Alibaba Cloud": AlibabaCloudProviderBadge,
@@ -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",
@@ -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<IacCredentials>}
/>
)}
{providerType === "image" && (
<ImageCredentialsForm
control={form.control as unknown as Control<ImageCredentials>}
/>
)}
{providerType === "oraclecloud" && (
<OracleCloudCredentialsForm
control={form.control as unknown as Control<OCICredentials>}
@@ -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",
@@ -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<ImageCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md text-default-foreground leading-9 font-bold">
Connect via Registry Credentials
</div>
<div className="text-default-500 text-sm">
Provide registry credentials to authenticate with your container
registry (all fields are optional).
</div>
</div>
<CustomInput
control={control}
name="registry_username"
label="Registry Username (Optional)"
labelPlacement="inside"
placeholder="Username for registry authentication"
variant="bordered"
type="text"
isRequired={false}
/>
<CustomInput
control={control}
name="registry_password"
label="Registry Password (Optional)"
labelPlacement="inside"
placeholder="Password for registry authentication"
variant="bordered"
type="password"
isRequired={false}
/>
<CustomInput
control={control}
name="registry_token"
label="Registry Token (Optional)"
labelPlacement="inside"
placeholder="Token for registry authentication"
variant="bordered"
type="password"
isRequired={false}
/>
<div className="flex flex-col pt-2">
<div className="text-md text-default-foreground leading-9 font-bold">
Scan Scope
</div>
<div className="text-default-500 text-sm">
Limit which repositories and tags are scanned using regex patterns.
</div>
</div>
<CustomInput
control={control}
name="image_filter"
label="Image Filter (Optional)"
labelPlacement="inside"
placeholder="e.g. ^prod/.*"
variant="bordered"
type="text"
isRequired={false}
/>
<CustomInput
control={control}
name="tag_filter"
label="Tag Filter (Optional)"
labelPlacement="inside"
placeholder="e.g. ^(latest|v\d+\.\d+\.\d+)$"
variant="bordered"
type="text"
isRequired={false}
/>
</>
);
};
@@ -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";
@@ -5,6 +5,7 @@ import {
GCPProviderBadge,
GitHubProviderBadge,
IacProviderBadge,
ImageProviderBadge,
KS8ProviderBadge,
M365ProviderBadge,
MongoDBAtlasProviderBadge,
@@ -29,6 +30,8 @@ export const getProviderLogo = (provider: ProviderType) => {
return <GitHubProviderBadge width={35} height={35} />;
case "iac":
return <IacProviderBadge width={35} height={35} />;
case "image":
return <ImageProviderBadge width={35} height={35} />;
case "oraclecloud":
return <OracleCloudProviderBadge width={35} height={35} />;
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":
+5
View File
@@ -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?",
@@ -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),
@@ -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",
+10
View File
@@ -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
+39 -25
View File
@@ -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"
? {
+2
View File
@@ -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<ProviderType, string> = {
mongodbatlas: "MongoDB Atlas",
github: "GitHub",
iac: "Infrastructure as Code",
image: "Container Registry",
oraclecloud: "Oracle Cloud Infrastructure",
alibabacloud: "Alibaba Cloud",
openstack: "OpenStack",