fix(kubernetes): reject exec auth in cloud kubeconfigs (#11753)

This commit is contained in:
Hugo Pereira Brito
2026-07-07 09:57:46 +01:00
committed by GitHub
parent 6cae37174c
commit ad04e69c35
10 changed files with 273 additions and 4 deletions
+4
View File
@@ -20,6 +20,10 @@ All notable changes to the **Prowler UI** are documented in this file.
- Invitation callback paths are now preserved when invited users continue with Google, GitHub, or SAML authentication [(#11752)](https://github.com/prowler-cloud/prowler/pull/11752)
### 🔐 Security
- Kubernetes provider credential forms now reject kubeconfigs using `exec` authentication in Prowler Cloud before submission [(#11753)](https://github.com/prowler-cloud/prowler/pull/11753)
---
## [1.32.0] (Prowler v5.32.0)
@@ -1,13 +1,28 @@
"use client";
import { Control } from "react-hook-form";
import { useWatch } from "react-hook-form";
import { WizardTextareaField } from "@/components/providers/workflow/forms/fields";
import { KubernetesCredentials } from "@/types";
import {
KUBECONFIG_EXEC_AUTHENTICATION_ERROR,
kubeconfigContainsExecAuthentication,
} from "@/types/formSchemas";
export const KubernetesCredentialsForm = ({
control,
}: {
control: Control<KubernetesCredentials>;
}) => {
const kubeconfigContent = useWatch({
control,
name: "kubeconfig_content",
});
const hasExecAuthentication = kubeconfigContainsExecAuthentication(
kubeconfigContent ?? "",
);
return (
<>
<div className="flex flex-col">
@@ -28,6 +43,11 @@ export const KubernetesCredentialsForm = ({
minRows={10}
isRequired
/>
{hasExecAuthentication && (
<p className="text-text-error-primary text-xs">
{KUBECONFIG_EXEC_AUTHENTICATION_ERROR}
</p>
)}
</>
);
};
+70
View File
@@ -150,3 +150,73 @@ describe("addCredentialsFormSchema - okta", () => {
);
});
});
describe("addCredentialsFormSchema - kubernetes", () => {
const BASE_KUBERNETES_VALUES = {
[ProviderCredentialFields.PROVIDER_ID]: "provider-kubernetes-1",
[ProviderCredentialFields.PROVIDER_TYPE]: "kubernetes",
} as const;
it("accepts kubeconfig content without exec authentication", () => {
const schema = addCredentialsFormSchema("kubernetes");
const result = schema.safeParse({
...BASE_KUBERNETES_VALUES,
[ProviderCredentialFields.KUBECONFIG_CONTENT]: `apiVersion: v1
kind: Config
users:
- name: test-user
user:
token: test-token`,
});
expect(result.success).toBe(true);
});
it("reports kubeconfig exec authentication on kubeconfig_content field", () => {
const schema = addCredentialsFormSchema("kubernetes");
const result = schema.safeParse({
...BASE_KUBERNETES_VALUES,
[ProviderCredentialFields.KUBECONFIG_CONTENT]: `apiVersion: v1
kind: Config
users:
- name: test-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1
command: kubectl`,
});
expect(result.success).toBe(false);
if (result.success) return;
expect(result.error.issues).toContainEqual(
expect.objectContaining({
path: [ProviderCredentialFields.KUBECONFIG_CONTENT],
}),
);
});
it("accepts malformed kubeconfig content for backend validation", () => {
const schema = addCredentialsFormSchema("kubernetes");
const result = schema.safeParse({
...BASE_KUBERNETES_VALUES,
[ProviderCredentialFields.KUBECONFIG_CONTENT]: "apiVersion: [",
});
expect(result.success).toBe(true);
});
it("accepts non-mapping kubeconfig content for backend validation", () => {
const schema = addCredentialsFormSchema("kubernetes");
const result = schema.safeParse({
...BASE_KUBERNETES_VALUES,
[ProviderCredentialFields.KUBECONFIG_CONTENT]: "[]",
});
expect(result.success).toBe(true);
});
});
+37 -1
View File
@@ -1,3 +1,4 @@
import yaml from "js-yaml";
import { z } from "zod";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
@@ -5,6 +6,35 @@ import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
import { PROVIDER_TYPES, ProviderType } from "./providers";
export const KUBECONFIG_EXEC_AUTHENTICATION_ERROR =
"Kubernetes kubeconfig exec authentication is not supported in Prowler Cloud for security reasons.";
const isRecord = (value: unknown): value is Record<string, unknown> => {
return typeof value === "object" && value !== null && !Array.isArray(value);
};
export const kubeconfigContainsExecAuthentication = (
value: string,
): boolean => {
try {
const parsed = yaml.load(value);
if (!isRecord(parsed) || !Array.isArray(parsed.users)) {
return false;
}
return parsed.users.some((userEntry) => {
if (!isRecord(userEntry) || !isRecord(userEntry.user)) {
return false;
}
return "exec" in userEntry.user;
});
} catch {
return false;
}
};
export const addRoleFormSchema = z.object({
name: z.string().min(1, "Name is required"),
manage_users: z.boolean().default(false),
@@ -207,7 +237,13 @@ export const addCredentialsFormSchema = (
? {
[ProviderCredentialFields.KUBECONFIG_CONTENT]: z
.string()
.min(1, "Kubeconfig Content is required"),
.min(1, "Kubeconfig Content is required")
.refine(
(value) => !kubeconfigContainsExecAuthentication(value),
{
error: KUBECONFIG_EXEC_AUTHENTICATION_ERROR,
},
),
}
: providerType === "m365"
? {