mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-30 15:24:22 +00:00
feat(ui): add image (Container Registry) provider support
This commit is contained in:
1 parent
23e51158e0
commit
4b36be1f11
19 files changed
+255
-3
No files matched your search
@@ -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)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
ImageProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
@@ -33,6 +34,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} />,
|
||||
|
||||
@@ -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<IconProps> }
|
||||
{ label: string; icon: ComponentType<IconProps> }
|
||||
> = {
|
||||
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,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
ImageProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
@@ -83,6 +84,15 @@ export const CustomProviderInputIac = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomProviderInputImage = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<ImageProviderBadge width={25} height={25} />
|
||||
<p className="text-sm">Container Registry</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomProviderInputOracleCloud = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
export const ImageProviderBadge: 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 256 256"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<rect width="256" height="256" fill="#1c1917" rx="60" />
|
||||
<g
|
||||
transform="translate(20, 20) scale(9)"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<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" />
|
||||
<path d="M3.5 6L12 10.5L20.5 6" />
|
||||
<path d="M12 22.5V10.5" />
|
||||
</g>
|
||||
</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";
|
||||
@@ -20,6 +21,7 @@ export {
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
ImageProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
@@ -35,6 +37,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,
|
||||
@@ -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",
|
||||
|
||||
@@ -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<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,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";
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GCPProviderBadge,
|
||||
GitHubProviderBadge,
|
||||
IacProviderBadge,
|
||||
ImageProviderBadge,
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
@@ -28,6 +29,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":
|
||||
@@ -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":
|
||||
|
||||
@@ -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?",
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
+24
-1
@@ -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<string, string | undefined>, ctx) => {
|
||||
if (providerType === "m365") {
|
||||
|
||||
@@ -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<ProviderType, string> = {
|
||||
mongodbatlas: "MongoDB Atlas",
|
||||
github: "GitHub",
|
||||
iac: "Infrastructure as Code",
|
||||
image: "Container Registry",
|
||||
oraclecloud: "Oracle Cloud Infrastructure",
|
||||
alibabacloud: "Alibaba Cloud",
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user