feat(ui): add MongoDB Atlas provider support (#9253)

Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com>
This commit is contained in:
Daniel Barranquero
2025-11-27 12:37:20 +01:00
committed by GitHub
parent 6e135abaa0
commit ed5f6b3af6
31 changed files with 256 additions and 179 deletions

View File

@@ -90,7 +90,7 @@ prowler dashboard
| M365 | 70 | 7 | 3 | 2 | Official | UI, API, CLI | | M365 | 70 | 7 | 3 | 2 | Official | UI, API, CLI |
| OCI | 51 | 13 | 1 | 10 | Official | UI, API, CLI | | OCI | 51 | 13 | 1 | 10 | Official | UI, API, CLI |
| IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI | | IaC | [See `trivy` docs.](https://trivy.dev/latest/docs/coverage/iac/) | N/A | N/A | N/A | Official | UI, API, CLI |
| MongoDB Atlas | 10 | 3 | 0 | 0 | Official | CLI, API | | MongoDB Atlas | 10 | 3 | 0 | 0 | Official | UI, API, CLI |
| LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI | | LLM | [See `promptfoo` docs.](https://www.promptfoo.dev/docs/red-team/plugins/) | N/A | N/A | N/A | Official | CLI |
| NHN | 6 | 2 | 1 | 0 | Unofficial | CLI | | NHN | 6 | 2 | 1 | 0 | Unofficial | CLI |

View File

@@ -33,7 +33,7 @@ The supported providers right now are:
| [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI | | [Github](/user-guide/providers/github/getting-started-github) | Official | UI, API, CLI |
| [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | UI, API, CLI | | [Oracle Cloud](/user-guide/providers/oci/getting-started-oci) | Official | UI, API, CLI |
| [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | UI, API, CLI | | [Infra as Code](/user-guide/providers/iac/getting-started-iac) | Official | UI, API, CLI |
| [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | CLI, API | | [MongoDB Atlas](/user-guide/providers/mongodbatlas/getting-started-mongodbatlas) | Official | UI, API, CLI |
| [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI | | [LLM](/user-guide/providers/llm/getting-started-llm) | Official | CLI |
| **NHN** | Unofficial | CLI | | **NHN** | Unofficial | CLI |

View File

@@ -22,6 +22,7 @@ All notable changes to the **Prowler UI** are documented in this file.
- IaC (Infrastructure as Code) provider support for scanning remote repositories [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751) - IaC (Infrastructure as Code) provider support for scanning remote repositories [(#8751)](https://github.com/prowler-cloud/prowler/pull/8751)
- PDF reporting for NIS2 compliance framework [(#9170)](https://github.com/prowler-cloud/prowler/pull/9170) - PDF reporting for NIS2 compliance framework [(#9170)](https://github.com/prowler-cloud/prowler/pull/9170)
- External resource link to IaC findings for direct navigation to source code in Git repositories [(#9151)](https://github.com/prowler-cloud/prowler/pull/9151) - External resource link to IaC findings for direct navigation to source code in Git repositories [(#9151)](https://github.com/prowler-cloud/prowler/pull/9151)
- MongoDB Atlas provider support [(#9253)](https://github.com/prowler-cloud/prowler/pull/9253)
- New Overview page and new app styles [(#9234)](https://github.com/prowler-cloud/prowler/pull/9234) - New Overview page and new app styles [(#9234)](https://github.com/prowler-cloud/prowler/pull/9234)
- Use branch name as region for IaC findings [(#9296)](https://github.com/prowler-cloud/prowler/pull/9296) - Use branch name as region for IaC findings [(#9296)](https://github.com/prowler-cloud/prowler/pull/9296)

View File

@@ -4,62 +4,6 @@ import { redirect } from "next/navigation";
import { apiBaseUrl, getAuthHeaders } from "@/lib"; import { apiBaseUrl, getAuthHeaders } from "@/lib";
import { handleApiResponse } from "@/lib/server-actions-helper"; import { handleApiResponse } from "@/lib/server-actions-helper";
import { FindingsResponse } from "@/types";
interface IncludedItem {
type: string;
id: string;
attributes?: { provider?: string };
relationships?: { provider?: { data?: { id: string } } };
}
type FindingsApiResponse = FindingsResponse & {
included?: IncludedItem[];
};
const filterMongoFindings = <T extends FindingsApiResponse | null | undefined>(
result: T,
): T => {
if (!result?.data) return result;
const included = (result as FindingsApiResponse).included || [];
// Get IDs of providers containing "mongo" in included items
const mongoProviderIds = new Set(
included
.filter(
(item) =>
item.type === "providers" &&
item.attributes?.provider?.toLowerCase().includes("mongo"),
)
.map((item) => item.id),
);
// Filter out findings associated with mongo providers
result.data = result.data.filter((finding) => {
const scanId = finding.relationships?.scan?.data?.id;
// Find the scan in included items
const scan = included.find(
(item) => item.type === "scans" && item.id === scanId,
);
const providerId = scan?.relationships?.provider?.data?.id;
return !providerId || !mongoProviderIds.has(providerId);
});
// Filter out mongo-related included items
if ((result as FindingsApiResponse).included) {
(result as FindingsApiResponse).included = included.filter(
(item) =>
!(
item.type === "providers" &&
item.attributes?.provider?.toLowerCase().includes("mongo")
),
);
}
return result;
};
export const getFindings = async ({ export const getFindings = async ({
page = 1, page = 1,
pageSize = 10, pageSize = 10,
@@ -89,9 +33,7 @@ export const getFindings = async ({
headers, headers,
}); });
const result = await handleApiResponse(findings); return handleApiResponse(findings);
return filterMongoFindings(result);
} catch (error) { } catch (error) {
console.error("Error fetching findings:", error); console.error("Error fetching findings:", error);
return undefined; return undefined;
@@ -129,9 +71,7 @@ export const getLatestFindings = async ({
headers, headers,
}); });
const result = await handleApiResponse(findings); return handleApiResponse(findings);
return filterMongoFindings(result);
} catch (error) { } catch (error) {
console.error("Error fetching findings:", error); console.error("Error fetching findings:", error);
return undefined; return undefined;

View File

@@ -28,8 +28,6 @@ interface AggregatedProvider {
fail: number; fail: number;
} }
const EXCLUDED_PROVIDERS = new Set(["mongo", "mongodb", "mongodbatlas"]);
// API can return multiple entries for the same provider type, so we sum their findings // API can return multiple entries for the same provider type, so we sum their findings
function aggregateProvidersByType( function aggregateProvidersByType(
providers: ProviderOverview[], providers: ProviderOverview[],
@@ -39,8 +37,6 @@ function aggregateProvidersByType(
for (const provider of providers) { for (const provider of providers) {
const { id, attributes } = provider; const { id, attributes } = provider;
if (EXCLUDED_PROVIDERS.has(id)) continue;
const existing = aggregated.get(id); const existing = aggregated.get(id);
if (existing) { if (existing) {

View File

@@ -39,26 +39,9 @@ export const getProviders = async ({
headers, headers,
}); });
const result = (await handleApiResponse(response)) as return (await handleApiResponse(response)) as
| ProvidersApiResponse | ProvidersApiResponse
| undefined; | undefined;
if (result?.data) {
// Filter out providers with provider type containing "mongo"
result.data = result.data.filter(
(provider) =>
!provider.attributes?.provider?.toLowerCase().includes("mongo"),
);
// Also filter out mongo-related included items if present
if (result.included) {
result.included = result.included.filter(
(item) => !item.type.toLowerCase().includes("mongo"),
);
}
}
return result;
} catch (error) { } catch (error) {
console.error("Error fetching providers:", error); console.error("Error fetching providers:", error);
return undefined; return undefined;

View File

@@ -9,47 +9,6 @@ import {
} from "@/lib/compliance/compliance-report-types"; } from "@/lib/compliance/compliance-report-types";
import { addScanOperation } from "@/lib/sentry-breadcrumbs"; import { addScanOperation } from "@/lib/sentry-breadcrumbs";
import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper"; import { handleApiError, handleApiResponse } from "@/lib/server-actions-helper";
import { ScansApiResponse } from "@/types";
const filterMongoScans = (result: ScansApiResponse | undefined) => {
if (!result?.data) return result;
const included = result.included || [];
// Get IDs of providers containing "mongo"
const mongoProviderIds = new Set(
included
.filter(
(item) =>
item.type === "providers" &&
item.attributes?.provider?.toLowerCase().includes("mongo"),
)
.map((item) => item.id),
);
// If no mongo providers found, return as-is
if (mongoProviderIds.size === 0) return result;
// Filter out scans associated with mongo providers
result.data = result.data.filter((scan) => {
const providerId = scan.relationships?.provider?.data?.id;
return !providerId || !mongoProviderIds.has(providerId);
});
// Filter out mongo-related included items
if (result.included) {
result.included = included.filter(
(item) =>
!(
item.type === "providers" &&
item.attributes?.provider?.toLowerCase().includes("mongo")
),
);
}
return result;
};
export const getScans = async ({ export const getScans = async ({
page = 1, page = 1,
query = "", query = "",
@@ -84,10 +43,7 @@ export const getScans = async ({
try { try {
const response = await fetch(url.toString(), { headers }); const response = await fetch(url.toString(), { headers });
const result = await handleApiResponse(response); return handleApiResponse(response);
// Filter out mongo-related scans when provider is included
return filterMongoScans(result);
} catch (error) { } catch (error) {
console.error("Error fetching scans:", error); console.error("Error fetching scans:", error);
return undefined; return undefined;

View File

@@ -11,6 +11,7 @@ import {
IacProviderBadge, IacProviderBadge,
KS8ProviderBadge, KS8ProviderBadge,
M365ProviderBadge, M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge, OracleCloudProviderBadge,
} from "@/components/icons/providers-badge"; } from "@/components/icons/providers-badge";
import { import {
@@ -31,6 +32,7 @@ const PROVIDER_ICON: Record<ProviderType, ReactNode> = {
github: <GitHubProviderBadge width={18} height={18} />, github: <GitHubProviderBadge width={18} height={18} />,
iac: <IacProviderBadge width={18} height={18} />, iac: <IacProviderBadge width={18} height={18} />,
oraclecloud: <OracleCloudProviderBadge width={18} height={18} />, oraclecloud: <OracleCloudProviderBadge width={18} height={18} />,
mongodbatlas: <MongoDBAtlasProviderBadge width={18} height={18} />,
}; };
interface AccountsSelectorProps { interface AccountsSelectorProps {

View File

@@ -52,6 +52,11 @@ const OracleCloudProviderBadge = lazy(() =>
default: m.OracleCloudProviderBadge, default: m.OracleCloudProviderBadge,
})), })),
); );
const MongoDBAtlasProviderBadge = lazy(() =>
import("@/components/icons/providers-badge").then((m) => ({
default: m.MongoDBAtlasProviderBadge,
})),
);
type IconProps = { width: number; height: number }; type IconProps = { width: number; height: number };
@@ -95,6 +100,10 @@ const PROVIDER_DATA: Record<
label: "Oracle Cloud Infrastructure", label: "Oracle Cloud Infrastructure",
icon: OracleCloudProviderBadge, icon: OracleCloudProviderBadge,
}, },
mongodbatlas: {
label: "MongoDB Atlas",
icon: MongoDBAtlasProviderBadge,
},
}; };
type ProviderTypeSelectorProps = { type ProviderTypeSelectorProps = {

View File

@@ -8,6 +8,7 @@ import {
IacProviderBadge, IacProviderBadge,
KS8ProviderBadge, KS8ProviderBadge,
M365ProviderBadge, M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge, OracleCloudProviderBadge,
} from "../icons/providers-badge"; } from "../icons/providers-badge";
@@ -38,6 +39,15 @@ export const CustomProviderInputM365 = () => {
); );
}; };
export const CustomProviderInputMongoDBAtlas = () => {
return (
<div className="flex items-center gap-x-2">
<MongoDBAtlasProviderBadge width={25} height={25} />
<p className="text-sm">MongoDB Atlas</p>
</div>
);
};
export const CustomProviderInputGCP = () => { export const CustomProviderInputGCP = () => {
return ( return (
<div className="flex items-center gap-x-2"> <div className="flex items-center gap-x-2">

View File

@@ -14,6 +14,7 @@ import {
CustomProviderInputIac, CustomProviderInputIac,
CustomProviderInputKubernetes, CustomProviderInputKubernetes,
CustomProviderInputM365, CustomProviderInputM365,
CustomProviderInputMongoDBAtlas,
CustomProviderInputOracleCloud, CustomProviderInputOracleCloud,
} from "./custom-provider-inputs"; } from "./custom-provider-inputs";
@@ -25,21 +26,13 @@ const providerDisplayData: Record<
label: "Amazon Web Services", label: "Amazon Web Services",
component: <CustomProviderInputAWS />, component: <CustomProviderInputAWS />,
}, },
gcp: {
label: "Google Cloud Platform",
component: <CustomProviderInputGCP />,
},
azure: { azure: {
label: "Microsoft Azure", label: "Microsoft Azure",
component: <CustomProviderInputAzure />, component: <CustomProviderInputAzure />,
}, },
m365: { gcp: {
label: "Microsoft 365", label: "Google Cloud Platform",
component: <CustomProviderInputM365 />, component: <CustomProviderInputGCP />,
},
kubernetes: {
label: "Kubernetes",
component: <CustomProviderInputKubernetes />,
}, },
github: { github: {
label: "GitHub", label: "GitHub",
@@ -49,6 +42,18 @@ const providerDisplayData: Record<
label: "Infrastructure as Code", label: "Infrastructure as Code",
component: <CustomProviderInputIac />, component: <CustomProviderInputIac />,
}, },
kubernetes: {
label: "Kubernetes",
component: <CustomProviderInputKubernetes />,
},
m365: {
label: "Microsoft 365",
component: <CustomProviderInputM365 />,
},
mongodbatlas: {
label: "MongoDB Atlas",
component: <CustomProviderInputMongoDBAtlas />,
},
oraclecloud: { oraclecloud: {
label: "Oracle Cloud Infrastructure", label: "Oracle Cloud Infrastructure",
component: <CustomProviderInputOracleCloud />, component: <CustomProviderInputOracleCloud />,

View File

@@ -1,6 +1,19 @@
import { CONNECTION_STATUS_MAPPING } from "@/lib/helper-filters"; import { CONNECTION_STATUS_MAPPING } from "@/lib/helper-filters";
import { FilterOption, FilterType } from "@/types/filters"; import { FilterOption, FilterType } from "@/types/filters";
import { PROVIDER_TYPES } from "@/types/providers"; import {
PROVIDER_DISPLAY_NAMES,
PROVIDER_TYPES,
ProviderType,
} from "@/types/providers";
// Create a mapping for provider types to display with icons and labels
const PROVIDER_TYPE_MAPPING = PROVIDER_TYPES.map((providerType) => ({
[providerType]: {
provider: providerType as ProviderType,
uid: "",
alias: PROVIDER_DISPLAY_NAMES[providerType],
},
}));
export const filterProviders: FilterOption[] = [ export const filterProviders: FilterOption[] = [
{ {
@@ -13,6 +26,7 @@ export const filterProviders: FilterOption[] = [
key: "provider__in", key: "provider__in",
labelCheckboxGroup: "Cloud Provider", labelCheckboxGroup: "Cloud Provider",
values: [...PROVIDER_TYPES], values: [...PROVIDER_TYPES],
valueLabelMapping: PROVIDER_TYPE_MAPPING,
}, },
// Add more filter categories as needed // Add more filter categories as needed
]; ];
@@ -22,6 +36,7 @@ export const filterScans = [
key: "provider_type__in", key: "provider_type__in",
labelCheckboxGroup: "Cloud Provider", labelCheckboxGroup: "Cloud Provider",
values: [...PROVIDER_TYPES], values: [...PROVIDER_TYPES],
valueLabelMapping: PROVIDER_TYPE_MAPPING,
index: 0, index: 0,
}, },
{ {
@@ -64,6 +79,7 @@ export const filterFindings = [
key: FilterType.PROVIDER_TYPE, key: FilterType.PROVIDER_TYPE,
labelCheckboxGroup: "Cloud Provider", labelCheckboxGroup: "Cloud Provider",
values: [...PROVIDER_TYPES], values: [...PROVIDER_TYPES],
valueLabelMapping: PROVIDER_TYPE_MAPPING,
index: 5, index: 5,
}, },
{ {

View File

@@ -7,6 +7,7 @@ import { GitHubProviderBadge } from "./github-provider-badge";
import { IacProviderBadge } from "./iac-provider-badge"; import { IacProviderBadge } from "./iac-provider-badge";
import { KS8ProviderBadge } from "./ks8-provider-badge"; import { KS8ProviderBadge } from "./ks8-provider-badge";
import { M365ProviderBadge } from "./m365-provider-badge"; import { M365ProviderBadge } from "./m365-provider-badge";
import { MongoDBAtlasProviderBadge } from "./mongodbatlas-provider-badge";
import { OracleCloudProviderBadge } from "./oraclecloud-provider-badge"; import { OracleCloudProviderBadge } from "./oraclecloud-provider-badge";
export { export {
@@ -17,6 +18,7 @@ export {
IacProviderBadge, IacProviderBadge,
KS8ProviderBadge, KS8ProviderBadge,
M365ProviderBadge, M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge, OracleCloudProviderBadge,
}; };
@@ -30,4 +32,5 @@ export const PROVIDER_ICONS: Record<string, React.FC<IconSvgProps>> = {
GitHub: GitHubProviderBadge, GitHub: GitHubProviderBadge,
"Infrastructure as Code": IacProviderBadge, "Infrastructure as Code": IacProviderBadge,
"Oracle Cloud Infrastructure": OracleCloudProviderBadge, "Oracle Cloud Infrastructure": OracleCloudProviderBadge,
"MongoDB Atlas": MongoDBAtlasProviderBadge,
}; };

View File

@@ -0,0 +1,40 @@
import * as React from "react";
import { IconSvgProps } from "@/types";
export const MongoDBAtlasProviderBadge: 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="#e6f4ec" rx="60" />
<g transform="translate(128 128) scale(7.25) translate(-16 -16)">
<path
d="M15.9.087l.854 1.604c.192.296.4.558.645.802.715.715 1.394 1.464 2.004 2.266 1.447 1.9 2.423 4.01 3.12 6.292.418 1.394.645 2.824.662 4.27.07 4.323-1.412 8.035-4.4 11.12-.488.488-1.01.94-1.57 1.342-.296 0-.436-.227-.558-.436-.227-.383-.366-.82-.436-1.255-.105-.523-.174-1.046-.14-1.586v-.244C16.057 24.21 15.796.21 15.9.087z"
fill="#599636"
/>
<path
d="M15.9.034c-.035-.07-.07-.017-.105.017.017.35-.105.662-.296.96-.21.296-.488.523-.767.767-1.55 1.342-2.77 2.963-3.747 4.776-1.3 2.44-1.97 5.055-2.16 7.808-.087.993.314 4.497.627 5.508.854 2.684 2.388 4.933 4.375 6.885.488.47 1.01.906 1.55 1.325.157 0 .174-.14.21-.244a4.78 4.78 0 0 0 .157-.68l.35-2.614L15.9.034z"
fill="#6cac48"
/>
<path
d="M16.754 28.845c.035-.4.227-.732.436-1.063-.21-.087-.366-.26-.488-.453-.105-.174-.192-.383-.26-.575-.244-.732-.296-1.5-.366-2.248v-.453c-.087.07-.105.662-.105.75a17.37 17.37 0 0 1-.314 2.353c-.052.314-.087.627-.28.906 0 .035 0 .07.017.122.314.924.4 1.865.453 2.824v.35c0 .418-.017.33.33.47.14.052.296.07.436.174.105 0 .122-.087.122-.157l-.052-.575v-1.604c-.017-.28.035-.558.07-.82z"
fill="#c2bfbf"
/>
</g>
</g>
</svg>
);

View File

@@ -19,6 +19,7 @@ const providerTypeLabels: Record<ProviderType, string> = {
github: "GitHub", github: "GitHub",
iac: "Infrastructure as Code", iac: "Infrastructure as Code",
oraclecloud: "Oracle Cloud Infrastructure", oraclecloud: "Oracle Cloud Infrastructure",
mongodbatlas: "MongoDB Atlas",
}; };
interface EnhancedProviderSelectorProps { interface EnhancedProviderSelectorProps {

View File

@@ -1,10 +1,10 @@
import { Tooltip } from "@heroui/tooltip"; import { Tooltip } from "@heroui/tooltip";
import React from "react";
import { ProviderType } from "@/types"; import { ProviderType } from "@/types";
import { ConnectionFalse, ConnectionPending, ConnectionTrue } from "../icons"; import { ConnectionFalse, ConnectionPending, ConnectionTrue } from "../icons";
import { getProviderLogo } from "../ui/entities"; import { getProviderLogo } from "../ui/entities";
interface ProviderInfoProps { interface ProviderInfoProps {
connected: boolean | null; connected: boolean | null;
provider: ProviderType; provider: ProviderType;
@@ -12,12 +12,12 @@ interface ProviderInfoProps {
providerUID?: string; providerUID?: string;
} }
export const ProviderInfo: React.FC<ProviderInfoProps> = ({ export const ProviderInfo = ({
connected, connected,
provider, provider,
providerAlias, providerAlias,
providerUID, providerUID,
}) => { }: ProviderInfoProps) => {
const getIcon = () => { const getIcon = () => {
switch (connected) { switch (connected) {
case true: case true:
@@ -31,8 +31,8 @@ export const ProviderInfo: React.FC<ProviderInfoProps> = ({
case false: case false:
return ( return (
<Tooltip content="Provider connection failed" className="text-xs"> <Tooltip content="Provider connection failed" className="text-xs">
<div className="rounded-medium border-danger bg-system-error-lighter flex items-center justify-center border-2 p-1"> <div className="rounded-medium border-border-error flex items-center justify-center border-2 p-1">
<ConnectionFalse className="text-text-error" size={24} /> <ConnectionFalse className="text-text-error-primary" size={24} />
</div> </div>
</Tooltip> </Tooltip>
); );

View File

@@ -15,6 +15,7 @@ import {
IacProviderBadge, IacProviderBadge,
KS8ProviderBadge, KS8ProviderBadge,
M365ProviderBadge, M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge, OracleCloudProviderBadge,
} from "../icons/providers-badge"; } from "../icons/providers-badge";
import { CustomRadio } from "../ui/custom"; import { CustomRadio } from "../ui/custom";
@@ -68,6 +69,12 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
<span className="ml-2">Microsoft 365</span> <span className="ml-2">Microsoft 365</span>
</div> </div>
</CustomRadio> </CustomRadio>
<CustomRadio description="MongoDB Atlas" value="mongodbatlas">
<div className="flex items-center">
<MongoDBAtlasProviderBadge size={26} />
<span className="ml-2">MongoDB Atlas</span>
</div>
</CustomRadio>
<CustomRadio description="Kubernetes" value="kubernetes"> <CustomRadio description="Kubernetes" value="kubernetes">
<div className="flex items-center"> <div className="flex items-center">
<KS8ProviderBadge size={26} /> <KS8ProviderBadge size={26} />

View File

@@ -20,6 +20,7 @@ import {
KubernetesCredentials, KubernetesCredentials,
M365CertificateCredentials, M365CertificateCredentials,
M365ClientSecretCredentials, M365ClientSecretCredentials,
MongoDBAtlasCredentials,
OCICredentials, OCICredentials,
ProviderType, ProviderType,
} from "@/types"; } from "@/types";
@@ -37,6 +38,7 @@ import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form";
import { IacCredentialsForm } from "./via-credentials/iac-credentials-form"; import { IacCredentialsForm } from "./via-credentials/iac-credentials-form";
import { KubernetesCredentialsForm } from "./via-credentials/k8s-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"; import { OracleCloudCredentialsForm } from "./via-credentials/oraclecloud-credentials-form";
type BaseCredentialsFormProps = { type BaseCredentialsFormProps = {
@@ -172,6 +174,13 @@ export const BaseCredentialsForm = ({
control={form.control as unknown as Control<OCICredentials>} control={form.control as unknown as Control<OCICredentials>}
/> />
)} )}
{providerType === "mongodbatlas" && (
<MongoDBAtlasCredentialsForm
control={
form.control as unknown as Control<MongoDBAtlasCredentials>
}
/>
)}
<div className="flex w-full justify-end gap-4"> <div className="flex w-full justify-end gap-4">
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && ( {showBackButton && requiresBackButton(searchParamsObj.get("via")) && (

View File

@@ -62,6 +62,11 @@ const getProviderFieldDetails = (providerType?: ProviderType) => {
label: "Tenancy OCID", label: "Tenancy OCID",
placeholder: "e.g. ocid1.tenancy.oc1..aaaaaaa...", placeholder: "e.g. ocid1.tenancy.oc1..aaaaaaa...",
}; };
case "mongodbatlas":
return {
label: "Organization ID",
placeholder: "e.g. 5f43a8c4e1234567890abcde",
};
default: default:
return { return {
label: "Provider UID", label: "Provider UID",

View File

@@ -45,7 +45,7 @@ export const TestConnectionForm = ({
}; };
provider: ProviderType; provider: ProviderType;
alias: string; alias: string;
scanner_args: Record<string, any>; scanner_args: Record<string, unknown>;
}; };
relationships: { relationships: {
secret: { secret: {
@@ -239,27 +239,27 @@ export const TestConnectionForm = ({
</div> </div>
{apiErrorMessage && ( {apiErrorMessage && (
<div className="text-text-error mt-4 rounded-md bg-red-100 p-3"> <div className="text-text-error-primary mt-4 rounded-md p-3">
<p>{`Provider ID ${apiErrorMessage?.toLowerCase()}. Please check and try again.`}</p> <p>{`Provider ID ${apiErrorMessage?.toLowerCase()}. Please check and try again.`}</p>
</div> </div>
)} )}
{connectionStatus && !connectionStatus.connected && ( {connectionStatus && !connectionStatus.connected && (
<> <>
<div className="flex items-center gap-4 rounded-lg border border-red-200 bg-red-50 p-4"> <div className="border-border-error flex items-start gap-4 rounded-lg border p-4">
<div className="flex items-center"> <div className="flex shrink-0 items-center">
<Icon <Icon
icon="heroicons:exclamation-circle" icon="heroicons:exclamation-circle"
className="text-text-error h-5 w-5" className="text-text-error-primary h-5 w-5"
/> />
</div> </div>
<div className="flex items-center"> <div className="min-w-0 flex-1">
<p className="text-small text-text-error"> <p className="text-small text-text-error-primary break-words">
{connectionStatus.error || "Unknown error"} {connectionStatus.error || "Unknown error"}
</p> </p>
</div> </div>
</div> </div>
<p className="text-small text-text-error"> <p className="text-small text-text-error-primary">
It seems there was an issue with your credentials. Please review It seems there was an issue with your credentials. Please review
your credentials and try again. your credentials and try again.
</p> </p>

View File

@@ -2,3 +2,4 @@ export * from "./azure-credentials-form";
export * from "./github-credentials-form"; export * from "./github-credentials-form";
export * from "./iac-credentials-form"; export * from "./iac-credentials-form";
export * from "./k8s-credentials-form"; export * from "./k8s-credentials-form";
export * from "./mongodbatlas-credentials-form";

View File

@@ -0,0 +1,49 @@
import { Control } from "react-hook-form";
import { CustomInput } from "@/components/ui/custom";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { MongoDBAtlasCredentials } from "@/types";
export const MongoDBAtlasCredentialsForm = ({
control,
}: {
control: Control<MongoDBAtlasCredentials>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md text-default-foreground leading-9 font-bold">
Connect via API Keys
</div>
<div className="text-default-500 text-sm">
Provide an organization-level MongoDB Atlas API public and private key
with read access to the resources you want Prowler to assess.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.ATLAS_PUBLIC_KEY}
type="text"
label="Atlas Public Key"
labelPlacement="inside"
placeholder="e.g. abcdefgh"
variant="bordered"
isRequired
/>
<CustomInput
control={control}
name={ProviderCredentialFields.ATLAS_PRIVATE_KEY}
type="password"
label="Atlas Private Key"
labelPlacement="inside"
placeholder="Enter the private key"
variant="bordered"
isRequired
/>
<div className="text-default-400 text-xs">
Keys never leave your browser unencrypted and are stored as secrets in
the backend. Rotate the key from MongoDB Atlas anytime if needed.
</div>
</>
);
};

View File

@@ -8,6 +8,7 @@ import {
IacProviderBadge, IacProviderBadge,
KS8ProviderBadge, KS8ProviderBadge,
M365ProviderBadge, M365ProviderBadge,
MongoDBAtlasProviderBadge,
OracleCloudProviderBadge, OracleCloudProviderBadge,
} from "@/components/icons/providers-badge"; } from "@/components/icons/providers-badge";
import { ProviderType } from "@/types"; import { ProviderType } from "@/types";
@@ -30,6 +31,8 @@ export const getProviderLogo = (provider: ProviderType) => {
return <IacProviderBadge width={35} height={35} />; return <IacProviderBadge width={35} height={35} />;
case "oraclecloud": case "oraclecloud":
return <OracleCloudProviderBadge width={35} height={35} />; return <OracleCloudProviderBadge width={35} height={35} />;
case "mongodbatlas":
return <MongoDBAtlasProviderBadge width={35} height={35} />;
default: default:
return null; return null;
} }
@@ -53,6 +56,8 @@ export const getProviderName = (provider: ProviderType): string => {
return "Infrastructure as Code"; return "Infrastructure as Code";
case "oraclecloud": case "oraclecloud":
return "Oracle Cloud Infrastructure"; return "Oracle Cloud Infrastructure";
case "mongodbatlas":
return "MongoDB Atlas";
default: default:
return "Unknown Provider"; return "Unknown Provider";
} }

View File

@@ -167,6 +167,12 @@ export const useCredentialsForm = ({
[ProviderCredentialFields.OCI_REGION]: "", [ProviderCredentialFields.OCI_REGION]: "",
[ProviderCredentialFields.OCI_PASS_PHRASE]: "", [ProviderCredentialFields.OCI_PASS_PHRASE]: "",
}; };
case "mongodbatlas":
return {
...baseDefaults,
[ProviderCredentialFields.ATLAS_PUBLIC_KEY]: "",
[ProviderCredentialFields.ATLAS_PRIVATE_KEY]: "",
};
default: default:
return baseDefaults; return baseDefaults;
} }

View File

@@ -28,4 +28,6 @@ export const PROVIDER_CREDENTIALS_ERROR_MAPPING: Record<string, string> = {
[ErrorPointers.ROLE_SESSION_NAME]: ProviderCredentialFields.ROLE_SESSION_NAME, [ErrorPointers.ROLE_SESSION_NAME]: ProviderCredentialFields.ROLE_SESSION_NAME,
[ErrorPointers.SERVICE_ACCOUNT_KEY]: [ErrorPointers.SERVICE_ACCOUNT_KEY]:
ProviderCredentialFields.SERVICE_ACCOUNT_KEY, ProviderCredentialFields.SERVICE_ACCOUNT_KEY,
[ErrorPointers.ATLAS_PUBLIC_KEY]: ProviderCredentialFields.ATLAS_PUBLIC_KEY,
[ErrorPointers.ATLAS_PRIVATE_KEY]: ProviderCredentialFields.ATLAS_PRIVATE_KEY,
}; };

View File

@@ -42,6 +42,11 @@ export const getProviderHelpText = (provider: string) => {
text: "Need help connecting your Oracle Cloud account?", text: "Need help connecting your Oracle Cloud account?",
link: "https://goto.prowler.com/provider-oraclecloud", link: "https://goto.prowler.com/provider-oraclecloud",
}; };
case "mongodbatlas":
return {
text: "Need help connecting your MongoDB Atlas organization?",
link: "https://goto.prowler.com/provider-mongodbatlas",
};
default: default:
return { return {
text: "How to setup a provider?", text: "How to setup a provider?",

View File

@@ -197,6 +197,20 @@ export const buildGitHubSecret = (formData: FormData) => {
return {}; return {};
}; };
export const buildMongoDBAtlasSecret = (formData: FormData) => {
const secret = {
[ProviderCredentialFields.ATLAS_PUBLIC_KEY]: getFormValue(
formData,
ProviderCredentialFields.ATLAS_PUBLIC_KEY,
),
[ProviderCredentialFields.ATLAS_PRIVATE_KEY]: getFormValue(
formData,
ProviderCredentialFields.ATLAS_PRIVATE_KEY,
),
};
return filterEmptyValues(secret);
};
export const buildIacSecret = (formData: FormData) => { export const buildIacSecret = (formData: FormData) => {
const secret = { const secret = {
[ProviderCredentialFields.REPOSITORY_URL]: getFormValue( [ProviderCredentialFields.REPOSITORY_URL]: getFormValue(
@@ -308,6 +322,10 @@ export const buildSecretConfig = (
secretType: "static", secretType: "static",
secret: buildOracleCloudSecret(formData, providerUid), secret: buildOracleCloudSecret(formData, providerUid),
}), }),
mongodbatlas: () => ({
secretType: "static",
secret: buildMongoDBAtlasSecret(formData),
}),
}; };
const builder = secretBuilders[providerType]; const builder = secretBuilders[providerType];

View File

@@ -45,6 +45,10 @@ export const ProviderCredentialFields = {
GITHUB_APP_ID: "github_app_id", GITHUB_APP_ID: "github_app_id",
GITHUB_APP_KEY: "github_app_key_content", GITHUB_APP_KEY: "github_app_key_content",
// MongoDB Atlas fields
ATLAS_PUBLIC_KEY: "atlas_public_key",
ATLAS_PRIVATE_KEY: "atlas_private_key",
// IaC fields // IaC fields
REPOSITORY_URL: "repository_url", REPOSITORY_URL: "repository_url",
ACCESS_TOKEN: "access_token", ACCESS_TOKEN: "access_token",
@@ -95,6 +99,8 @@ export const ErrorPointers = {
OCI_TENANCY: "/data/attributes/secret/tenancy", OCI_TENANCY: "/data/attributes/secret/tenancy",
OCI_REGION: "/data/attributes/secret/region", OCI_REGION: "/data/attributes/secret/region",
OCI_PASS_PHRASE: "/data/attributes/secret/pass_phrase", OCI_PASS_PHRASE: "/data/attributes/secret/pass_phrase",
ATLAS_PUBLIC_KEY: "/data/attributes/secret/atlas_public_key",
ATLAS_PRIVATE_KEY: "/data/attributes/secret/atlas_private_key",
} as const; } as const;
export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers]; export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers];

View File

@@ -264,6 +264,12 @@ export type OCICredentials = {
[ProviderCredentialFields.PROVIDER_ID]: string; [ProviderCredentialFields.PROVIDER_ID]: string;
}; };
export type MongoDBAtlasCredentials = {
[ProviderCredentialFields.ATLAS_PUBLIC_KEY]: string;
[ProviderCredentialFields.ATLAS_PRIVATE_KEY]: string;
[ProviderCredentialFields.PROVIDER_ID]: string;
};
export type CredentialsFormSchema = export type CredentialsFormSchema =
| AWSCredentials | AWSCredentials
| AzureCredentials | AzureCredentials
@@ -272,7 +278,8 @@ export type CredentialsFormSchema =
| KubernetesCredentials | KubernetesCredentials
| IacCredentials | IacCredentials
| M365Credentials | M365Credentials
| OCICredentials; | OCICredentials
| MongoDBAtlasCredentials;
export interface SearchParamsProps { export interface SearchParamsProps {
[key: string]: string | string[] | undefined; [key: string]: string | string[] | undefined;

View File

@@ -120,6 +120,11 @@ export const addProviderFormSchema = z
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(), [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
providerUid: z.string(), providerUid: z.string(),
}), }),
z.object({
providerType: z.literal("mongodbatlas"),
[ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
providerUid: z.string(),
}),
]), ]),
); );
@@ -231,7 +236,16 @@ export const addCredentialsFormSchema = (
.union([z.string(), z.literal("")]) .union([z.string(), z.literal("")])
.optional(), .optional(),
} }
: {}), : providerType === "mongodbatlas"
? {
[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"),
}
: {}),
}) })
.superRefine((data: Record<string, any>, ctx) => { .superRefine((data: Record<string, any>, ctx) => {
if (providerType === "m365") { if (providerType === "m365") {

View File

@@ -4,6 +4,7 @@ export const PROVIDER_TYPES = [
"gcp", "gcp",
"kubernetes", "kubernetes",
"m365", "m365",
"mongodbatlas",
"github", "github",
"iac", "iac",
"oraclecloud", "oraclecloud",
@@ -17,6 +18,7 @@ export const PROVIDER_DISPLAY_NAMES: Record<ProviderType, string> = {
gcp: "Google Cloud", gcp: "Google Cloud",
kubernetes: "Kubernetes", kubernetes: "Kubernetes",
m365: "Microsoft 365", m365: "Microsoft 365",
mongodbatlas: "MongoDB Atlas",
github: "GitHub", github: "GitHub",
iac: "Infrastructure as Code", iac: "Infrastructure as Code",
oraclecloud: "Oracle Cloud Infrastructure", oraclecloud: "Oracle Cloud Infrastructure",
@@ -85,27 +87,6 @@ export interface ProviderConnectionStatus {
value: string; value: string;
} }
export interface ProviderOverviewProps {
data: {
type: "provider-overviews";
id: ProviderType;
attributes: {
findings: {
pass: number;
fail: number;
manual: number;
total: number;
};
resources: {
total: number;
};
};
}[];
meta: {
version: string;
};
}
export interface ProvidersApiResponse { export interface ProvidersApiResponse {
links: { links: {
first: string; first: string;