Skip to content

Commit cae6847

Browse files
ericwylesrjferguson21chance-colemanmjnagel
authored
feat: add saml and attribute/mapper support for keycloak in uds pepr operator (#328)
## Description Add support for saml protocol and attributes and protocolMappers support for keycloak clients. ## Related Issue Relates to #326 Relates to #305 ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Other (security config, docs update, etc) ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/uds-template-capability/blob/main/CONTRIBUTING.md)(https://github.com/defenseunicorns/uds-template-capability/blob/main/CONTRIBUTING.md#submitting-a-pull-request) followed --------- Co-authored-by: Rob Ferguson <[email protected]> Co-authored-by: Chance <[email protected]> Co-authored-by: Micah Nagel <[email protected]>
1 parent 8c089c4 commit cae6847

File tree

6 files changed

+107
-1
lines changed

6 files changed

+107
-1
lines changed

src/pepr/operator/controllers/keycloak/client-sync.spec.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from "@jest/globals";
2-
import { generateSecretData } from "./client-sync";
2+
import { extractSamlCertificateFromXML, generateSecretData } from "./client-sync";
33
import { Client } from "./types";
44

55
const mockClient: Client = {
@@ -60,6 +60,41 @@ const mockClientStringified: Record<string, string> = {
6060
standardFlowEnabled: "true",
6161
};
6262

63+
describe("Test XML Extraction Using Regex", () => {
64+
it("extract xml", async () => {
65+
// Sample XML string with namespace prefixes
66+
const xmlString = `
67+
<md:EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://keycloak.admin.uds.dev/realms/uds">
68+
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
69+
<md:KeyDescriptor use="signing">
70+
<ds:KeyInfo>
71+
<ds:KeyName>SO1zm7gOpX2xlm16-pZ08zOJui0i7PwEHIqM6h4d9Sw</ds:KeyName>
72+
<ds:X509Data>
73+
<ds:X509Certificate>FOUND THE CERT</ds:X509Certificate>
74+
</ds:X509Data>
75+
</ds:KeyInfo>
76+
</md:KeyDescriptor>
77+
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml/resolve" index="0"></md:ArtifactResolutionService>
78+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleLogoutService>
79+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleLogoutService>
80+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleLogoutService>
81+
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleLogoutService>
82+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
83+
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
84+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
85+
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
86+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleSignOnService>
87+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleSignOnService>
88+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleSignOnService>
89+
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://keycloak.admin.uds.dev/realms/uds/protocol/saml"></md:SingleSignOnService>
90+
</md:IDPSSODescriptor>
91+
</md:EntityDescriptor>
92+
`;
93+
94+
expect(extractSamlCertificateFromXML(xmlString)).toEqual("FOUND THE CERT");
95+
});
96+
});
97+
6398
describe("Test Secret & Template Data Generation", () => {
6499
it("generates data without template", async () => {
65100
const expected: Record<string, string> = {};

src/pepr/operator/controllers/keycloak/client-sync.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,25 @@ import { Client } from "./types";
88

99
const apiURL =
1010
"http://keycloak-http.keycloak.svc.cluster.local:8080/realms/uds/clients-registrations/default";
11+
const samlDescriptorUrl =
12+
"http://keycloak-http.keycloak.svc.cluster.local:8080/realms/uds/protocol/saml/descriptor";
1113

1214
// Template regex to match clientField() references, see https://regex101.com/r/e41Dsk/3 for details
1315
const secretTemplateRegex = new RegExp(
1416
'clientField\\(([a-zA-Z]+)\\)(?:\\["?([\\w]+)"?\\]|(\\.json\\(\\)))?',
1517
"gm",
1618
);
1719

20+
// Template regex to match IDPSSODescriptor in the SAML IDP Descriptor XML, see https://regex101.com/r/DGvzjd/1
21+
const idpSSODescriptorRegex = new RegExp(
22+
/<[^>]*:IDPSSODescriptor[^>]*>((.|[\n\r])*)<\/[^>]*:IDPSSODescriptor>/,
23+
);
24+
25+
// Template regex to match the X509Certificate within the IDPSSODescriptor XML, see https://regex101.com/r/NjGZF5/1
26+
const x509CertRegex = new RegExp(
27+
/<[^>]*:X509Certificate[^>]*>((.|[\n\r])*)<\/[^>]*:X509Certificate>/,
28+
);
29+
1830
/**
1931
* Create or update the Keycloak clients for the package
2032
*
@@ -89,6 +101,10 @@ async function syncClient(
89101
// Remove the registrationAccessToken from the client object to avoid problems (one-time use token)
90102
delete client.registrationAccessToken;
91103

104+
if (clientReq.protocol === "saml") {
105+
client.samlIdpCertificate = await getSamlCertificate();
106+
}
107+
92108
// Create or update the client secret
93109
await K8s(kind.Secret).Apply({
94110
metadata: {
@@ -191,6 +207,21 @@ export function generateSecretData(client: Client, secretTemplate?: { [key: stri
191207
return stringMap;
192208
}
193209

210+
export async function getSamlCertificate() {
211+
const resp = await fetch<string>(samlDescriptorUrl);
212+
213+
if (!resp.ok) {
214+
return undefined;
215+
}
216+
217+
return extractSamlCertificateFromXML(resp.data);
218+
}
219+
220+
export function extractSamlCertificateFromXML(xmlString: string) {
221+
const extractedIDPSSODescriptor = xmlString.match(idpSSODescriptorRegex)?.[1] || "";
222+
return extractedIDPSSODescriptor.match(x509CertRegex)?.[1] || "";
223+
}
224+
194225
/**
195226
* Process the secret template and convert the client data to base64 encoded strings for use in a secret
196227
*

src/pepr/operator/controllers/keycloak/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,5 @@ export interface Client {
2525
standardFlowEnabled: boolean;
2626
surrogateAuthRequired: boolean;
2727
webOrigins: string[];
28+
samlIdpCertificate?: string;
2829
}

src/pepr/operator/crd/generated/package-v1alpha1.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,10 @@ export interface Sso {
421421
* session.
422422
*/
423423
alwaysDisplayInConsole?: boolean;
424+
/**
425+
* Specifies attributes for the client.
426+
*/
427+
attributes?: { [key: string]: string };
424428
/**
425429
* The client authenticator type
426430
*/
@@ -449,6 +453,10 @@ export interface Sso {
449453
* Specifies display name of the client
450454
*/
451455
name: string;
456+
/**
457+
* Specifies the protocol of the client, either 'openid-connect' or 'saml'
458+
*/
459+
protocol?: Protocol;
452460
/**
453461
* Valid URI pattern a browser can redirect to after a successful login. Simple wildcards
454462
* are allowed such as 'https://unicorns.uds.dev/*'
@@ -485,6 +493,14 @@ export enum ClientAuthenticatorType {
485493
ClientSecret = "client-secret",
486494
}
487495

496+
/**
497+
* Specifies the protocol of the client, either 'openid-connect' or 'saml'
498+
*/
499+
export enum Protocol {
500+
OpenidConnect = "openid-connect",
501+
Saml = "saml",
502+
}
503+
488504
export interface Status {
489505
endpoints?: string[];
490506
networkPolicyCount?: number;

src/pepr/operator/crd/sources/package/v1alpha1.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,18 @@ const sso = {
200200
"A description for the client, can be a URL to an image to replace the login logo",
201201
type: "string",
202202
},
203+
protocol: {
204+
description: "Specifies the protocol of the client, either 'openid-connect' or 'saml'",
205+
type: "string",
206+
enum: ["openid-connect", "saml"],
207+
},
208+
attributes: {
209+
description: "Specifies attributes for the client.",
210+
type: "object",
211+
additionalProperties: {
212+
type: "string",
213+
},
214+
},
203215
rootUrl: {
204216
description: "Root URL appended to relative URLs",
205217
type: "string",

tasks.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ tasks:
3131
echo " - Otherwise run 'npx pepr deploy' to deploy the Pepr module to the cluster"
3232
echo " - Additional source packages can be deployed with 'zarf dev deploy src/<package>'"
3333
34+
- name: slim-dev
35+
actions:
36+
- description: "Create slim dev package"
37+
task: create:slim-dev-package
38+
39+
- description: "Build slim dev bundle"
40+
task: create:k3d-slim-dev-bundle
41+
42+
- description: "Deploy slim dev bundle"
43+
task: deploy:k3d-slim-dev-bundle
44+
3445
- name: dev-deploy
3546
actions:
3647
- description: "Deploy the given source package with Zarf Dev"

0 commit comments

Comments
 (0)