mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-05 03:12:14 +00:00
fix(ui): match API whitespace stripping and lock the cert cap + extensions
This commit is contained in:
@@ -96,6 +96,37 @@ describe("generateProwlerCertificate", () => {
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("emits a leaf cert with BasicConstraints(cA=false) and KeyUsage(digitalSignature)", async () => {
|
||||
// Guardrail: audit tooling and strict CA validators flag self-signed
|
||||
// leaves that omit these extensions. A future edit that drops them
|
||||
// from the `extensions:` array must not slip past review.
|
||||
const {
|
||||
BasicConstraintsExtension,
|
||||
KeyUsageFlags,
|
||||
KeyUsagesExtension,
|
||||
X509Certificate,
|
||||
} = await import("@peculiar/x509");
|
||||
|
||||
const result = await generateProwlerCertificate({ modulusLength: 2048 });
|
||||
|
||||
const publicDer = Uint8Array.from(
|
||||
atob(result.publicCertificateBase64Der),
|
||||
(c) => c.charCodeAt(0),
|
||||
);
|
||||
const cert = new X509Certificate(publicDer);
|
||||
|
||||
const basicConstraints = cert.getExtension(BasicConstraintsExtension);
|
||||
expect(basicConstraints).toBeDefined();
|
||||
expect(basicConstraints!.ca).toBe(false);
|
||||
|
||||
const keyUsages = cert.getExtension(KeyUsagesExtension);
|
||||
expect(keyUsages).toBeDefined();
|
||||
// `usages` is a bitmask — verify the digitalSignature bit is set.
|
||||
expect(keyUsages!.usages & KeyUsageFlags.digitalSignature).toBe(
|
||||
KeyUsageFlags.digitalSignature,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("downloadPublicCertificateFile", () => {
|
||||
|
||||
@@ -7,7 +7,9 @@ import {
|
||||
addCredentialsFormSchema,
|
||||
addCredentialsRoleFormSchema,
|
||||
addProviderFormSchema,
|
||||
CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
KUBECONFIG_UNSUPPORTED_COMMAND_AUTHENTICATION_ERROR,
|
||||
MAX_CERTIFICATE_CONTENT_LENGTH,
|
||||
samlConfigFormSchema,
|
||||
} from "./formSchemas";
|
||||
|
||||
@@ -125,6 +127,54 @@ describe("addCredentialsFormSchema - azure certificate", () => {
|
||||
// Then
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects certificate content that exceeds the base64 length cap", () => {
|
||||
// Guardrail against a future edit dropping the `.max(...)` on the
|
||||
// Azure `certificate_content` field: a payload larger than the API's
|
||||
// `_MAX_CERTIFICATE_CONTENT_LENGTH` must never leave the browser.
|
||||
const schema = addCredentialsFormSchema("azure", "app_certificate");
|
||||
// Padding-safe: 4-char multiple of "A" (all valid base64 chars) longer
|
||||
// than the cap, so only the size check rejects (isValidBase64 passes).
|
||||
const oversized = "A".repeat(MAX_CERTIFICATE_CONTENT_LENGTH + 4);
|
||||
|
||||
const result = schema.safeParse({
|
||||
...BASE_AZURE_VALUES,
|
||||
[ProviderCredentialFields.CERTIFICATE_CONTENT]: oversized,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
if (result.success) return;
|
||||
expect(result.error.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
message: CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
path: [ProviderCredentialFields.CERTIFICATE_CONTENT],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts a wrapped-line certificate content that fits after stripping whitespace", () => {
|
||||
// openssl and PowerShell wrap base64 output at 64 chars with LF/CRLF.
|
||||
// The API strips whitespace before enforcing the cap; the client does
|
||||
// the same via `.transform(strip)`. A legitimate ~50 KB base64 that
|
||||
// exceeds the cap only because of embedded whitespace must still pass.
|
||||
const schema = addCredentialsFormSchema("azure", "app_certificate");
|
||||
const rawBase64 = "A".repeat(MAX_CERTIFICATE_CONTENT_LENGTH);
|
||||
const wrapped = rawBase64.match(/.{1,64}/g)!.join("\r\n");
|
||||
// Sanity: wrapping made it longer than the cap.
|
||||
expect(wrapped.length).toBeGreaterThan(MAX_CERTIFICATE_CONTENT_LENGTH);
|
||||
|
||||
const result = schema.safeParse({
|
||||
...BASE_AZURE_VALUES,
|
||||
[ProviderCredentialFields.CERTIFICATE_CONTENT]: wrapped,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
if (!result.success) return;
|
||||
// The parsed value is the stripped base64 — matches what the API sees.
|
||||
expect(
|
||||
result.data[ProviderCredentialFields.CERTIFICATE_CONTENT],
|
||||
).toBe(rawBase64);
|
||||
});
|
||||
});
|
||||
|
||||
describe("addProviderFormSchema - okta", () => {
|
||||
|
||||
+24
-6
@@ -251,9 +251,18 @@ export const addCredentialsFormSchema = (
|
||||
[ProviderCredentialFields.CLIENT_SECRET]: z.string().optional(),
|
||||
[ProviderCredentialFields.CERTIFICATE_CONTENT]: z
|
||||
.string()
|
||||
.max(
|
||||
MAX_CERTIFICATE_CONTENT_LENGTH,
|
||||
CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
// The API strips base64 whitespace before enforcing its
|
||||
// 68268-char cap; measure the same value on the client so
|
||||
// a legitimate CRLF-wrapped paste (openssl / PowerShell
|
||||
// default line wrap) is not rejected as "too large".
|
||||
.transform((value) => value.replace(/\s+/g, ""))
|
||||
.pipe(
|
||||
z
|
||||
.string()
|
||||
.max(
|
||||
MAX_CERTIFICATE_CONTENT_LENGTH,
|
||||
CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
),
|
||||
)
|
||||
.optional(),
|
||||
[ProviderCredentialFields.TENANT_ID]: z.guid({
|
||||
@@ -298,9 +307,18 @@ export const addCredentialsFormSchema = (
|
||||
.optional(),
|
||||
[ProviderCredentialFields.CERTIFICATE_CONTENT]: z
|
||||
.string()
|
||||
.max(
|
||||
MAX_CERTIFICATE_CONTENT_LENGTH,
|
||||
CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
// Same whitespace-then-cap contract as Azure — the
|
||||
// API strips base64 whitespace before enforcing the
|
||||
// 68268-char cap, so the client measures the same
|
||||
// stripped value.
|
||||
.transform((value) => value.replace(/\s+/g, ""))
|
||||
.pipe(
|
||||
z
|
||||
.string()
|
||||
.max(
|
||||
MAX_CERTIFICATE_CONTENT_LENGTH,
|
||||
CERTIFICATE_CONTENT_MAX_SIZE_ERROR,
|
||||
),
|
||||
)
|
||||
.optional(),
|
||||
[ProviderCredentialFields.TENANT_ID]: z
|
||||
|
||||
Reference in New Issue
Block a user