From c8ccd9c88abfe872df6a0895b6302122121406e3 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Wed, 20 Mar 2024 03:02:21 -0500 Subject: [PATCH 1/9] feat: introduce sso secret templating --- src/pepr/operator/README.md | 64 ++++++++++++++++++- .../controllers/keycloak/client-sync.ts | 61 ++++++++++++++---- .../crd/generated/package-v1alpha1.ts | 4 ++ .../operator/crd/sources/package/v1alpha1.ts | 8 +++ 4 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src/pepr/operator/README.md b/src/pepr/operator/README.md index bdc02ebf2..5bd7d9d69 100644 --- a/src/pepr/operator/README.md +++ b/src/pepr/operator/README.md @@ -3,6 +3,7 @@ The UDS Operator manages the lifecycle of UDS Package CRs and their corresponding resources (e.g. NetworkPolicies, Istio VirtualServices, etc.) as well UDS Exemption CRs. The operator uses [Pepr](https://pepr.dev) to bind the watch operations to the enqueue and reconciler. The operator is responsible for: #### Package + - enabling Istio sidecar injection in namespaces where the CR is deployed - establishing default-deny ingress/egress network policies - creating a layered allow-list based approach on top of the default deny network policies including some basic defaults such as Istio requirements and DNS egress @@ -10,6 +11,7 @@ The UDS Operator manages the lifecycle of UDS Package CRs and their correspondin - creating Istio Virtual Services & related ingress gateway network policies #### Exemption + - allowing exemption custom resources only in the `uds-policy-exemptions` namespace unless configured to allow in all namespaces (see [configuring policy exemptions](../../../docs/CONFIGURE_POLICY_EXEMPTIONS.md)) - updating the policies Pepr store with registered exemptions @@ -23,6 +25,7 @@ metadata: namespace: grafana spec: network: + # Expose rules generate Istio VirtualServices and related network policies expose: - service: grafana selector: @@ -32,6 +35,7 @@ spec: port: 80 targetPort: 3000 + # Allow rules generate NetworkPolicies allow: - direction: Egress selector: @@ -44,6 +48,12 @@ spec: app.kubernetes.io/name: tempo port: 9411 description: "Tempo" + # SSO allows for the creation of Keycloak clients and with automatic secret generation + sso: + - name: Grafana Dashboard + clientId: uds-core-admin-grafana + redirectUris: + - "https://grafana.admin.uds.dev/login/generic_oauth" ``` ### Example UDS Exemption CR @@ -66,7 +76,7 @@ spec: matcher: namespace: neuvector name: "^neuvector-enforcer-pod.*" - + - policies: - DisallowPrivileged - RequireNonRootUser @@ -81,7 +91,56 @@ spec: - DropAllCapabilities matcher: namespace: neuvector - name: "^neuvector-prometheus-exporter-pod.*" + name: "^neuvector-prometheus-exporter-pod.*" +``` + +### Example UDS Package CR with SSO Templating + +By default UDS generates a secret for the SSO client with all the contents of the client as an opaque secret such that each key is it's own env variable or file (depending on how you mount the secret). If you need to customize how the secret is rendered, you can perform some basic templating with the `secretTemplate` property. Below are some examples of this usage. You can also see how templating works via this regex site: https://regex101.com/r/e41Dsk/3. + +```yaml +apiVersion: uds.dev/v1alpha1 +kind: Package +metadata: + name: grafana + namespace: grafana +spec: + sso: + - name: My Keycloak Client + clientId: demo-client + redirectUris: + - "https://demo.uds.dev/login" + # Customize the name of the generated secret + secretName: my-cool-auth-client + secretTemplate: + # Raw text examples + rawTextClientId: "clientField(clientId)" + rawTextClientSecret: "clientField(secret)" + + # JSON example + auth.json: | + { + "client_id": "clientField(clientId)", + "client_secret": "clientField(secret)", + "defaultScopes": clientField(defaultClientScopes).json(), + "redirect_uri": "clientField(redirectUris)[0]", + "bearerOnly": clientField(bearerOnly), + } + + # Properties example + auth.properties: | + client-id=clientField(clientId) + client-secret=clientField(secret) + default-scopes=clientField(defaultClientScopes) + redirect-uri=clientField(redirectUris)[0] + + # YAML example (uses JSON for the defaultScopes array) + auth.yaml: | + client_id: clientField(clientId) + client_secret: clientField(secret) + default_scopes: clientField(defaultClientScopes).json() + redirect_uri: clientField(redirectUris)[0] + bearer_only: clientField(bearerOnly) ``` ### Key Files and Folders @@ -102,6 +161,7 @@ src/pepr/operator/ ├── index.ts # Entrypoint for the UDS Operator └── reconcilers # Reconciles Custom Resources via the controllers ``` + ### Flow The UDS Operator leverages a Pepr Watch. The following diagram shows the flow of the UDS Operator: diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index 8e8504bf7..5806dc53f 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -8,6 +8,12 @@ import { Client } from "./types"; const apiURL = "http://keycloak-http.keycloak.svc.cluster.local:8080/realms/uds/clients-registrations/default"; +// Template regex to match clientField() references, see https://regex101.com/r/e41Dsk/3 for details +const secretTemplateRegex = new RegExp( + 'clientField\\(([a-zA-Z]+)\\)(?:\\["?([\\w]+)"?\\]|(\\.json\\(\\)))?', + "gm", +); + /** * Create or update the Keycloak clients for the package * @@ -53,7 +59,7 @@ export async function purgeSSOClients(pkg: UDSPackage, refs: string[] = []) { } async function syncClient( - { isAuthSvcClient, ...clientReq }: Sso, + { isAuthSvcClient, secretName, secretTemplate, ...clientReq }: Sso, pkg: UDSPackage, isRetry = false, ) { @@ -61,7 +67,7 @@ async function syncClient( try { // Not including the CR data in the ref because Keycloak client IDs must be unique already - const name = getClientName(clientReq); + const name = `sso-client-${clientReq.clientId}`; const token = Store.getItem(name); let client: Client; @@ -86,9 +92,9 @@ async function syncClient( metadata: { namespace: pkg.metadata!.namespace, // Use the CR secret name if provided, otherwise use the client name - name: clientReq.secretName || name, + name: secretName || name, }, - stringData: clientToStringmap(client), + stringData: generateSecretData(client, secretTemplate), }); if (isAuthSvcClient) { @@ -99,12 +105,12 @@ async function syncClient( } catch (err) { const msg = `Failed to process client request '${clientReq.clientId}' for ` + - `${pkg.metadata?.namespace}/${pkg.metadata?.name}`; + `${pkg.metadata?.namespace}/${pkg.metadata?.name}. This can occur if a client already exists with the same ID that Pepr isn't tracking.`; Log.error({ err }, msg); if (isRetry) { Log.error(`${msg}, retry failed, aborting`); - throw err; + throw new Error(`${msg}. RETRY FAILED, aborting: ${JSON.stringify(err)}`); } // Retry the request @@ -155,13 +161,14 @@ async function apiCall(sso: Partial, method = "POST", authToken = "") { return resp.data; } -function getClientName(client: Partial) { - return `sso-client-${client.clientId}`; -} - -function clientToStringmap(client: Client) { +function generateSecretData(client: Client, secretTemplate?: { [key: string]: string }) { const stringMap: Record = {}; + if (secretTemplate) { + // Iterate over the secret template entry and process each value + return templateData(secretTemplate, stringMap, client); + } + // iterate over the client object and convert all values to strings for (const [key, value] of Object.entries(client)) { if (typeof value === "object") { @@ -175,3 +182,35 @@ function clientToStringmap(client: Client) { return stringMap; } +function templateData( + secretTemplate: { [key: string]: string }, + stringMap: Record, + client: Client, +) { + for (const [key, value] of Object.entries(secretTemplate)) { + // Replace any clientField() references with the actual client data + stringMap[key] = value.replace( + secretTemplateRegex, + (_match, fieldName: keyof Client, key, json) => { + // Make typescript happy with a more generic type + const value = client[fieldName] as Record | string; + + // If a key is provided, use it to get the value + if (key) { + return String(value[key] ?? ""); + } + + // If .json() is provided, convert the value to a JSON string + if (json) { + return JSON.stringify(value); + } + + // Otherwise, convert the value to a string + return value !== undefined ? String(value) : ""; + }, + ); + } + + // Return the processed secret template without any further processing + return stringMap; +} diff --git a/src/pepr/operator/crd/generated/package-v1alpha1.ts b/src/pepr/operator/crd/generated/package-v1alpha1.ts index 125371424..0cd7091d5 100644 --- a/src/pepr/operator/crd/generated/package-v1alpha1.ts +++ b/src/pepr/operator/crd/generated/package-v1alpha1.ts @@ -466,6 +466,10 @@ export interface Sso { * The name of the secret to store the client secret */ secretName?: string; + /** + * A template for the generated secret + */ + secretTemplate?: { [key: string]: string }; /** * Allowed CORS origins. To permit all origins of Valid Redirect URIs, add '+'. This does * not include the '*' wildcard though. To permit all origins, explicitly add '*'. diff --git a/src/pepr/operator/crd/sources/package/v1alpha1.ts b/src/pepr/operator/crd/sources/package/v1alpha1.ts index 07bf5fdd8..bffaa106a 100644 --- a/src/pepr/operator/crd/sources/package/v1alpha1.ts +++ b/src/pepr/operator/crd/sources/package/v1alpha1.ts @@ -175,6 +175,14 @@ const sso = { description: "The name of the secret to store the client secret", type: "string", }, + secretTemplate: { + description: "A template for the generated secret", + // Create a map of the secret data + type: "object", + additionalProperties: { + type: "string", + }, + }, clientId: { description: "The client identifier registered with the identity provider.", type: "string", From d7c04405126bae0a7d9acbf4befb6f2e04a9b442 Mon Sep 17 00:00:00 2001 From: TristanHoladay <40547442+TristanHoladay@users.noreply.github.com> Date: Thu, 21 Mar 2024 21:05:39 -0600 Subject: [PATCH 2/9] unit tests for generateSecretData() --- src/pepr/operator/README.md | 1 + .../controllers/keycloak/client-sync.spec.ts | 94 +++++++++++++++++++ .../controllers/keycloak/client-sync.ts | 5 +- 3 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/pepr/operator/controllers/keycloak/client-sync.spec.ts diff --git a/src/pepr/operator/README.md b/src/pepr/operator/README.md index 5bd7d9d69..49e157f03 100644 --- a/src/pepr/operator/README.md +++ b/src/pepr/operator/README.md @@ -48,6 +48,7 @@ spec: app.kubernetes.io/name: tempo port: 9411 description: "Tempo" + # SSO allows for the creation of Keycloak clients and with automatic secret generation sso: - name: Grafana Dashboard diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts new file mode 100644 index 000000000..a1687c0fc --- /dev/null +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from "@jest/globals"; +import { generateSecretData } from "./client-sync"; +import { Client } from "./types"; + +const mockClient: Client = { + alwaysDisplayInConsole: true, + attributes: { first: "attribute" }, + authenticationFlowBindingOverrides: {}, + bearerOnly: true, + clientAuthenticatorType: "", + clientId: "testId", + consentRequired: true, + defaultClientScopes: [], + defaultRoles: [], + directAccessGrantsEnabled: true, + enabled: true, + frontchannelLogout: true, + fullScopeAllowed: true, + implicitFlowEnabled: true, + nodeReRegistrationTimeout: 1, + notBefore: 1, + optionalClientScopes: [], + protocol: "", + publicClient: true, + secret: "", + redirectUris: ["https://demo.uds.dev/login"], + registrationAccessToken: "", + surrogateAuthRequired: true, + serviceAccountsEnabled: true, + webOrigins: [], + standardFlowEnabled: true, +}; + +const mockClientStringified = { + alwaysDisplayInConsole: "true", + attributes: '{"first":"attribute"}', + authenticationFlowBindingOverrides: "{}", + bearerOnly: "true", + clientAuthenticatorType: "", + clientId: "testId", + consentRequired: "true", + defaultClientScopes: "[]", + defaultRoles: "[]", + directAccessGrantsEnabled: "true", + enabled: "true", + frontchannelLogout: "true", + fullScopeAllowed: "true", + implicitFlowEnabled: "true", + nodeReRegistrationTimeout: "1", + notBefore: "1", + optionalClientScopes: "[]", + protocol: "", + publicClient: "true", + secret: "", + redirectUris: '["https://demo.uds.dev/login"]', + registrationAccessToken: "", + surrogateAuthRequired: "true", + serviceAccountsEnabled: "true", + webOrigins: "[]", + standardFlowEnabled: "true", +}; + +describe("Test Secret & Template Data Generation", () => { + it("generates data without template", async () => { + expect(generateSecretData(mockClient)).toEqual(mockClientStringified); + }); + + it("generates data from template: no key or .json()", () => { + const mockTemplate = { + "auth.json": JSON.stringify({ client_id: "clientField(clientId)" }), + }; + expect(generateSecretData(mockClient, mockTemplate)).toEqual({ + "auth.json": '{"client_id":"testId"}', + }); + }); + + it("generates data from template: has key", () => { + const mockTemplate = { + "auth.json": JSON.stringify({ redirect_uri: "clientField(redirectUris)[0]" }), + }; + expect(generateSecretData(mockClient, mockTemplate)).toEqual({ + "auth.json": '{"redirect_uri":"https://demo.uds.dev/login"}', + }); + }); + + it("generates data from template: has .json()", () => { + const mockTemplate = { + "auth.json": JSON.stringify({ defaultScopes: "clientField(attributes).json()" }), + }; + expect(generateSecretData(mockClient, mockTemplate)).toEqual({ + "auth.json": '{"defaultScopes":"{"first":"attribute"}"}', + }); + }); +}); diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index 5806dc53f..dd42ada00 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -72,7 +72,7 @@ async function syncClient( let client: Client; - // If and existing client is found, update it + // If an existing client is found, update it if (token && !isRetry) { Log.debug(pkg.metadata, `Found existing token for ${clientReq.clientId}`); client = await apiCall(clientReq, "PUT", token); @@ -161,7 +161,7 @@ async function apiCall(sso: Partial, method = "POST", authToken = "") { return resp.data; } -function generateSecretData(client: Client, secretTemplate?: { [key: string]: string }) { +export function generateSecretData(client: Client, secretTemplate?: { [key: string]: string }) { const stringMap: Record = {}; if (secretTemplate) { @@ -182,6 +182,7 @@ function generateSecretData(client: Client, secretTemplate?: { [key: string]: st return stringMap; } + function templateData( secretTemplate: { [key: string]: string }, stringMap: Record, From b6fb5af1f8e605a36d962133e30164d0ac8b23bc Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Thu, 21 Mar 2024 22:09:18 -0500 Subject: [PATCH 3/9] pepr 0.28.5 --- package-lock.json | 278 +++++++++++++++++----------------------------- package.json | 2 +- 2 files changed, 105 insertions(+), 175 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5799f2bb0..23ef50a5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "uds-core", "version": "0.4.0", "dependencies": { - "pepr": "0.28.3" + "pepr": "0.28.5" }, "devDependencies": { "@jest/globals": "29.7.0", @@ -42,113 +42,42 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", "dev": true, "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", - "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.1.tgz", + "integrity": "sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", - "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.3.tgz", + "integrity": "sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.1", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.24.0", - "@babel/parser": "^7.24.0", + "@babel/helpers": "^7.24.1", + "@babel/parser": "^7.24.1", "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -174,14 +103,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", - "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.1.tgz", + "integrity": "sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==", "dev": true, "dependencies": { - "@babel/types": "^7.23.6", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", + "@babel/types": "^7.24.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" }, "engines": { @@ -248,12 +177,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", - "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -312,9 +241,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -339,13 +268,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", - "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.1.tgz", + "integrity": "sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==", "dev": true, "dependencies": { "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.0", + "@babel/traverse": "^7.24.1", "@babel/types": "^7.24.0" }, "engines": { @@ -353,14 +282,15 @@ } }, "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", + "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" @@ -438,9 +368,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", - "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", + "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -510,12 +440,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", - "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz", + "integrity": "sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -612,12 +542,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", - "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz", + "integrity": "sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.0" }, "engines": { "node": ">=6.9.0" @@ -641,18 +571,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", - "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.1.tgz", + "integrity": "sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/generator": "^7.23.6", + "@babel/code-frame": "^7.24.1", + "@babel/generator": "^7.24.1", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.24.0", + "@babel/parser": "^7.24.1", "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" @@ -1768,9 +1698,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "20.11.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", - "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dependencies": { "undici-types": "~5.26.4" } @@ -2543,9 +2473,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001597", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001597.tgz", - "integrity": "sha512-7LjJvmQU6Sj7bL0j5b5WY/3n7utXUJvAe1lxhsHDbLmwX9mdL86Yjtr+5SRCyf8qME4M7pU2hswj0FpyBVCv9w==", + "version": "1.0.30001599", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", + "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", "dev": true, "funding": [ { @@ -2721,9 +2651,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -2917,9 +2847,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.705", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.705.tgz", - "integrity": "sha512-LKqhpwJCLhYId2VVwEzFXWrqQI5n5zBppz1W9ehhTlfYU8CUUW6kClbN8LHF/v7flMgRdETS772nqywJ+ckVAw==", + "version": "1.4.715", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.715.tgz", + "integrity": "sha512-XzWNH4ZSa9BwVUQSDorPWAUQ5WGuYz7zJUNpNif40zFCiCl20t8zgylmreNmn26h5kiyw2lg7RfTmeMBsDklqg==", "dev": true }, "node_modules/emittery": { @@ -3274,16 +3204,16 @@ } }, "node_modules/express": { - "version": "4.18.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz", - "integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==", + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.1.tgz", + "integrity": "sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3328,9 +3258,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/fast-copy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", - "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -3383,9 +3313,9 @@ "peer": true }, "node_modules/fast-redact": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.4.0.tgz", - "integrity": "sha512-2gwPvyna0zwBdxKnng1suu/dTL5s8XEy2ZqH8mwDUwJdDkV8w5kp+JV26mupdK68HmPMbm6yjW9m7/Ys/BHEHg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", "engines": { "node": ">=6" } @@ -4762,17 +4692,17 @@ } }, "node_modules/kubernetes-fluent-client": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/kubernetes-fluent-client/-/kubernetes-fluent-client-2.2.3.tgz", - "integrity": "sha512-rRkYEDWpEaNa4bg4xKrd2NJwlsDOQ3xoFTF8Ji27zK+vmNvi5oRBk7PKylViTJdBAYyqiP0GOw0nmnMTEgnPfw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/kubernetes-fluent-client/-/kubernetes-fluent-client-2.3.0.tgz", + "integrity": "sha512-G/bVkRwyMItLdc1WJNjEsqBYjGCZz3uC136/OdNCRaPAdeMBQKuE4Gm7XAZPiJk3o8O23Ja9wkHZzL+H73zLMA==", "dependencies": { "@kubernetes/client-node": "1.0.0-rc4", "byline": "5.0.0", "fast-json-patch": "3.1.1", "http-status-codes": "2.3.0", "node-fetch": "2.7.0", - "quicktype-core": "23.0.105", - "type-fest": "4.12.0", + "quicktype-core": "23.0.107", + "type-fest": "4.13.1", "yargs": "17.7.2" }, "bin": { @@ -4783,9 +4713,9 @@ } }, "node_modules/kubernetes-fluent-client/node_modules/type-fest": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.12.0.tgz", - "integrity": "sha512-5Y2/pp2wtJk8o08G0CMkuFPCO354FGwk/vbidxrdhRGZfd0tFnb4Qb8anp9XxXriwBgVPjdWbKpGl4J9lJY2jQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.13.1.tgz", + "integrity": "sha512-ASMgM+Vf2cLwDMt1KXSkMUDSYCxtckDJs8zsaVF/mYteIsiARKCVtyXtcK38mIKbLTctZP8v6GMqdNaeI3fo7g==", "engines": { "node": ">=16" }, @@ -5363,16 +5293,16 @@ } }, "node_modules/pepr": { - "version": "0.28.3", - "resolved": "https://registry.npmjs.org/pepr/-/pepr-0.28.3.tgz", - "integrity": "sha512-DytQRy6a9HWB4751pTzzchIXhz96T0URLE8a21LoZF0hgwsW2hSCvn9ysJFolhvK05goTVNL0nZ1PjiuC4T1Lw==", + "version": "0.28.5", + "resolved": "https://registry.npmjs.org/pepr/-/pepr-0.28.5.tgz", + "integrity": "sha512-gpdmMuK/ijFPFXvoqAQnk6LHdt+HELFKmWrJV1TAB2g3uSQRd4d5+E64HRbz+qthnuwFcavmKhAf0B/fznzMWQ==", "dependencies": { "@types/ramda": "0.29.11", - "express": "4.18.3", + "express": "4.19.1", "fast-json-patch": "3.1.1", - "kubernetes-fluent-client": "2.2.3", + "kubernetes-fluent-client": "2.3.0", "pino": "8.19.0", - "pino-pretty": "10.3.1", + "pino-pretty": "11.0.0", "prom-client": "15.1.0", "ramda": "0.29.1" }, @@ -5443,9 +5373,9 @@ } }, "node_modules/pino-pretty": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.3.1.tgz", - "integrity": "sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.0.0.tgz", + "integrity": "sha512-YFJZqw59mHIY72wBnBs7XhLGG6qpJMa4pEQTRgEPEbjIYbng2LXEZZF1DoyDg9CfejEy8uZCyzpcBXXG0oOCwQ==", "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", @@ -5670,9 +5600,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", - "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", "dev": true, "funding": [ { @@ -5730,9 +5660,9 @@ "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, "node_modules/quicktype-core": { - "version": "23.0.105", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.105.tgz", - "integrity": "sha512-amxAkFPUMfvU5uzmIAwdCFo30bPj3TrSnFq3dFoIgmcltPPMdOxFzcwHG9DCHveR4u6Qj0DRWj7IQ4e/1TEJeA==", + "version": "23.0.107", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.107.tgz", + "integrity": "sha512-Z985C3qiXngj6xhAc3B2+vZOvBFFWUEUu8Jz4WGwOGMQmUXVb2XQN+wc5Fotviqz+P7QkHoX9KqjyjQTy1YQhg==", "dependencies": { "@glideapps/ts-necessities": "2.1.3", "@types/urijs": "^1.19.19", @@ -6322,9 +6252,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", diff --git a/package.json b/package.json index 572e1a732..22b35ca9e 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "k3d-setup": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0'" }, "dependencies": { - "pepr": "0.28.3" + "pepr": "0.28.5" }, "devDependencies": { "@jest/globals": "29.7.0", From 222e2f64670e0f2cf5f34352af3d236c66ea35ef Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Thu, 21 Mar 2024 22:15:31 -0500 Subject: [PATCH 4/9] use data vs stringData due to k8s oddities --- .../controllers/keycloak/client-sync.ts | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index dd42ada00..5c0ba96c4 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -94,7 +94,7 @@ async function syncClient( // Use the CR secret name if provided, otherwise use the client name name: secretName || name, }, - stringData: generateSecretData(client, secretTemplate), + data: generateSecretData(client, secretTemplate), }); if (isAuthSvcClient) { @@ -162,35 +162,39 @@ async function apiCall(sso: Partial, method = "POST", authToken = "") { } export function generateSecretData(client: Client, secretTemplate?: { [key: string]: string }) { - const stringMap: Record = {}; - if (secretTemplate) { // Iterate over the secret template entry and process each value - return templateData(secretTemplate, stringMap, client); + return templateData(secretTemplate, client); } + const stringMap: Record = {}; + // iterate over the client object and convert all values to strings for (const [key, value] of Object.entries(client)) { - if (typeof value === "object") { - // For objects and arrays, convert to a JSON string - stringMap[key] = JSON.stringify(value); - } else { - // For primitive values, convert directly to string - stringMap[key] = String(value); - } + // For objects and arrays, convert to a JSON string + const processed = typeof value === "object" ? JSON.stringify(value) : String(value); + + // Convert the value to a base64 encoded string + stringMap[key] = Buffer.from(processed).toString("base64"); } return stringMap; } -function templateData( - secretTemplate: { [key: string]: string }, - stringMap: Record, - client: Client, -) { +/** + * Process the secret template and convert the client data to base64 encoded strings for use in a secret + * + * @param secretTemplate The template to use for generating the secret + * @param client + * @returns + */ +function templateData(secretTemplate: { [key: string]: string }, client: Client) { + const stringMap: Record = {}; + + // Iterate over the secret template and process each entry for (const [key, value] of Object.entries(secretTemplate)) { // Replace any clientField() references with the actual client data - stringMap[key] = value.replace( + const templated = value.replace( secretTemplateRegex, (_match, fieldName: keyof Client, key, json) => { // Make typescript happy with a more generic type @@ -210,6 +214,9 @@ function templateData( return value !== undefined ? String(value) : ""; }, ); + + // Convert the templated value to a base64 encoded string + stringMap[key] = Buffer.from(templated).toString("base64"); } // Return the processed secret template without any further processing From 125180faa70e24f055c7092f1a71f875a9b2b099 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Thu, 21 Mar 2024 22:16:02 -0500 Subject: [PATCH 5/9] more logging for skipping CRs --- src/pepr/operator/reconcilers/exempt-reconciler.ts | 4 ++-- src/pepr/operator/reconcilers/index.spec.ts | 10 +++++----- src/pepr/operator/reconcilers/index.ts | 8 +++++--- src/pepr/operator/reconcilers/package-reconciler.ts | 11 ++++++----- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/pepr/operator/reconcilers/exempt-reconciler.ts b/src/pepr/operator/reconcilers/exempt-reconciler.ts index 45ae03a41..f026285f3 100644 --- a/src/pepr/operator/reconcilers/exempt-reconciler.ts +++ b/src/pepr/operator/reconcilers/exempt-reconciler.ts @@ -1,11 +1,11 @@ import { Log } from "pepr"; -import { handleFailure, isPendingOrCurrent, updateStatus } from "."; +import { handleFailure, shouldSkip, updateStatus } from "."; import { processExemptions } from "../controllers/exemptions/exemptions"; import { Phase, UDSExemption } from "../crd"; export async function exemptReconciler(exempt: UDSExemption) { - if (isPendingOrCurrent(exempt)) { + if (shouldSkip(exempt)) { return; } diff --git a/src/pepr/operator/reconcilers/index.spec.ts b/src/pepr/operator/reconcilers/index.spec.ts index 36f7962b0..39c3ad614 100644 --- a/src/pepr/operator/reconcilers/index.spec.ts +++ b/src/pepr/operator/reconcilers/index.spec.ts @@ -3,7 +3,7 @@ import { GenericKind } from "kubernetes-fluent-client"; import { K8s, Log, kind } from "pepr"; import { Mock } from "jest-mock"; -import { handleFailure, isPendingOrCurrent, updateStatus, writeEvent } from "."; +import { handleFailure, shouldSkip, updateStatus, writeEvent } from "."; import { ExemptStatus, Phase, PkgStatus, UDSExemption, UDSPackage } from "../crd"; jest.mock("pepr", () => ({ @@ -29,12 +29,12 @@ describe("isPendingOrCurrent", () => { it("should return false for a new CR", () => { const cr = { metadata: { uid: "1" }, status: { phase: Phase.Pending } } as UDSPackage; - expect(isPendingOrCurrent(cr)).toBe(false); + expect(shouldSkip(cr)).toBe(false); }); it("should return true for a pending CR on subsequent calls", () => { const cr = { metadata: { uid: "1" }, status: { phase: Phase.Pending } } as UDSPackage; - expect(isPendingOrCurrent(cr)).toBe(true); + expect(shouldSkip(cr)).toBe(true); }); it("should return true for a CR with current generation on subsequent calls", () => { @@ -42,7 +42,7 @@ describe("isPendingOrCurrent", () => { metadata: { uid: "1", generation: 1 }, status: { observedGeneration: 1 }, } as UDSPackage; - expect(isPendingOrCurrent(cr)).toBe(true); + expect(shouldSkip(cr)).toBe(true); }); it("should return false for a CR with different generation on subsequent calls", () => { @@ -50,7 +50,7 @@ describe("isPendingOrCurrent", () => { metadata: { uid: "1", generation: 2 }, status: { observedGeneration: 1 }, } as UDSPackage; - expect(isPendingOrCurrent(cr)).toBe(false); + expect(shouldSkip(cr)).toBe(false); }); }); diff --git a/src/pepr/operator/reconcilers/index.ts b/src/pepr/operator/reconcilers/index.ts index 473d1dfd9..098fe5a11 100644 --- a/src/pepr/operator/reconcilers/index.ts +++ b/src/pepr/operator/reconcilers/index.ts @@ -12,24 +12,26 @@ const uidSeen = new Set(); * @param cr The custom resource to check * @returns true if the CRD is pending or the current generation has been processed */ -export function isPendingOrCurrent(cr: UDSExemption | UDSPackage) { +export function shouldSkip(cr: UDSExemption | UDSPackage) { const isPending = cr.status?.phase === Phase.Pending; const isCurrentGeneration = cr.metadata?.generation === cr.status?.observedGeneration; // First check if the CR has been seen before and return false if it has not // This ensures that all CRs are processed at least once during the lifetime of the pod if (!uidSeen.has(cr.metadata!.uid!)) { - Log.debug(cr, `First time processed during this pod's lifetime`); + Log.debug(cr, `Should skip? No, first time processed during this pod's lifetime`); uidSeen.add(cr.metadata!.uid!); return false; } // This is the second time the CR has been seen, so check if it is pending or the current generation if (isPending || isCurrentGeneration) { - Log.debug(cr, `Skipping pending or completed exemption`); + Log.debug(cr, `Should skip? Yes, pending or current generation and not first time seen`); return true; } + Log.debug(cr, `Should skip? No, not pending or current generation and not first time seen`); + return false; } diff --git a/src/pepr/operator/reconcilers/package-reconciler.ts b/src/pepr/operator/reconcilers/package-reconciler.ts index 5910da9f8..ef8a1eace 100644 --- a/src/pepr/operator/reconcilers/package-reconciler.ts +++ b/src/pepr/operator/reconcilers/package-reconciler.ts @@ -1,6 +1,6 @@ import { Log } from "pepr"; -import { handleFailure, isPendingOrCurrent, updateStatus } from "."; +import { handleFailure, shouldSkip, updateStatus } from "."; import { UDSConfig } from "../../config"; import { enableInjection } from "../controllers/istio/injection"; import { virtualService } from "../controllers/istio/virtual-service"; @@ -16,15 +16,16 @@ import { migrate } from "../crd/migrate"; * @param pkg the package to reconcile */ export async function packageReconciler(pkg: UDSPackage) { - if (isPendingOrCurrent(pkg)) { - return; - } - const metadata = pkg.metadata!; const { namespace, name } = metadata; Log.info(pkg, `Processing Package ${namespace}/${name}`); + if (shouldSkip(pkg)) { + Log.info(pkg, `Skipping Package ${namespace}/${name}`); + return; + } + // Migrate the package to the latest version migrate(pkg); From ad638d556533fcd1654eb6e7062816f9dd319a78 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Fri, 22 Mar 2024 00:53:36 -0500 Subject: [PATCH 6/9] re-add debug logging --- package-lock.json | 6 +++--- package.json | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23ef50a5e..e2d74e937 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2473,9 +2473,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001599", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz", - "integrity": "sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA==", + "version": "1.0.30001600", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", + "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index 22b35ca9e..7d74279ea 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "name": "UDS Core", "uuid": "uds-core", "onError": "reject", + "logLevel": "debug", "alwaysIgnore": { "namespaces": [ "uds-dev-stack", From 931df644ad725a962e53d566bb4136c8ba10fb9d Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Fri, 22 Mar 2024 02:09:59 -0500 Subject: [PATCH 7/9] more logs --- src/pepr/operator/controllers/keycloak/client-sync.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index 5c0ba96c4..c34ec25da 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -163,12 +163,15 @@ async function apiCall(sso: Partial, method = "POST", authToken = "") { export function generateSecretData(client: Client, secretTemplate?: { [key: string]: string }) { if (secretTemplate) { + Log.debug(`Using secret template for client: ${client.clientId}`); // Iterate over the secret template entry and process each value return templateData(secretTemplate, client); } const stringMap: Record = {}; + Log.debug(`Using client data for secret: ${client.clientId}`); + // iterate over the client object and convert all values to strings for (const [key, value] of Object.entries(client)) { // For objects and arrays, convert to a JSON string From d67b1f2b53bd4abcd556f319c11724e352003864 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Fri, 22 Mar 2024 02:10:07 -0500 Subject: [PATCH 8/9] update tests --- .../controllers/keycloak/client-sync.spec.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts index a1687c0fc..ac733af55 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -31,7 +31,7 @@ const mockClient: Client = { standardFlowEnabled: true, }; -const mockClientStringified = { +const mockClientStringified: Record = { alwaysDisplayInConsole: "true", attributes: '{"first":"attribute"}', authenticationFlowBindingOverrides: "{}", @@ -62,7 +62,12 @@ const mockClientStringified = { describe("Test Secret & Template Data Generation", () => { it("generates data without template", async () => { - expect(generateSecretData(mockClient)).toEqual(mockClientStringified); + const expected: Record = {}; + + for (const key in mockClientStringified) { + expected[key] = Buffer.from(mockClientStringified[key]).toString("base64"); + } + expect(generateSecretData(mockClient)).toEqual(expected); }); it("generates data from template: no key or .json()", () => { @@ -70,7 +75,7 @@ describe("Test Secret & Template Data Generation", () => { "auth.json": JSON.stringify({ client_id: "clientField(clientId)" }), }; expect(generateSecretData(mockClient, mockTemplate)).toEqual({ - "auth.json": '{"client_id":"testId"}', + "auth.json": Buffer.from('{"client_id":"testId"}').toString("base64"), }); }); @@ -79,7 +84,7 @@ describe("Test Secret & Template Data Generation", () => { "auth.json": JSON.stringify({ redirect_uri: "clientField(redirectUris)[0]" }), }; expect(generateSecretData(mockClient, mockTemplate)).toEqual({ - "auth.json": '{"redirect_uri":"https://demo.uds.dev/login"}', + "auth.json": Buffer.from('{"redirect_uri":"https://demo.uds.dev/login"}').toString("base64"), }); }); @@ -88,7 +93,7 @@ describe("Test Secret & Template Data Generation", () => { "auth.json": JSON.stringify({ defaultScopes: "clientField(attributes).json()" }), }; expect(generateSecretData(mockClient, mockTemplate)).toEqual({ - "auth.json": '{"defaultScopes":"{"first":"attribute"}"}', + "auth.json": Buffer.from('{"defaultScopes":"{"first":"attribute"}"}').toString("base64"), }); }); }); From 93f162d738a040024ae57951936db07df5ada9d8 Mon Sep 17 00:00:00 2001 From: Jeff McCoy Date: Fri, 22 Mar 2024 02:10:27 -0500 Subject: [PATCH 9/9] fix cleanup issues --- src/pepr/operator/controllers/keycloak/client-sync.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index c34ec25da..4c335d477 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -3,6 +3,7 @@ import { K8s, Log, fetch, kind } from "pepr"; import { UDSConfig } from "../../../config"; import { Store } from "../../common"; import { Sso, UDSPackage } from "../../crd"; +import { getOwnerRef } from "../utils"; import { Client } from "./types"; const apiURL = @@ -51,6 +52,7 @@ export async function purgeSSOClients(pkg: UDSPackage, refs: string[] = []) { const token = Store.getItem(ref); const clientId = ref.replace("sso-client-", ""); if (token) { + Store.removeItem(ref); await apiCall({ clientId }, "DELETE", token); } else { Log.warn(pkg.metadata, `Failed to remove client ${clientId}, token not found`); @@ -93,6 +95,11 @@ async function syncClient( namespace: pkg.metadata!.namespace, // Use the CR secret name if provided, otherwise use the client name name: secretName || name, + labels: { + "uds/package": pkg.metadata!.name, + }, + // Use the CR as the owner ref for each VirtualService + ownerReferences: getOwnerRef(pkg), }, data: generateSecretData(client, secretTemplate), });