mirror of
https://github.com/prowler-cloud/prowler.git
synced 2025-12-19 05:17:47 +00:00
Compare commits
5 Commits
489454b5c6
...
PRWLR-7569
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ae8ed0fe3 | ||
|
|
40219d5f09 | ||
|
|
638f44e6c9 | ||
|
|
6d94cd54ae | ||
|
|
186851e264 |
@@ -12,6 +12,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- Navigation link in Scans view to access Compliance Overview [(#8251)](https://github.com/prowler-cloud/prowler/pull/8251)
|
||||
- Status column for findings table in the Compliance Detail view [(#8244)](https://github.com/prowler-cloud/prowler/pull/8244)
|
||||
- Allow to restrict routes access based on user permissions [(#8287)](https://github.com/prowler-cloud/prowler/pull/8287)
|
||||
- Support for Service Principal-only and Service Principal + User authentication flows for M365 provider [(#8332)](https://github.com/prowler-cloud/prowler/pull/8332)
|
||||
- Max character limit validation for Scan label [(#8319)](https://github.com/prowler-cloud/prowler/pull/8319)
|
||||
|
||||
### Security
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
AddViaServiceAccountForm,
|
||||
SelectViaGCP,
|
||||
} from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
@@ -26,9 +27,18 @@ export default function AddCredentialsPage({ searchParams }: Props) {
|
||||
<SelectViaGCP initialVia={searchParams.via} />
|
||||
)}
|
||||
|
||||
{searchParams.type === "m365" && !searchParams.via && (
|
||||
<SelectViaM365 initialVia={searchParams.via} />
|
||||
)}
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
(searchParams.type === "m365" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "m365" &&
|
||||
searchParams.via === "service-principal-user") ||
|
||||
(searchParams.type !== "aws" &&
|
||||
searchParams.type !== "gcp" &&
|
||||
searchParams.type !== "m365")) && (
|
||||
<AddViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ interface Props {
|
||||
export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
return (
|
||||
<>
|
||||
{(searchParams.type === "aws" || searchParams.type === "gcp") &&
|
||||
{(searchParams.type === "aws" ||
|
||||
searchParams.type === "gcp" ||
|
||||
searchParams.type === "m365") &&
|
||||
!searchParams.via && (
|
||||
<CredentialsUpdateInfo
|
||||
providerType={searchParams.type}
|
||||
@@ -30,7 +32,12 @@ export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
(searchParams.type === "m365" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "m365" &&
|
||||
searchParams.via === "service-principal-user") ||
|
||||
(searchParams.type !== "aws" &&
|
||||
searchParams.type !== "gcp" &&
|
||||
searchParams.type !== "m365")) && (
|
||||
<UpdateViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
|
||||
import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface UpdateCredentialsInfoProps {
|
||||
@@ -20,6 +21,9 @@ export const CredentialsUpdateInfo = ({
|
||||
if (providerType === "gcp") {
|
||||
return <SelectViaGCP initialVia={initialVia} />;
|
||||
}
|
||||
if (providerType === "m365") {
|
||||
return <SelectViaM365 initialVia={initialVia} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,9 +24,10 @@ import { AWSStaticCredentialsForm } from "./select-credentials-type/aws/credenti
|
||||
import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credentials-type/aws-role-credentials-form";
|
||||
import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type";
|
||||
import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
|
||||
import { M365ServicePrincipalForm } from "./select-credentials-type/m365/credentials-type";
|
||||
import { M365ServicePrincipalUserForm } from "./select-credentials-type/m365/credentials-type/m365-service-principal-user-form";
|
||||
import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
|
||||
import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
|
||||
import { M365CredentialsForm } from "./via-credentials/m365-credentials-form";
|
||||
|
||||
type BaseCredentialsFormProps = {
|
||||
providerType: ProviderType;
|
||||
@@ -97,11 +98,18 @@ export const BaseCredentialsForm = ({
|
||||
control={form.control as unknown as Control<AzureCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "m365" && (
|
||||
<M365CredentialsForm
|
||||
control={form.control as unknown as Control<M365Credentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "m365" &&
|
||||
searchParamsObj.get("via") === "service-principal-user" && (
|
||||
<M365ServicePrincipalUserForm
|
||||
control={form.control as unknown as Control<M365Credentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "m365" &&
|
||||
searchParamsObj.get("via") !== "service-principal-user" && (
|
||||
<M365ServicePrincipalForm
|
||||
control={form.control as unknown as Control<M365Credentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "gcp" &&
|
||||
searchParamsObj.get("via") === "service-account" && (
|
||||
<GCPServiceAccountKeyForm
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { M365ServicePrincipalForm } from "./m365-service-principal-form";
|
||||
export { M365ServicePrincipalUserForm } from "./m365-service-principal-user-form";
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { M365Credentials } from "@/types";
|
||||
|
||||
export const M365ServicePrincipalForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<M365Credentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Application/Service Principal
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide the Application/Service Principal information for your
|
||||
Microsoft 365 tenant.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_ID}
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.CLIENT_ID]
|
||||
}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_SECRET}
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.CLIENT_SECRET]
|
||||
}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.TENANT_ID}
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.TENANT_ID]
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { InfoIcon } from "@/components/icons";
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { M365Credentials } from "@/types";
|
||||
|
||||
export const M365ServicePrincipalUserForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<M365Credentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect using Application/Service Principal and User Credentials
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Connect using Service Principal credentials combined with user
|
||||
authentication.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-xs font-bold text-default-500">
|
||||
Service Principal Information
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_ID}
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.CLIENT_ID]
|
||||
}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_SECRET}
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.CLIENT_SECRET]
|
||||
}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.TENANT_ID}
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.TENANT_ID]
|
||||
}
|
||||
/>
|
||||
|
||||
<span className="text-xs font-bold text-default-500">
|
||||
User Credentials
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.USER}
|
||||
type="text"
|
||||
label="User"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the User (e.g., user@company.onmicrosoft.com)"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.USER]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.PASSWORD}
|
||||
type="password"
|
||||
label="Password"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Password"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={
|
||||
!!control._formState.errors[ProviderCredentialFields.PASSWORD]
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex items-center rounded-lg border border-system-warning bg-system-warning-medium p-2 text-sm dark:text-default-300">
|
||||
<InfoIcon className="mr-2 inline h-4 w-4 flex-shrink-0" />
|
||||
<p className="text-xs font-extrabold">
|
||||
By September 2025, User Authentication will be deprecated.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./credentials-type";
|
||||
export { RadioGroupM365ViaCredentialsTypeForm } from "./radio-group-m365-via-credentials-type-form";
|
||||
export { SelectViaM365 } from "./select-via-m365";
|
||||
@@ -0,0 +1,74 @@
|
||||
import { RadioGroup } from "@nextui-org/react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
interface RadioGroupM365ViaCredentialsFormProps {
|
||||
control: Control<any>;
|
||||
isInvalid?: boolean;
|
||||
errorMessage?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const RadioGroupM365ViaCredentialsTypeForm = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
onChange,
|
||||
}: RadioGroupM365ViaCredentialsFormProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name="m365CredentialsType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-sm text-default-500">
|
||||
Application Authentication
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using Service Principal credentials only"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Service Principal</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-sm text-default-500">
|
||||
Application + User Authentication
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using Service Principal + User credentials"
|
||||
value="service-principal-user"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">
|
||||
Service Principal + User Credentials
|
||||
</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-system-error dark:text-system-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { Form } from "@/components/ui/form";
|
||||
|
||||
import { RadioGroupM365ViaCredentialsTypeForm } from "./radio-group-m365-via-credentials-type-form";
|
||||
|
||||
interface SelectViaM365Props {
|
||||
initialVia?: string;
|
||||
}
|
||||
|
||||
export const SelectViaM365 = ({ initialVia }: SelectViaM365Props) => {
|
||||
const router = useRouter();
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
m365CredentialsType: initialVia || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSelectionChange = (value: string) => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set("via", value);
|
||||
router.push(url.toString());
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RadioGroupM365ViaCredentialsTypeForm
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.m365CredentialsType}
|
||||
errorMessage={form.formState.errors.m365CredentialsType?.message}
|
||||
onChange={handleSelectionChange}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import { PROVIDER_CREDENTIALS_ERROR_MAPPING } from "@/lib/error-mappings";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import {
|
||||
addCredentialsFormSchema,
|
||||
addCredentialsM365UserFormSchema,
|
||||
addCredentialsRoleFormSchema,
|
||||
addCredentialsServiceAccountFormSchema,
|
||||
ProviderType,
|
||||
@@ -46,6 +47,9 @@ export const useCredentialsForm = ({
|
||||
if (providerType === "gcp" && via === "service-account") {
|
||||
return addCredentialsServiceAccountFormSchema(providerType);
|
||||
}
|
||||
if (providerType === "m365" && via === "service-principal-user") {
|
||||
return addCredentialsM365UserFormSchema();
|
||||
}
|
||||
return addCredentialsFormSchema(providerType);
|
||||
};
|
||||
|
||||
|
||||
@@ -69,6 +69,12 @@ export const awsCredentialsTypeSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const m365CredentialsTypeSchema = z.object({
|
||||
m365CredentialsType: z.string().min(1, {
|
||||
message: "Please select the type of credentials you want to use",
|
||||
}),
|
||||
});
|
||||
|
||||
export const addProviderFormSchema = z
|
||||
.object({
|
||||
providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
|
||||
@@ -257,6 +263,27 @@ export const addCredentialsServiceAccountFormSchema = (
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: z.string(),
|
||||
});
|
||||
|
||||
export const addCredentialsM365UserFormSchema = () =>
|
||||
z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: z.string(),
|
||||
[ProviderCredentialFields.CLIENT_ID]: z
|
||||
.string()
|
||||
.nonempty("Client ID is required"),
|
||||
[ProviderCredentialFields.CLIENT_SECRET]: z
|
||||
.string()
|
||||
.nonempty("Client Secret is required"),
|
||||
[ProviderCredentialFields.TENANT_ID]: z
|
||||
.string()
|
||||
.nonempty("Tenant ID is required"),
|
||||
[ProviderCredentialFields.USER]: z
|
||||
.string()
|
||||
.nonempty("User is required for this authentication method"),
|
||||
[ProviderCredentialFields.PASSWORD]: z
|
||||
.string()
|
||||
.nonempty("Password is required for this authentication method"),
|
||||
});
|
||||
|
||||
export const testConnectionFormSchema = z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
runOnce: z.boolean().default(false),
|
||||
|
||||
Reference in New Issue
Block a user