mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-22 03:08:23 +00:00
feat(ui): add OpenStack provider support (#10046)
Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ea60f2d082
commit
40f6a7133d
@@ -6,6 +6,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- OpenStack provider support in the UI [(#10046)](https://github.com/prowler-cloud/prowler/pull/10046)
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
} from "@/components/icons/providers-badge";
|
||||
import {
|
||||
@@ -36,6 +37,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
|
||||
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
|
||||
mongodbatlas: <MongoDBAtlasProviderBadge width={18} height={18} />,
|
||||
alibabacloud: <AlibabaCloudProviderBadge width={18} height={18} />,
|
||||
openstack: <OpenStackProviderBadge width={18} height={18} />,
|
||||
};
|
||||
|
||||
interface AccountsSelectorProps {
|
||||
|
||||
@@ -63,6 +63,11 @@ const AlibabaCloudProviderBadge = lazy(() =>
|
||||
default: m.AlibabaCloudProviderBadge,
|
||||
})),
|
||||
);
|
||||
const OpenStackProviderBadge = lazy(() =>
|
||||
import("@/components/icons/providers-badge").then((m) => ({
|
||||
default: m.OpenStackProviderBadge,
|
||||
})),
|
||||
);
|
||||
|
||||
type IconProps = { width: number; height: number };
|
||||
|
||||
@@ -114,6 +119,10 @@ const PROVIDER_DATA: Record<
|
||||
label: "Alibaba Cloud",
|
||||
icon: AlibabaCloudProviderBadge,
|
||||
},
|
||||
openstack: {
|
||||
label: "OpenStack",
|
||||
icon: OpenStackProviderBadge,
|
||||
},
|
||||
};
|
||||
|
||||
type ProviderTypeSelectorProps = {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
|
||||
@@ -100,3 +101,12 @@ export const CustomProviderInputAlibabaCloud = () => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomProviderInputOpenStack = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<OpenStackProviderBadge width={25} height={25} />
|
||||
<p className="text-sm">OpenStack</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
} from "@/components/icons/providers-badge";
|
||||
import { ProviderType } from "@/types";
|
||||
@@ -23,6 +24,7 @@ export const PROVIDER_ICONS = {
|
||||
oraclecloud: OracleCloudProviderBadge,
|
||||
mongodbatlas: MongoDBAtlasProviderBadge,
|
||||
alibabacloud: AlibabaCloudProviderBadge,
|
||||
openstack: OpenStackProviderBadge,
|
||||
} as const;
|
||||
|
||||
interface ProviderIconCellProps {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { IacProviderBadge } from "./iac-provider-badge";
|
||||
import { KS8ProviderBadge } from "./ks8-provider-badge";
|
||||
import { M365ProviderBadge } from "./m365-provider-badge";
|
||||
import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge";
|
||||
import { OpenStackProviderBadge } from "./openstack-provider-badge";
|
||||
import { OracleCloudProviderBadge } from "./oraclecloud-provider-badge";
|
||||
|
||||
export {
|
||||
@@ -23,6 +24,7 @@ export {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
};
|
||||
|
||||
@@ -38,4 +40,5 @@ export const PROVIDER_ICONS: Record<string, FC<IconSvgProps>> = {
|
||||
"Oracle Cloud Infrastructure": OracleCloudProviderBadge,
|
||||
"MongoDB Atlas": MongoDBAtlasProviderBadge,
|
||||
"Alibaba Cloud": AlibabaCloudProviderBadge,
|
||||
OpenStack: OpenStackProviderBadge,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { IconSvgProps } from "@/types";
|
||||
|
||||
export const OpenStackProviderBadge: 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 256 256"
|
||||
width={size || width}
|
||||
{...props}
|
||||
>
|
||||
<g fill="none">
|
||||
<rect width="256" height="256" fill="#f4f2ed" rx="60" />
|
||||
<g transform="translate(48 48) scale(2.5)" fill="#da1a32">
|
||||
<path d="M58.054.68H5.946C2.676.68 0 3.356 0 6.626V20.64h14.452v-2.3c0-1.776 1.44-3.215 3.215-3.215h28.665c1.776 0 3.215 1.44 3.215 3.215v2.3H64v-14A5.97 5.97 0 0 0 58.054.68zm-8.506 44.97c0 1.776-1.44 3.215-3.215 3.215H17.67c-1.776 0-3.215-1.44-3.215-3.215v-2.3H0v14.013c0 3.27 2.676 5.946 5.946 5.946h52.108c3.27 0 5.946-2.676 5.946-5.946V43.36H49.548zM0 24.773h14.452v14.452H0zm49.548 0H64v14.452H49.548z" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
} from "../icons/providers-badge";
|
||||
import { FormMessage } from "../ui/form";
|
||||
@@ -73,6 +74,11 @@ const PROVIDERS = [
|
||||
label: "Alibaba Cloud",
|
||||
badge: AlibabaCloudProviderBadge,
|
||||
},
|
||||
{
|
||||
value: "openstack",
|
||||
label: "OpenStack",
|
||||
badge: OpenStackProviderBadge,
|
||||
},
|
||||
] as const;
|
||||
|
||||
interface RadioGroupProviderProps {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
M365ClientSecretCredentials,
|
||||
MongoDBAtlasCredentials,
|
||||
OCICredentials,
|
||||
OpenStackCredentials,
|
||||
ProviderType,
|
||||
} from "@/types";
|
||||
|
||||
@@ -46,6 +47,7 @@ import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form
|
||||
import { IacCredentialsForm } from "./via-credentials/iac-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";
|
||||
import { OracleCloudCredentialsForm } from "./via-credentials/oraclecloud-credentials-form";
|
||||
|
||||
type BaseCredentialsFormProps = {
|
||||
@@ -206,6 +208,11 @@ export const BaseCredentialsForm = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{providerType === "openstack" && (
|
||||
<OpenStackCredentialsForm
|
||||
control={form.control as unknown as Control<OpenStackCredentials>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end gap-4">
|
||||
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
|
||||
|
||||
@@ -72,6 +72,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
|
||||
label: "Account ID",
|
||||
placeholder: "e.g. 1234567890123456",
|
||||
};
|
||||
case "openstack":
|
||||
return {
|
||||
label: "Project ID",
|
||||
placeholder: "e.g. a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
label: "Provider UID",
|
||||
|
||||
@@ -3,3 +3,4 @@ export * from "./github-credentials-form";
|
||||
export * from "./iac-credentials-form";
|
||||
export * from "./k8s-credentials-form";
|
||||
export * from "./mongodbatlas-credentials-form";
|
||||
export * from "./openstack-credentials-form";
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import { OpenStackCredentials } from "@/types";
|
||||
|
||||
export const OpenStackCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<OpenStackCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md text-default-foreground leading-9 font-bold">
|
||||
Connect via Clouds YAML
|
||||
</div>
|
||||
<div className="text-default-500 text-sm">
|
||||
Please provide your OpenStack clouds.yaml content and the cloud name.
|
||||
</div>
|
||||
</div>
|
||||
<CustomTextarea
|
||||
control={control}
|
||||
name="clouds_yaml_content"
|
||||
label="Clouds YAML Content"
|
||||
labelPlacement="inside"
|
||||
placeholder="Paste your clouds.yaml content here"
|
||||
variant="bordered"
|
||||
minRows={10}
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="clouds_yaml_cloud"
|
||||
type="text"
|
||||
label="Cloud Name"
|
||||
labelPlacement="inside"
|
||||
placeholder="e.g. mycloud"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
KS8ProviderBadge,
|
||||
M365ProviderBadge,
|
||||
MongoDBAtlasProviderBadge,
|
||||
OpenStackProviderBadge,
|
||||
OracleCloudProviderBadge,
|
||||
} from "@/components/icons/providers-badge";
|
||||
import { ProviderType } from "@/types";
|
||||
@@ -34,6 +35,8 @@ export const getProviderLogo = (provider: ProviderType) => {
|
||||
return <MongoDBAtlasProviderBadge width={35} height={35} />;
|
||||
case "alibabacloud":
|
||||
return <AlibabaCloudProviderBadge width={35} height={35} />;
|
||||
case "openstack":
|
||||
return <OpenStackProviderBadge width={35} height={35} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -61,6 +64,8 @@ export const getProviderName = (provider: ProviderType): string => {
|
||||
return "MongoDB Atlas";
|
||||
case "alibabacloud":
|
||||
return "Alibaba Cloud";
|
||||
case "openstack":
|
||||
return "OpenStack";
|
||||
default:
|
||||
return "Unknown Provider";
|
||||
}
|
||||
|
||||
@@ -192,6 +192,12 @@ export const useCredentialsForm = ({
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID]: "",
|
||||
[ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET]: "",
|
||||
};
|
||||
case "openstack":
|
||||
return {
|
||||
...baseDefaults,
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: "",
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: "",
|
||||
};
|
||||
default:
|
||||
return baseDefaults;
|
||||
}
|
||||
|
||||
@@ -30,4 +30,8 @@ export const PROVIDER_CREDENTIALS_ERROR_MAPPING: Record<string, string> = {
|
||||
ProviderCredentialFields.SERVICE_ACCOUNT_KEY,
|
||||
[ErrorPointers.ATLAS_PUBLIC_KEY]: ProviderCredentialFields.ATLAS_PUBLIC_KEY,
|
||||
[ErrorPointers.ATLAS_PRIVATE_KEY]: ProviderCredentialFields.ATLAS_PRIVATE_KEY,
|
||||
[ErrorPointers.OPENSTACK_CLOUDS_YAML_CONTENT]:
|
||||
ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT,
|
||||
[ErrorPointers.OPENSTACK_CLOUDS_YAML_CLOUD]:
|
||||
ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD,
|
||||
};
|
||||
|
||||
@@ -58,6 +58,11 @@ export const getProviderHelpText = (provider: string) => {
|
||||
text: "Need help connecting your Alibaba Cloud account?",
|
||||
link: "https://goto.prowler.com/provider-alibabacloud",
|
||||
};
|
||||
case "openstack":
|
||||
return {
|
||||
text: "Need help connecting your OpenStack cloud?",
|
||||
link: "https://goto.prowler.com/provider-openstack",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: "How to setup a provider?",
|
||||
|
||||
@@ -250,6 +250,20 @@ export const buildAlibabaCloudSecret = (
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
export const buildOpenStackSecret = (formData: FormData) => {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT,
|
||||
),
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: getFormValue(
|
||||
formData,
|
||||
ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD,
|
||||
),
|
||||
};
|
||||
return filterEmptyValues(secret);
|
||||
};
|
||||
|
||||
export const buildIacSecret = (formData: FormData) => {
|
||||
const secret = {
|
||||
[ProviderCredentialFields.REPOSITORY_URL]: getFormValue(
|
||||
@@ -373,6 +387,10 @@ export const buildSecretConfig = (
|
||||
secret: buildAlibabaCloudSecret(formData, isRole),
|
||||
};
|
||||
},
|
||||
openstack: () => ({
|
||||
secretType: "static",
|
||||
secret: buildOpenStackSecret(formData),
|
||||
}),
|
||||
};
|
||||
|
||||
const builder = secretBuilders[providerType];
|
||||
|
||||
@@ -67,6 +67,10 @@ export const ProviderCredentialFields = {
|
||||
ALIBABACLOUD_ACCESS_KEY_SECRET: "access_key_secret",
|
||||
ALIBABACLOUD_ROLE_ARN: "role_arn",
|
||||
ALIBABACLOUD_ROLE_SESSION_NAME: "role_session_name",
|
||||
|
||||
// OpenStack fields
|
||||
OPENSTACK_CLOUDS_YAML_CONTENT: "clouds_yaml_content",
|
||||
OPENSTACK_CLOUDS_YAML_CLOUD: "clouds_yaml_cloud",
|
||||
} as const;
|
||||
|
||||
// Type for credential field values
|
||||
@@ -111,6 +115,8 @@ export const ErrorPointers = {
|
||||
ALIBABACLOUD_ACCESS_KEY_SECRET: "/data/attributes/secret/access_key_secret",
|
||||
ALIBABACLOUD_ROLE_ARN: "/data/attributes/secret/role_arn",
|
||||
ALIBABACLOUD_ROLE_SESSION_NAME: "/data/attributes/secret/role_session_name",
|
||||
OPENSTACK_CLOUDS_YAML_CONTENT: "/data/attributes/secret/clouds_yaml_content",
|
||||
OPENSTACK_CLOUDS_YAML_CLOUD: "/data/attributes/secret/clouds_yaml_cloud",
|
||||
} as const;
|
||||
|
||||
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];
|
||||
|
||||
@@ -334,6 +334,12 @@ export type AlibabaCloudCredentialsRole = {
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type OpenStackCredentials = {
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]: string;
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]: string;
|
||||
[ProviderCredentialFields.PROVIDER_ID]: string;
|
||||
};
|
||||
|
||||
export type CredentialsFormSchema =
|
||||
| AWSCredentials
|
||||
| AWSCredentialsRole
|
||||
@@ -346,7 +352,8 @@ export type CredentialsFormSchema =
|
||||
| OCICredentials
|
||||
| MongoDBAtlasCredentials
|
||||
| AlibabaCloudCredentials
|
||||
| AlibabaCloudCredentialsRole;
|
||||
| AlibabaCloudCredentialsRole
|
||||
| OpenStackCredentials;
|
||||
|
||||
export interface SearchParamsProps {
|
||||
[key: string]: string | string[] | undefined;
|
||||
|
||||
@@ -130,6 +130,11 @@ export const addProviderFormSchema = z
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
providerType: z.literal("openstack"),
|
||||
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
|
||||
providerUid: z.string(),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -259,6 +264,15 @@ export const addCredentialsFormSchema = (
|
||||
.string()
|
||||
.min(1, "Access Key Secret is required"),
|
||||
}
|
||||
: providerType === "openstack"
|
||||
? {
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CONTENT]:
|
||||
z
|
||||
.string()
|
||||
.min(1, "Clouds YAML content is required"),
|
||||
[ProviderCredentialFields.OPENSTACK_CLOUDS_YAML_CLOUD]:
|
||||
z.string().min(1, "Cloud name is required"),
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
.superRefine((data: Record<string, string | undefined>, ctx) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ export const PROVIDER_TYPES = [
|
||||
"iac",
|
||||
"oraclecloud",
|
||||
"alibabacloud",
|
||||
"openstack",
|
||||
] as const;
|
||||
|
||||
export type ProviderType = (typeof PROVIDER_TYPES)[number];
|
||||
@@ -24,6 +25,7 @@ export const PROVIDER_DISPLAY_NAMES: Record<ProviderType, string> = {
|
||||
iac: "Infrastructure as Code",
|
||||
oraclecloud: "Oracle Cloud Infrastructure",
|
||||
alibabacloud: "Alibaba Cloud",
|
||||
openstack: "OpenStack",
|
||||
};
|
||||
|
||||
export function getProviderDisplayName(providerId: string): string {
|
||||
|
||||
Reference in New Issue
Block a user