From bdb4e69f9f06b79af1e215f1b90b25605d8e9a2c Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Thu, 20 Jun 2024 12:16:24 -0600 Subject: [PATCH 1/8] fix: working pepr implementation --- .../operator/controllers/keycloak/client-sync.ts | 14 ++++++++++++++ .../operator/crd/generated/package-v1alpha1.ts | 14 ++++++++++++++ src/pepr/operator/crd/sources/package/v1alpha1.ts | 13 +++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index f7233d004..ca2d66a92 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -89,9 +89,11 @@ async function syncClient( // If an existing client is found, update it if (token && !isRetry) { Log.debug(pkg.metadata, `Found existing token for ${clientReq.clientId}`); + handleClientGroups(clientReq); client = await apiCall(clientReq, "PUT", token); } else { Log.debug(pkg.metadata, `Creating new client for ${clientReq.clientId}`); + handleClientGroups(clientReq); client = await apiCall(clientReq); } @@ -142,6 +144,18 @@ async function syncClient( } } +/** + * Handles the client groups by converting the groups to attributes. + * @param clientReq - The client request object. + */ +function handleClientGroups(clientReq: Sso) { + if (clientReq.groups) { + clientReq.attributes = clientReq.attributes || {}; + clientReq.attributes["uds.core.groups"] = JSON.stringify(clientReq.groups); + delete clientReq.groups; + } +} + async function apiCall(sso: Partial, method = "POST", authToken = "") { // Handle single test mode if (UDSConfig.isSingleTest) { diff --git a/src/pepr/operator/crd/generated/package-v1alpha1.ts b/src/pepr/operator/crd/generated/package-v1alpha1.ts index 6c00da529..bb6431f7b 100644 --- a/src/pepr/operator/crd/generated/package-v1alpha1.ts +++ b/src/pepr/operator/crd/generated/package-v1alpha1.ts @@ -478,6 +478,10 @@ export interface Sso { * Whether the SSO client is enabled */ enabled?: boolean; + /** + * The client sso group type + */ + groups?: Groups; /** * If true, the client will generate a new Auth Service client as well */ @@ -526,6 +530,16 @@ export enum ClientAuthenticatorType { ClientSecret = "client-secret", } +/** + * The client sso group type + */ +export interface Groups { + /** + * List of group allowed to access to client + */ + anyOf?: string[]; +} + /** * Specifies the protocol of the client, either 'openid-connect' or 'saml' */ diff --git a/src/pepr/operator/crd/sources/package/v1alpha1.ts b/src/pepr/operator/crd/sources/package/v1alpha1.ts index 93dfe87cd..1ea855413 100644 --- a/src/pepr/operator/crd/sources/package/v1alpha1.ts +++ b/src/pepr/operator/crd/sources/package/v1alpha1.ts @@ -303,6 +303,19 @@ const sso = { type: "string", }, }, + groups: { + description: "The client sso group type", + type: "object", + properties: { + anyOf: { + description: "List of group allowed to access to client", + type: "array", + items: { + type: "string", + }, + }, + }, + }, }, } as V1JSONSchemaProps, } as V1JSONSchemaProps; From 1a009113b1dc6db4723ed34eef8b2c4d1af32939 Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Tue, 25 Jun 2024 12:52:49 -0600 Subject: [PATCH 2/8] fix: address PR comment --- src/pepr/operator/controllers/keycloak/client-sync.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index ca2d66a92..4601a02f5 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -86,14 +86,14 @@ async function syncClient( let client: Client; + handleClientGroups(clientReq); + // If an existing client is found, update it if (token && !isRetry) { Log.debug(pkg.metadata, `Found existing token for ${clientReq.clientId}`); - handleClientGroups(clientReq); client = await apiCall(clientReq, "PUT", token); } else { Log.debug(pkg.metadata, `Creating new client for ${clientReq.clientId}`); - handleClientGroups(clientReq); client = await apiCall(clientReq); } From 577fa5d3d436abd74acfadc6345d7c3dbf5fdd81 Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Wed, 26 Jun 2024 15:17:17 -0600 Subject: [PATCH 3/8] fix: removing client groups attribute --- .../controllers/keycloak/client-sync.spec.ts | 46 ++++++++++++++++++- .../controllers/keycloak/client-sync.ts | 5 +- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts index 07614e7fd..d57cf11f0 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "@jest/globals"; -import { extractSamlCertificateFromXML, generateSecretData } from "./client-sync"; +import { Sso } from "../../crd"; +import { extractSamlCertificateFromXML, generateSecretData, handleClientGroups } from "./client-sync"; import { Client } from "./types"; const mockClient: Client = { @@ -132,3 +133,46 @@ describe("Test Secret & Template Data Generation", () => { }); }); }); + +describe('handleClientGroups function', () => { + it('should correctly transform groups into attributes["uds.core.groups"]', () => { + // Arrange + const ssoWithGroups: Sso = { + clientId: 'test-client', + name: 'Test Client', + redirectUris: ['https://example.com/callback'], + groups: { + anyOf: ['group1', 'group2'] + } + }; + + // Act + handleClientGroups(ssoWithGroups); + + // Assert + expect(ssoWithGroups.attributes).toBeDefined(); // Ensure attributes is defined + expect(typeof ssoWithGroups.attributes).toBe('object'); // Ensure attributes is an object + expect(ssoWithGroups.attributes!['uds.core.groups']).toEqual(JSON.stringify({ + anyOf: ['group1', 'group2'] + })); + expect(ssoWithGroups.groups).toBeUndefined(); // Ensure groups property is removed + }); + + it('should set attributes["uds.core.groups"] to an empty object if groups are not provided', () => { + // Arrange + const ssoWithoutGroups: Sso = { + clientId: 'test-client', + name: 'Test Client', + redirectUris: ['https://example.com/callback'] + }; + + // Act + handleClientGroups(ssoWithoutGroups); + + // Assert + expect(ssoWithoutGroups.attributes).toBeDefined(); // Ensure attributes is defined + expect(typeof ssoWithoutGroups.attributes).toBe('object'); // Ensure attributes is an object + expect(ssoWithoutGroups.attributes!['uds.core.groups']).toEqual(""); // Empty object as string + expect(ssoWithoutGroups.groups).toBeUndefined(); // Ensure groups property is removed + }); +}); \ No newline at end of file diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index 4601a02f5..a61d115c2 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -148,11 +148,14 @@ async function syncClient( * Handles the client groups by converting the groups to attributes. * @param clientReq - The client request object. */ -function handleClientGroups(clientReq: Sso) { +export function handleClientGroups(clientReq: Sso) { if (clientReq.groups) { clientReq.attributes = clientReq.attributes || {}; clientReq.attributes["uds.core.groups"] = JSON.stringify(clientReq.groups); delete clientReq.groups; + } else { + clientReq.attributes = clientReq.attributes || {}; + clientReq.attributes["uds.core.groups"] = ""; // Remove groups attribute from client } } From 4544ea70cbd37e6739442836644f513f4104697f Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Wed, 26 Jun 2024 15:20:13 -0600 Subject: [PATCH 4/8] fix: lint --- .../controllers/keycloak/client-sync.spec.ts | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts index d57cf11f0..872b6c886 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "@jest/globals"; import { Sso } from "../../crd"; -import { extractSamlCertificateFromXML, generateSecretData, handleClientGroups } from "./client-sync"; +import { + extractSamlCertificateFromXML, + generateSecretData, + handleClientGroups, +} from "./client-sync"; import { Client } from "./types"; const mockClient: Client = { @@ -134,36 +138,38 @@ describe("Test Secret & Template Data Generation", () => { }); }); -describe('handleClientGroups function', () => { +describe("handleClientGroups function", () => { it('should correctly transform groups into attributes["uds.core.groups"]', () => { // Arrange const ssoWithGroups: Sso = { - clientId: 'test-client', - name: 'Test Client', - redirectUris: ['https://example.com/callback'], + clientId: "test-client", + name: "Test Client", + redirectUris: ["https://example.com/callback"], groups: { - anyOf: ['group1', 'group2'] - } + anyOf: ["group1", "group2"], + }, }; - + // Act handleClientGroups(ssoWithGroups); - + // Assert expect(ssoWithGroups.attributes).toBeDefined(); // Ensure attributes is defined - expect(typeof ssoWithGroups.attributes).toBe('object'); // Ensure attributes is an object - expect(ssoWithGroups.attributes!['uds.core.groups']).toEqual(JSON.stringify({ - anyOf: ['group1', 'group2'] - })); + expect(typeof ssoWithGroups.attributes).toBe("object"); // Ensure attributes is an object + expect(ssoWithGroups.attributes!["uds.core.groups"]).toEqual( + JSON.stringify({ + anyOf: ["group1", "group2"], + }), + ); expect(ssoWithGroups.groups).toBeUndefined(); // Ensure groups property is removed }); it('should set attributes["uds.core.groups"] to an empty object if groups are not provided', () => { // Arrange const ssoWithoutGroups: Sso = { - clientId: 'test-client', - name: 'Test Client', - redirectUris: ['https://example.com/callback'] + clientId: "test-client", + name: "Test Client", + redirectUris: ["https://example.com/callback"], }; // Act @@ -171,8 +177,8 @@ describe('handleClientGroups function', () => { // Assert expect(ssoWithoutGroups.attributes).toBeDefined(); // Ensure attributes is defined - expect(typeof ssoWithoutGroups.attributes).toBe('object'); // Ensure attributes is an object - expect(ssoWithoutGroups.attributes!['uds.core.groups']).toEqual(""); // Empty object as string + expect(typeof ssoWithoutGroups.attributes).toBe("object"); // Ensure attributes is an object + expect(ssoWithoutGroups.attributes!["uds.core.groups"]).toEqual(""); // Empty object as string expect(ssoWithoutGroups.groups).toBeUndefined(); // Ensure groups property is removed }); -}); \ No newline at end of file +}); From 6a90823d174e49008f73a77c5c8e363a58481554 Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Thu, 27 Jun 2024 15:41:06 -0600 Subject: [PATCH 5/8] fix: typo and gen crds task fix --- src/pepr/operator/crd/sources/package/v1alpha1.ts | 2 +- src/pepr/tasks.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pepr/operator/crd/sources/package/v1alpha1.ts b/src/pepr/operator/crd/sources/package/v1alpha1.ts index 1ea855413..c5bba7092 100644 --- a/src/pepr/operator/crd/sources/package/v1alpha1.ts +++ b/src/pepr/operator/crd/sources/package/v1alpha1.ts @@ -308,7 +308,7 @@ const sso = { type: "object", properties: { anyOf: { - description: "List of group allowed to access to client", + description: "List of groups allowed to access to client", type: "array", items: { type: "string", diff --git a/src/pepr/tasks.yaml b/src/pepr/tasks.yaml index 08b657648..4033b25f9 100644 --- a/src/pepr/tasks.yaml +++ b/src/pepr/tasks.yaml @@ -6,9 +6,9 @@ tasks: - name: gen-crds description: "Generate CRDS, requires a running kubernetes cluster" actions: - - cmd: "npx ts-node src/pepr/operator/crd/register.ts" + - cmd: npx ts-node -e "import { registerCRDs } from './src/pepr/operator/crd/register'; registerCRDs()" env: - - "PEPR_WATCH_MODE=true" + - "PEPR_MODE=dev" - cmd: "npx kubernetes-fluent-client crd packages.uds.dev src/pepr/operator/crd/generated" From d1af5f812eb5b4de02d698eb7a15714a4837d038 Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Fri, 28 Jun 2024 07:21:15 -0600 Subject: [PATCH 6/8] fix: groups empty object add tests --- .../controllers/keycloak/client-sync.spec.ts | 58 ++++++++++++++++--- .../controllers/keycloak/client-sync.ts | 8 +-- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts index 872b6c886..3acd14ecd 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -154,14 +154,14 @@ describe("handleClientGroups function", () => { handleClientGroups(ssoWithGroups); // Assert - expect(ssoWithGroups.attributes).toBeDefined(); // Ensure attributes is defined - expect(typeof ssoWithGroups.attributes).toBe("object"); // Ensure attributes is an object + expect(ssoWithGroups.attributes).toBeDefined(); + expect(typeof ssoWithGroups.attributes).toBe("object"); expect(ssoWithGroups.attributes!["uds.core.groups"]).toEqual( JSON.stringify({ anyOf: ["group1", "group2"], }), ); - expect(ssoWithGroups.groups).toBeUndefined(); // Ensure groups property is removed + expect(ssoWithGroups.groups).toBeUndefined(); }); it('should set attributes["uds.core.groups"] to an empty object if groups are not provided', () => { @@ -176,9 +176,53 @@ describe("handleClientGroups function", () => { handleClientGroups(ssoWithoutGroups); // Assert - expect(ssoWithoutGroups.attributes).toBeDefined(); // Ensure attributes is defined - expect(typeof ssoWithoutGroups.attributes).toBe("object"); // Ensure attributes is an object - expect(ssoWithoutGroups.attributes!["uds.core.groups"]).toEqual(""); // Empty object as string - expect(ssoWithoutGroups.groups).toBeUndefined(); // Ensure groups property is removed + expect(ssoWithoutGroups.attributes).toBeDefined(); + expect(typeof ssoWithoutGroups.attributes).toBe("object"); + expect(ssoWithoutGroups.attributes!["uds.core.groups"]).toEqual(""); + expect(ssoWithoutGroups.groups).toBeUndefined(); + }); + + it('should set attributes["uds.core.groups"] to an empty object if empty groups object is provided', () => { + // Arrange + const ssoWithoutGroups: Sso = { + clientId: "test-client", + name: "Test Client", + redirectUris: ["https://example.com/callback"], + groups: {} + }; + + // Act + handleClientGroups(ssoWithoutGroups); + + // Assert + expect(ssoWithoutGroups.attributes).toBeDefined(); + expect(typeof ssoWithoutGroups.attributes).toBe("object"); + expect(ssoWithoutGroups.attributes!["uds.core.groups"]).toEqual(""); + expect(ssoWithoutGroups.groups).toBeUndefined(); + }); + + it('should set attributes["uds.core.groups"] to an empty array of groups if groups.anyOf is empty array', () => { + // Arrange + const ssoWithGroups: Sso = { + clientId: "test-client", + name: "Test Client", + redirectUris: ["https://example.com/callback"], + groups: { + anyOf: [], + }, + }; + + // Act + handleClientGroups(ssoWithGroups); + + // Assert + expect(ssoWithGroups.attributes).toBeDefined(); + expect(typeof ssoWithGroups.attributes).toBe("object"); + expect(ssoWithGroups.attributes!["uds.core.groups"]).toEqual( + JSON.stringify({ + anyOf: [], + }), + ); + expect(ssoWithGroups.groups).toBeUndefined(); }); }); diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index a61d115c2..7d277fe8c 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -148,16 +148,16 @@ async function syncClient( * Handles the client groups by converting the groups to attributes. * @param clientReq - The client request object. */ -export function handleClientGroups(clientReq: Sso) { - if (clientReq.groups) { +export function handleClientGroups(clientReq: Sso) { + if (clientReq.groups?.anyOf) { clientReq.attributes = clientReq.attributes || {}; clientReq.attributes["uds.core.groups"] = JSON.stringify(clientReq.groups); - delete clientReq.groups; } else { clientReq.attributes = clientReq.attributes || {}; clientReq.attributes["uds.core.groups"] = ""; // Remove groups attribute from client } -} + delete clientReq.groups; +} async function apiCall(sso: Partial, method = "POST", authToken = "") { // Handle single test mode From ad94817e06743aa67c5d3c3c8b692f49850b61db Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Fri, 28 Jun 2024 08:22:32 -0600 Subject: [PATCH 7/8] fix: lint --- src/pepr/operator/controllers/keycloak/client-sync.spec.ts | 4 ++-- src/pepr/operator/controllers/keycloak/client-sync.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts index 3acd14ecd..1fcfab6a8 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.spec.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.spec.ts @@ -181,14 +181,14 @@ describe("handleClientGroups function", () => { expect(ssoWithoutGroups.attributes!["uds.core.groups"]).toEqual(""); expect(ssoWithoutGroups.groups).toBeUndefined(); }); - + it('should set attributes["uds.core.groups"] to an empty object if empty groups object is provided', () => { // Arrange const ssoWithoutGroups: Sso = { clientId: "test-client", name: "Test Client", redirectUris: ["https://example.com/callback"], - groups: {} + groups: {}, }; // Act diff --git a/src/pepr/operator/controllers/keycloak/client-sync.ts b/src/pepr/operator/controllers/keycloak/client-sync.ts index 7d277fe8c..63430fbd4 100644 --- a/src/pepr/operator/controllers/keycloak/client-sync.ts +++ b/src/pepr/operator/controllers/keycloak/client-sync.ts @@ -148,7 +148,7 @@ async function syncClient( * Handles the client groups by converting the groups to attributes. * @param clientReq - The client request object. */ -export function handleClientGroups(clientReq: Sso) { +export function handleClientGroups(clientReq: Sso) { if (clientReq.groups?.anyOf) { clientReq.attributes = clientReq.attributes || {}; clientReq.attributes["uds.core.groups"] = JSON.stringify(clientReq.groups); @@ -157,7 +157,7 @@ export function handleClientGroups(clientReq: Sso) { clientReq.attributes["uds.core.groups"] = ""; // Remove groups attribute from client } delete clientReq.groups; -} +} async function apiCall(sso: Partial, method = "POST", authToken = "") { // Handle single test mode From 4318cf2a3305a8220fb5aad3024292565069d5b5 Mon Sep 17 00:00:00 2001 From: UnicornChance Date: Fri, 28 Jun 2024 08:45:33 -0600 Subject: [PATCH 8/8] chore: update identity-config image version --- src/keycloak/chart/values.yaml | 2 +- src/keycloak/zarf.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keycloak/chart/values.yaml b/src/keycloak/chart/values.yaml index ff0a59b2e..683128b50 100644 --- a/src/keycloak/chart/values.yaml +++ b/src/keycloak/chart/values.yaml @@ -7,7 +7,7 @@ image: pullPolicy: IfNotPresent # renovate: datasource=github-tags depName=defenseunicorns/uds-identity-config versioning=semver -configImage: ghcr.io/defenseunicorns/uds/identity-config:0.4.5 +configImage: ghcr.io/defenseunicorns/uds/identity-config:0.5.0 # The public domain name of the Keycloak server domain: "###ZARF_VAR_DOMAIN###" diff --git a/src/keycloak/zarf.yaml b/src/keycloak/zarf.yaml index 7b97cdf0c..67fcf997d 100644 --- a/src/keycloak/zarf.yaml +++ b/src/keycloak/zarf.yaml @@ -21,7 +21,7 @@ components: - "values/upstream-values.yaml" images: - quay.io/keycloak/keycloak:24.0.5 - - ghcr.io/defenseunicorns/uds/identity-config:0.4.5 + - ghcr.io/defenseunicorns/uds/identity-config:0.5.0 - name: keycloak required: true @@ -37,4 +37,4 @@ components: - "values/registry1-values.yaml" images: - registry1.dso.mil/ironbank/opensource/keycloak/keycloak:24.0.5 - - ghcr.io/defenseunicorns/uds/identity-config:0.4.5 + - ghcr.io/defenseunicorns/uds/identity-config:0.5.0