Launch the openldap and dex in kubernetes
Generate the PKI for openldap
- Caution: the certificate’s
subject's domain component (DC)
can with openldap DN same to name
1# install the certtool
2apt install gnutls-bin
3# generate the ca.key
4certtool --generate-privkey --outfile ca.key
5# generate the ca certificate
6certtool --generate-self-signed --load-privkey ca.key --outfile ca.crt
7# generate the server endpoint private key
8certtool --generate-privkey --outfile tls.key --rsa
9# server endpoint certificate request
10certtool --generate-request --load-privkey tls.key --outfile request.crt
11# use ca certificate to sign the server endpoint certificate
12certtool --generate-certificate --load-request request.crt --outfile tls.crt --load-ca-certificate ca.crt --load-ca-privkey ca.key
- Create the kubernetes secrets to serve the above certificates the data key is
tls.crt
,tls.key
,ca.crt
Launch the Openldap
helm chart
the image use only: jpgouin/openldap:2.6.7-fix
about this issue: ldap_start_tls failed
1 global:
2 ldapDomain: "dc=mycomp,dc=test"
3 image:
4 # repository: bitnami/openldap
5 # tag: 2.6.8
6 pullPolicy: IfNotPresent
7 replicaCount: 2
8 users: devloper,devops
9 userPasswords: test,test
10 group: dev-team
11 initSchema:
12 image:
13 pullPolicy: IfNotPresent
14 initTLSSecret:
15 tls_enabled: true
16 secret: "ldap-tls-secret"
17 logLevel: info
18 env:
19 BITNAMI_DEBUG: "true"
20 LDAP_LOGLEVEL: "256"
21 LDAP_TLS_ENFORCE: "false"
22 LDAPTLS_REQCERT: "allow"
23 LDAP_ENABLE_TLS: "yes"
24 LDAP_REQUIRE_TLS: "no"
25 LDAP_SKIP_DEFAULT_TREE: "no"
26 ltb-passwd:
27 image:
28 tag: 5.3.3
29 pullPolicy: IfNotPresent
30 ingress:
31 enabled: true
32 annotations:
33 kubernetes.io/ingress.class: nginx
34 hosts:
35 - "self-service.local.dev"
36 phpldapadmin:
37 image:
38 tag: 0.9.0
39 pullPolicy: IfNotPresent
40 ingress:
41 enabled: true
42 annotations:
43 kubernetes.io/ingress.class: nginx
44 hosts:
45 - "phpldap.local.dev"
Deployment the dex
use the official charts: Dex chart this the helm chart example values
1 config:
2 issuer: https://dexidp.local.dev
3 storage:
4 type: kubernetes
5 config:
6 inCluster: true
7 connectors:
8 - type: ldap
9 name: OpenLDAP
10 id: ldap
11 config:
12 host: openldap:389
13 insecureNoSSL: true
14 bindDN: cn=admin,dc=talos,dc=test
15 bindPW: "{{.Env.LDAP_ADMIN_PASSWORD}}"
16 usernamePrompt: Email Address
17 userSearch:
18 baseDN: ou=users,dc=talos,dc=test
19 filter: "(objectClass=inetOrgPerson)"
20 username: mail
21 idAttr: DN
22 emailAttr: mail
23 nameAttr: cn
24 groupSearch:
25 baseDN: ou=users,dc=talos,dc=test
26 filter: "(objectClass=groupOfNames)"
27 userMatchers:
28 - userAttr: DN
29 groupAttr: member
30 nameAttr: cn
31 staticClients:
32 - id: headlamp
33 redirectURIs:
34 - 'https://k8s.local.dev/oidc-callback'
35 name: 'headlamp'
36 secret: 'xxxxxxxxxxx'
37 envVars:
38 - name: LDAP_ADMIN_PASSWORD
39 valueFrom:
40 secretKeyRef:
41 name: "openldap"
42 key: "LDAP_ADMIN_PASSWORD"
43 ingress:
44 enabled: true
45 className: "nginx"
46 annotations:
47 kubernetes.io/ingress.class: nginx
48 hosts:
49 - host: dexidp.local.dev
50 paths:
51 - path: /
52 pathType: ImplementationSpecific
Setup the kubernetes to use oidc
- create the user rolebinding
1kubectl create clusterrolebinding oidc-cluster-admin \
2 --clusterrole=cluster-admin \
3 --user="[email protected]"
- enable the api server oidc feature
1 - '--oidc-client-id=headlamp'
2 - '--oidc-issuer-url=https://dexidp.local.dev'
3 - '--oidc-username-claim=email'
- user kubectl krew plugin to testing install the kubectl plugin management tool: krew
1# install the oidc-login
2kubectl krew install oidc-login
3# set up the oidc-login
4kubectl oidc-login setup --oidc-issuer-url=https://<YOUR-DEX-URL> \ --oidc-client-id=<CLIENT-ID> \
5--oidc-client-secret=<CLIENT-SECRET>
6# set the credentials
7kubectl config set-credentials oidc-user \
8 --exec-api-version=client.authentication.k8s.io/v1beta1 \
9 --exec-command=kubectl \
10 --exec-arg=oidc-login \
11 --exec-arg=get-token \
12 --exec-arg=--oidc-issuer-url=<YOUR-DEX-URL> \
13 --exec-arg=--oidc-client-id=<CLIENT-ID> \
14 --exec-arg=--oidc-client-secret=<CLIENT-SECRET> \
15 --exec-arg=--oidc-extra-scope=email
To debug and testing the openldap
1# test the tls connect to each other
2ldapsearch -x -H ldaps://openldap-1.openldap-headless.tools.svc.cluster.local:1636 -D "cn=admin,dc=mycomp,dc=test" -w "xxxxx" -b "cn=test,ou=users,dc=mycomp,dc=test"
3#
Application integration the
- View detail oauth2.0 in dex configuration:
https://dexidp.local.dev/.well-known/openid-configuration
- Add the
staticClient
to dex config
1staticClients:
2- id: local-app
3 name: local-app
4 redirectURIs:
5 - http://localhost:3000/oauth/callback
6 secret: xxxx
- Write client app to testing Oauth2
1<html>
2 <head>
3 <title>oauth walkthrough</title>
4 </head>
5 <body>
6 <a href="/oauth/dex">login with dex</a>
7 </body>
8</html>
- The
TypeScript
code to implementation logic
1/*
2The workflow with dex use oauth2.0
31. When user click the index.html button: login with dex
42. Your website asks dex for permission
5
6*/
7
8import express from "express";
9import crypto from "crypto";
10
11const PORT = 3000;
12const CLIENT_ID = process.env.CLIENT_ID as string;
13const CLIENT_SECRET = process.env.CLIENT_SECRET as string;
14
15const app = express();
16
17app.use(express.static("static"));
18
19app.get("/oauth/dex", (req, res) => {
20 const params = new URLSearchParams();
21
22 params.set("response_type", "code");
23 params.set("client_id", CLIENT_ID);
24 params.set("redirect_uri", "http://localhost:3000/oauth/callback");
25 params.set("scope", "openid email");
26
27 const state = crypto.randomBytes(16).toString("hex");
28 params.set("state", state);
29
30 const url = `https://dexidp.local.dev/auth?${params.toString()}`;
31
32 res.set("Set-Cookie", `oauth_state=${state}; HttpOnly; Secure; SameSite=Lax`);
33 res.redirect(url);
34});
35
36app.get("/oauth/callback", async (req, res) => {
37 const { code, state } = req.query;
38 const oauthState = getCookie("oauth_state", req.headers.cookie as string);
39
40 if (state !== oauthState) {
41 return res.status(400).send("Invalid state");
42 }
43
44 const jsonToken = await exchangeCodeForToken(code as string);
45 const userInfo = await getUserInfo(jsonToken);
46
47 res.header("Content-Type", "application/json").send(JSON.stringify(userInfo));
48 });
49
50 app.listen(PORT, () => {
51 console.log(`Example app listening at http://localhost:${PORT}`);
52 });
53
54 function getCookie(name: string, cookies: string) {
55 const cookie = cookies.split(";").find((cookie) => cookie.trim().startsWith(`${name}=`));
56 if (!cookie) {
57 return null;
58 }
59 return cookie.split("=")[1];
60 }
61
62 async function exchangeCodeForToken(code: string) {
63 const resp = await fetch(`https://dexidp.local.dev/token`, {
64 method: "POST",
65 headers: {
66 "Content-Type": "application/x-www-form-urlencoded",
67 },
68 body: new URLSearchParams({
69 grant_type: "authorization_code",
70 code: code,
71 redirect_uri: "http://localhost:3000/oauth/callback",
72 client_id: CLIENT_ID,
73 client_secret: CLIENT_SECRET,
74 }).toString(),
75 });
76
77 if (!resp.ok) {
78 throw new Error("Something went wrong");
79 }
80
81 return resp.json() as Promise<{ access_token: string }>;
82 }
83
84 async function getUserInfo(jsonToken: string) {
85 console.log(jsonToken)
86 const resp = await fetch(`https://dexidp.local.dev/userinfo`, {
87 headers: {
88 Authorization: `Bearer ${jsonToken['access_token']}`,
89 },
90 });
91
92 return resp.json() as Promise<{ email: string }>;
93 }
- The
package.json
1{
2 "name": "dex-test",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1",
8 "start": "tsx index.ts"
9 },
10 "dependencies": {
11 "express": "^4.18.2"
12 },
13 "devDependencies": {
14 "@types/express": "^4.17.21",
15 "@types/node": "^20.11.19",
16 "tsx": "^4.7.1",
17 "typescript": "^5.3.3"
18 },
19 "keywords": [],
20 "author": "",
21 "license": "ISC"
22}
- to run
CLIENT_SECRET=xxxxx CLIENT_ID=local-app pnpm run start