From d2a7f58118b21a48538d0a6696cfd7adb6f747b8 Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Wed, 7 Oct 2026 08:41:09 +0300 Subject: [PATCH 1/3] Read KEDA GCP credential Secret refs from the actual API field --- internal/audit/crd_config_refs.go | 2 +- internal/audit/crd_config_refs_test.go | 9 ++++++- .../renderers/KedaCredentials.test.tsx | 26 +++++++++++++++++++ .../renderers/KedaTriggerAuthRenderer.tsx | 9 ++++--- 4 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx diff --git a/internal/audit/crd_config_refs.go b/internal/audit/crd_config_refs.go index 04de472d25..4a16cdd391 100644 --- a/internal/audit/crd_config_refs.go +++ b/internal/audit/crd_config_refs.go @@ -387,7 +387,7 @@ func kedaTriggerAuthConfigRefs(u *unstructured.Unstructured) []bp.ConfigObjectRe for _, ref := range mapsAt(u.Object, "spec", "configMapTargetRef") { addConfigMap(&refs, ns, stringValue(ref["name"])) } - addSecret(&refs, ns, stringAt(u.Object, "spec", "gcpSecretManager", "credentials", "clientSecret", "name")) + addSecret(&refs, ns, stringAt(u.Object, "spec", "gcpSecretManager", "credentials", "clientSecret", "valueFrom", "secretKeyRef", "name")) return refs } diff --git a/internal/audit/crd_config_refs_test.go b/internal/audit/crd_config_refs_test.go index c98aa4a22a..8a8c1acf55 100644 --- a/internal/audit/crd_config_refs_test.go +++ b/internal/audit/crd_config_refs_test.go @@ -186,10 +186,17 @@ func TestDynamicConfigObjectRefs(t *testing.T) { obj: map[string]any{"spec": map[string]any{ "secretTargetRef": []any{map[string]any{"name": "queue-secret"}}, "configMapTargetRef": []any{map[string]any{"name": "queue-config"}}, - "gcpSecretManager": map[string]any{"credentials": map[string]any{"clientSecret": map[string]any{"name": "gcp-secret"}}}, + "gcpSecretManager": map[string]any{"credentials": map[string]any{"clientSecret": map[string]any{"valueFrom": map[string]any{"secretKeyRef": map[string]any{"name": "gcp-secret", "key": "credentials.json"}}}}}, }}, want: refs(secret("app", "queue-secret"), configMap("app", "queue-config"), secret("app", "gcp-secret")), }, + { + name: "keda shallow GCP credentials are not a reference", + gvr: gvr("keda.sh", "v1alpha1", "triggerauthentications"), + ns: "app", + obj: map[string]any{"spec": map[string]any{"gcpSecretManager": map[string]any{"credentials": map[string]any{"clientSecret": map[string]any{"name": "fictional"}}}}}, + want: refs(), + }, { name: "prometheus servicemonitor refs", gvr: gvr("monitoring.coreos.com", "v1", "servicemonitors"), diff --git a/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx new file mode 100644 index 0000000000..37e2b7ee6b --- /dev/null +++ b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx @@ -0,0 +1,26 @@ +// @vitest-environment jsdom +import { act } from 'react' +import { createRoot } from 'react-dom/client' +import { renderToStaticMarkup } from 'react-dom/server' +import { expect, it, vi } from 'vitest' +import { KedaTriggerAuthRenderer } from './KedaTriggerAuthRenderer' +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) +const spec = {secretTargetRef:[{parameter:'token',name:'direct',key:'token'}],gcpSecretManager:{credentials:{clientSecret:{valueFrom:{secretKeyRef:{name:'iam',key:'credentials.json'}}}}}} + +it('links the actual nested GCP credential Secret in the authentication namespace', async () => { + const element=document.createElement('div');const root=createRoot(element);const onNavigate=vi.fn() + try { + await act(async()=>root.render()) + const button=[...element.querySelectorAll('button')].find(b=>b.textContent==='iam');expect(button).toBeDefined();await act(async()=>button!.click()) + expect(onNavigate).toHaveBeenCalledWith({kind:'secrets',group:'',namespace:'app',name:'iam'});expect(element.textContent).toContain('credentials.json') + }finally{await act(async()=>root.unmount())} +}) + +it('retains cluster-auth names without inventing an operator credential namespace',()=>{ + const html=renderToStaticMarkup({}}/>);const doc=new DOMParser().parseFromString(html,'text/html') + expect(doc.body.textContent).toContain('iam');expect(doc.body.textContent).toContain('direct');expect([...doc.querySelectorAll('button')].some(b=>b.textContent==='iam'||b.textContent==='direct')).toBe(false) +}) + +it('does not accept the previously invented shallow GCP reference shape',()=>{ + const html=renderToStaticMarkup();expect(html).not.toContain('fictional') +}) diff --git a/packages/k8s-ui/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx b/packages/k8s-ui/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx index 881ee51234..002144304d 100644 --- a/packages/k8s-ui/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx @@ -21,6 +21,8 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen const awsSecretManager = spec.awsSecretManager const gcpSecretManager = spec.gcpSecretManager const podIdentity = spec.podIdentity + const credentialRef = gcpSecretManager?.credentials?.clientSecret?.valueFrom?.secretKeyRef + const namespace = data.metadata?.namespace return ( <> @@ -48,7 +50,7 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen
{ref.parameter} - Secret: / {ref.key} + Secret: {namespace ? : ref.name} / {ref.key}
@@ -150,9 +152,10 @@ export function KedaTriggerAuthRenderer({ data, onNavigate }: KedaTriggerAuthRen {gcpSecretManager && (
- {gcpSecretManager.credentials?.clientSecret?.name && ( - + {credentialRef?.name && ( + : credentialRef.name} /> )} + {credentialRef?.key && } {gcpSecretManager.secrets && gcpSecretManager.secrets.length > 0 && (
From ef7f47b13d7450650ba545bce908211bb8774cbf Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Wed, 7 Oct 2026 08:55:51 +0300 Subject: [PATCH 2/3] Format credential reference interaction tests --- .../renderers/KedaCredentials.test.tsx | 107 ++++++++++++++---- 1 file changed, 85 insertions(+), 22 deletions(-) diff --git a/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx index 37e2b7ee6b..80f2172260 100644 --- a/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx @@ -1,26 +1,89 @@ // @vitest-environment jsdom -import { act } from 'react' -import { createRoot } from 'react-dom/client' -import { renderToStaticMarkup } from 'react-dom/server' -import { expect, it, vi } from 'vitest' -import { KedaTriggerAuthRenderer } from './KedaTriggerAuthRenderer' -Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) -const spec = {secretTargetRef:[{parameter:'token',name:'direct',key:'token'}],gcpSecretManager:{credentials:{clientSecret:{valueFrom:{secretKeyRef:{name:'iam',key:'credentials.json'}}}}}} +import { act } from "react"; +import { createRoot } from "react-dom/client"; +import { renderToStaticMarkup } from "react-dom/server"; +import { expect, it, vi } from "vitest"; +import { KedaTriggerAuthRenderer } from "./KedaTriggerAuthRenderer"; +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); +const spec = { + secretTargetRef: [{ parameter: "token", name: "direct", key: "token" }], + gcpSecretManager: { + credentials: { + clientSecret: { + valueFrom: { secretKeyRef: { name: "iam", key: "credentials.json" } }, + }, + }, + }, +}; -it('links the actual nested GCP credential Secret in the authentication namespace', async () => { - const element=document.createElement('div');const root=createRoot(element);const onNavigate=vi.fn() - try { - await act(async()=>root.render()) - const button=[...element.querySelectorAll('button')].find(b=>b.textContent==='iam');expect(button).toBeDefined();await act(async()=>button!.click()) - expect(onNavigate).toHaveBeenCalledWith({kind:'secrets',group:'',namespace:'app',name:'iam'});expect(element.textContent).toContain('credentials.json') - }finally{await act(async()=>root.unmount())} -}) +it("links the actual nested GCP credential Secret in the authentication namespace", async () => { + const element = document.createElement("div"); + const root = createRoot(element); + const onNavigate = vi.fn(); + try { + await act(async () => + root.render( + , + ), + ); + const button = [...element.querySelectorAll("button")].find( + (b) => b.textContent === "iam", + ); + expect(button).toBeDefined(); + await act(async () => button!.click()); + expect(onNavigate).toHaveBeenCalledWith({ + kind: "secrets", + group: "", + namespace: "app", + name: "iam", + }); + expect(element.textContent).toContain("credentials.json"); + } finally { + await act(async () => root.unmount()); + } +}); -it('retains cluster-auth names without inventing an operator credential namespace',()=>{ - const html=renderToStaticMarkup({}}/>);const doc=new DOMParser().parseFromString(html,'text/html') - expect(doc.body.textContent).toContain('iam');expect(doc.body.textContent).toContain('direct');expect([...doc.querySelectorAll('button')].some(b=>b.textContent==='iam'||b.textContent==='direct')).toBe(false) -}) +it("retains cluster-auth names without inventing an operator credential namespace", () => { + const html = renderToStaticMarkup( + {}} + />, + ); + const doc = new DOMParser().parseFromString(html, "text/html"); + expect(doc.body.textContent).toContain("iam"); + expect(doc.body.textContent).toContain("direct"); + expect( + [...doc.querySelectorAll("button")].some( + (b) => b.textContent === "iam" || b.textContent === "direct", + ), + ).toBe(false); +}); -it('does not accept the previously invented shallow GCP reference shape',()=>{ - const html=renderToStaticMarkup();expect(html).not.toContain('fictional') -}) +it("does not accept the previously invented shallow GCP reference shape", () => { + const html = renderToStaticMarkup( + , + ); + expect(html).not.toContain("fictional"); +}); From 88d0855f21856284b212c8d2c26aa27b0a14a995 Mon Sep 17 00:00:00 2001 From: Nadav Erell Date: Wed, 7 Oct 2026 12:09:24 +0300 Subject: [PATCH 3/3] Match k8s-ui quote style in KEDA credential tests --- .../renderers/KedaCredentials.test.tsx | 94 +++++++++---------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx index 80f2172260..4bff8b9e81 100644 --- a/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx +++ b/packages/k8s-ui/src/components/resources/renderers/KedaCredentials.test.tsx @@ -1,89 +1,85 @@ // @vitest-environment jsdom -import { act } from "react"; -import { createRoot } from "react-dom/client"; -import { renderToStaticMarkup } from "react-dom/server"; -import { expect, it, vi } from "vitest"; -import { KedaTriggerAuthRenderer } from "./KedaTriggerAuthRenderer"; -Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); +import { act } from 'react' +import { createRoot } from 'react-dom/client' +import { renderToStaticMarkup } from 'react-dom/server' +import { expect, it, vi } from 'vitest' +import { KedaTriggerAuthRenderer } from './KedaTriggerAuthRenderer' +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) const spec = { - secretTargetRef: [{ parameter: "token", name: "direct", key: "token" }], + secretTargetRef: [{ parameter: 'token', name: 'direct', key: 'token' }], gcpSecretManager: { credentials: { clientSecret: { - valueFrom: { secretKeyRef: { name: "iam", key: "credentials.json" } }, + valueFrom: { secretKeyRef: { name: 'iam', key: 'credentials.json' } }, }, }, }, -}; +} -it("links the actual nested GCP credential Secret in the authentication namespace", async () => { - const element = document.createElement("div"); - const root = createRoot(element); - const onNavigate = vi.fn(); +it('links the actual nested GCP credential Secret in the authentication namespace', async () => { + const element = document.createElement('div') + const root = createRoot(element) + const onNavigate = vi.fn() try { await act(async () => root.render( , ), - ); - const button = [...element.querySelectorAll("button")].find( - (b) => b.textContent === "iam", - ); - expect(button).toBeDefined(); - await act(async () => button!.click()); + ) + const button = [...element.querySelectorAll('button')].find(b => b.textContent === 'iam') + expect(button).toBeDefined() + await act(async () => button!.click()) expect(onNavigate).toHaveBeenCalledWith({ - kind: "secrets", - group: "", - namespace: "app", - name: "iam", - }); - expect(element.textContent).toContain("credentials.json"); + kind: 'secrets', + group: '', + namespace: 'app', + name: 'iam', + }) + expect(element.textContent).toContain('credentials.json') } finally { - await act(async () => root.unmount()); + await act(async () => root.unmount()) } -}); +}) -it("retains cluster-auth names without inventing an operator credential namespace", () => { +it('retains cluster-auth names without inventing an operator credential namespace', () => { const html = renderToStaticMarkup( {}} />, - ); - const doc = new DOMParser().parseFromString(html, "text/html"); - expect(doc.body.textContent).toContain("iam"); - expect(doc.body.textContent).toContain("direct"); - expect( - [...doc.querySelectorAll("button")].some( - (b) => b.textContent === "iam" || b.textContent === "direct", - ), - ).toBe(false); -}); + ) + const doc = new DOMParser().parseFromString(html, 'text/html') + expect(doc.body.textContent).toContain('iam') + expect(doc.body.textContent).toContain('direct') + expect([...doc.querySelectorAll('button')].some(b => b.textContent === 'iam' || b.textContent === 'direct')).toBe( + false, + ) +}) -it("does not accept the previously invented shallow GCP reference shape", () => { +it('does not accept the previously invented shallow GCP reference shape', () => { const html = renderToStaticMarkup( , - ); - expect(html).not.toContain("fictional"); -}); + ) + expect(html).not.toContain('fictional') +})