@@ -13,7 +13,7 @@ This Docker Compose demo demonstrates SSH certificate authentication with LemonL
1313
1414``` mermaid
1515flowchart LR
16- subgraph Docker["Docker Network (llng -net)"]
16+ subgraph Docker["Docker Network (ob-cert -net)"]
1717 SSO["SSO<br/>(LLNG)<br/>:80"]
1818 Bastion["Bastion<br/>(SSH)<br/>:2222"]
1919 Backend["Backend<br/>(SSH)<br/>:22"]
@@ -99,14 +99,14 @@ ssh -p 2222 dwho@localhost
9999### 5. From bastion, connect to backend
100100
101101The backend server requires a signed JWT from the bastion to prove the connection
102- comes from an authorized bastion server. Use the ` llng -ssh-proxy` command:
102+ comes from an authorized bastion server. Use the ` ob -ssh-proxy` command:
103103
104104``` bash
105105# On bastion - the proxy automatically gets a JWT and forwards it
106- llng -ssh-proxy backend
106+ ob -ssh-proxy backend
107107
108108# Or using SSH with ProxyCommand
109- ssh -o ProxyCommand=' llng -ssh-proxy %h %p' dwho@backend
109+ ssh -o ProxyCommand=' ob -ssh-proxy %h %p' dwho@backend
110110```
111111
112112** Note** : Direct SSH connections to the backend (without the bastion JWT) will be rejected,
@@ -153,7 +153,7 @@ sequenceDiagram
1531533 . ** Server Trust Configuration** : SSH servers are configured to trust this CA:
154154
155155 ```
156- # In /etc/ssh/sshd_config.d/llng -*.conf
156+ # In /etc/ssh/sshd_config.d/open-bastion -*.conf
157157 TrustedUserCAKeys /etc/ssh/llng_ca.pub
158158 ```
159159
@@ -211,15 +211,15 @@ docker compose up -d backend-new
211211
212212``` bash
213213# Download CA key from the SSO portal
214- docker exec llng- backend-new curl -sf http://sso/ssh/ca -o /etc/ssh/llng_ca.pub
215- docker exec llng -backend-new cat /etc/ssh/llng_ca.pub
214+ docker exec ob-cert- backend-new curl -sf http://sso:8080 /ssh/ca -o /etc/ssh/llng_ca.pub
215+ docker exec ob-cert -backend-new cat /etc/ssh/llng_ca.pub
216216```
217217
218218#### Step 3: Configure sshd for certificate authentication
219219
220220``` bash
221- docker exec llng- backend-new tee /etc/ssh/sshd_config.d/llng .conf << 'EOF '
222- # LemonLDAP::NG SSH Configuration
221+ docker exec ob-cert- backend-new tee /etc/ssh/sshd_config.d/open-bastion .conf << 'EOF '
222+ # Open Bastion SSH Configuration
223223TrustedUserCAKeys /etc/ssh/llng_ca.pub
224224PubkeyAuthentication yes
225225PasswordAuthentication no
@@ -230,33 +230,33 @@ PermitRootLogin no
230230EOF
231231```
232232
233- #### Step 4: Configure PAM for LLNG
233+ #### Step 4: Configure Open Bastion PAM module
234234
235235``` bash
236- docker exec llng- backend-new tee /etc/security/pam_llng .conf << 'EOF '
237- # LemonLDAP::NG PAM configuration
238- portal_url = http://sso
236+ docker exec ob-cert- backend-new tee /etc/open-bastion/openbastion .conf << 'EOF '
237+ # Open Bastion PAM configuration
238+ portal_url = http://sso:8080
239239server_group = backend-new
240240client_id = pam-access
241241client_secret = pamsecret
242242timeout = 10
243243verify_ssl = false
244244cache_enabled = true
245- cache_dir = /var/cache/pam_llng
245+ cache_dir = /var/cache/open-bastion
246246cache_ttl = 300
247247log_level = info
248248EOF
249249
250- docker exec llng- backend-new chmod 600 /etc/security/pam_llng .conf
250+ docker exec ob-cert- backend-new chmod 600 /etc/open-bastion/openbastion .conf
251251```
252252
253253#### Step 5: Configure NSS for dynamic user resolution
254254
255255``` bash
256- docker exec llng -backend-new tee /etc/open-bastion/nss_openbastion.conf << 'EOF '
256+ docker exec ob-cert -backend-new tee /etc/open-bastion/nss_openbastion.conf << 'EOF '
257257# Open Bastion NSS configuration
258- portal_url = http://sso
259- server_token_file = /etc/security/pam_llng. token
258+ portal_url = http://sso:8080
259+ server_token_file = /etc/open-bastion/ token
260260cache_ttl = 300
261261min_uid = 10000
262262max_uid = 60000
@@ -265,32 +265,33 @@ default_shell = /bin/bash
265265default_home_base = /home
266266EOF
267267
268- # Configure nsswitch.conf to use LLNG
269- docker exec llng- backend-new sed -i ' s/^passwd:.*/passwd: files llng /' /etc/nsswitch.conf
270- docker exec llng- backend-new sed -i ' s/^group:.*/group: files llng /' /etc/nsswitch.conf
268+ # Configure nsswitch.conf to use Open Bastion
269+ docker exec ob-cert- backend-new sed -i ' s/^passwd:.*/passwd: files openbastion /' /etc/nsswitch.conf
270+ docker exec ob-cert- backend-new sed -i ' s/^group:.*/group: files openbastion /' /etc/nsswitch.conf
271271```
272272
273273#### Step 6: Configure PAM stack with home directory creation
274274
275275``` bash
276- docker exec llng -backend-new tee /etc/pam.d/sshd << 'EOF '
277- # PAM configuration for SSH with LemonLDAP::NG
276+ docker exec ob-cert -backend-new tee /etc/pam.d/sshd << 'EOF '
277+ # PAM configuration for SSH with Open Bastion
278278auth required pam_permit.so
279- account required pam_llng.so
279+ account required pam_openbastion.so
280+ session required pam_openbastion.so create_user=true
280281session required pam_unix.so
281282session optional pam_mkhomedir.so skel=/etc/skel umask=0022
282283EOF
283284```
284285
285- #### Step 7: Enroll the server with llng-pam -enroll
286+ #### Step 7: Enroll the server with ob -enroll
286287
287288``` bash
288- docker exec -it llng- backend-new llng-pam -enroll
289+ docker exec -it ob-cert- backend-new ob -enroll
289290```
290291
291292The script will:
292293
293- 1 . Read configuration from ` /etc/security/pam_llng .conf `
294+ 1 . Read configuration from ` /etc/open-bastion/openbastion .conf `
2942952 . Request a device authorization code from the portal
2952963 . Display a ** user code** (e.g., ` WXYZ-1234 ` )
2962974 . Wait for administrator approval
@@ -306,14 +307,13 @@ While the script is waiting, open a browser:
306307
307308After approval, the script will:
308309
309- - Save the token to ` /etc/security/pam_llng.token `
310- - Update the PAM configuration with ` server_token_file `
310+ - Save the token to ` /etc/open-bastion/token `
311311- Verify the enrollment
312312
313313#### Step 9: Restart sshd to apply configuration
314314
315315``` bash
316- docker exec llng -backend-new pkill -HUP sshd
316+ docker exec ob-cert -backend-new pkill -HUP sshd
317317```
318318
319319#### Step 10: Test the connection
@@ -331,61 +331,65 @@ curl -s -X POST http://localhost:80/ssh/sign \
331331 | jq -r ' .certificate' > /tmp/test_key-cert.pub
332332
333333# 3. Copy key and certificate to bastion
334- docker exec llng -bastion mkdir -p /home/dwho/.ssh
335- docker cp /tmp/test_key llng -bastion:/home/dwho/.ssh/id_ed25519
336- docker cp /tmp/test_key-cert.pub llng -bastion:/home/dwho/.ssh/id_ed25519-cert.pub
337- docker exec llng -bastion chown -R dwho:dwho /home/dwho/.ssh
338- docker exec llng -bastion chmod 700 /home/dwho/.ssh
339- docker exec llng -bastion chmod 600 /home/dwho/.ssh/id_ed25519
334+ docker exec ob-cert -bastion mkdir -p /home/dwho/.ssh
335+ docker cp /tmp/test_key ob-cert -bastion:/home/dwho/.ssh/id_ed25519
336+ docker cp /tmp/test_key-cert.pub ob-cert -bastion:/home/dwho/.ssh/id_ed25519-cert.pub
337+ docker exec ob-cert -bastion chown -R dwho:dwho /home/dwho/.ssh
338+ docker exec ob-cert -bastion chmod 700 /home/dwho/.ssh
339+ docker exec ob-cert -bastion chmod 600 /home/dwho/.ssh/id_ed25519
340340
341341# 4. Connect from bastion to backend-new
342- docker exec -u dwho llng -bastion ssh -o StrictHostKeyChecking=no backend-new whoami
342+ docker exec -u dwho ob-cert -bastion ssh -o StrictHostKeyChecking=no backend-new whoami
343343# Expected: dwho
344344```
345345
346346Note: The user ` dwho ` is resolved dynamically via the NSS module - no local account exists on the server. The home directory is created automatically on first login by ` pam_mkhomedir ` .
347347
348- ### Summary: The llng-pam -enroll Script
348+ ### Summary: The ob -enroll Script
349349
350- The ` llng-pam -enroll` script automates the entire Device Authorization flow:
350+ The ` ob -enroll` script automates the entire Device Authorization flow:
351351
352- 1 . ** Reads configuration** from ` /etc/security/pam_llng .conf ` (portal URL, client credentials)
352+ 1 . ** Reads configuration** from ` /etc/open-bastion/openbastion .conf ` (portal URL, client credentials)
3533532 . ** Requests device code** from ` /oauth2/device ` endpoint
3543543 . ** Displays instructions** with user code for administrator approval
3553554 . ** Polls for token** at configurable intervals
356- 5 . ** Saves token** securely to ` /etc/security/pam_llng.token `
357- 6 . ** Updates configuration** with ` server_token_file ` directive
358- 7 . ** Verifies enrollment** by calling ` /pam/authorize `
356+ 5 . ** Saves token** securely to ` /etc/open-bastion/token `
357+ 6 . ** Verifies enrollment** by calling ` /pam/authorize `
359358
360359Options:
361360
362361``` bash
363- llng-pam -enroll --help
362+ ob -enroll --help
364363
365364Options:
366365 -p, --portal URL LemonLDAP::NG portal URL
367366 -c, --client-id ID OIDC client ID (default: pam-access)
368- -s, --client-secret S OIDC client secret
367+ -s, --client-secret S OIDC client secret (prefer OB_CLIENT_SECRET env var)
369368 -g, --server-group G Server group name
370- -t, --token-file FILE Where to save the token
369+ -t, --token-file FILE Where to save the token (default: /etc/open-bastion/token)
370+ -C, --config FILE Read settings from config file
371371 -k, --insecure Skip SSL certificate verification
372372 -q, --quiet Quiet mode
373373```
374374
375375### How it works
376376
377- 1 . ** Server Token** : Each SSH server needs an OAuth2 access token to authenticate its PAM module requests to the LLNG portal.
377+ 1 . ** Server Token** : Each SSH server needs an OAuth2 access token to authenticate
378+ its PAM module requests to the LLNG portal.
378379
379- 2 . ** Token Configuration** : The token is passed via the ` LLNG_SERVER_TOKEN ` environment variable and stored in ` /etc/security/llng_server_token ` .
380+ 2 . ** Token Configuration** : The token is stored in ` /etc/open-bastion/token ` and
381+ referenced by ` server_token_file ` in ` /etc/open-bastion/openbastion.conf ` .
380382
3813833 . ** PAM Authorization Flow** :
382384 ```
383- User SSH → sshd → PAM (pam_llng .so) → LLNG /pam/authorize → Allow/Deny
385+ User SSH → sshd → PAM (pam_openbastion .so) → LLNG /pam/authorize → Allow/Deny
384386 ```
385387
386388### Registering a new SSH server (production)
387389
388- In production, use the Device Authorization Grant (RFC 8628) to register each server:
390+ In production, use the Device Authorization Grant (RFC 8628) to register each server.
391+ The recommended way is to run ` ob-enroll ` on the server, which takes care of the
392+ entire flow. The raw HTTP exchange below is shown for reference:
389393
390394``` bash
391395# 1. Request device authorization
@@ -406,16 +410,16 @@ TOKEN_RESP=$(curl -s -X POST https://sso.example.com/oauth2/token \
406410 -d " client_secret=<secret>" )
407411
408412# 4. Save the token
409- echo $TOKEN_RESP | jq -r ' .access_token' > /etc/security/llng_server_token
410- chmod 600 /etc/security/llng_server_token
413+ echo $TOKEN_RESP | jq -r ' .access_token' > /etc/open-bastion/token
414+ chmod 600 /etc/open-bastion/token
411415```
412416
413417### PAM Configuration
414418
415- Each server needs ` /etc/security/pam_llng .conf ` :
419+ Each server needs ` /etc/open-bastion/openbastion .conf ` :
416420
417421``` ini
418- # LemonLDAP::NG PAM configuration
422+ # Open Bastion PAM configuration
419423portal_url = https://sso.example.com
420424server_group = production-servers
421425
@@ -424,23 +428,23 @@ client_id = pam-access
424428client_secret = <secret>
425429
426430# Server token for authorization
427- server_token_file = /etc/security/llng_server_token
431+ server_token_file = /etc/open-bastion/token
428432
429433# HTTP settings
430434timeout = 10
431435verify_ssl = true
432436
433437# Cache settings (for offline mode)
434438cache_enabled = true
435- cache_dir = /var/cache/pam_llng
439+ cache_dir = /var/cache/open-bastion
436440cache_ttl = 300
437441```
438442
439443And ` /etc/pam.d/sshd ` :
440444
441445```
442446auth required pam_permit.so
443- account required pam_llng .so
447+ account required pam_openbastion .so
444448session required pam_unix.so
445449```
446450
@@ -469,7 +473,7 @@ sequenceDiagram
469473### How it works:
470474
4714751 . User SSH to bastion with SSH certificate
472- 2 . From bastion, user runs ` llng -ssh-proxy backend`
476+ 2 . From bastion, user runs ` ob -ssh-proxy backend`
4734773 . Proxy requests a signed JWT from LLNG ` /pam/bastion-token `
4744784 . Proxy connects to backend with JWT in ` LLNG_BASTION_JWT ` env var
4754795 . Backend verifies JWT signature using cached JWKS (offline capable)
@@ -494,15 +498,15 @@ sequenceDiagram
494498### Check container logs
495499
496500``` bash
497- docker logs llng -sso
498- docker logs llng -bastion
499- docker logs llng -backend
501+ docker logs ob-cert -sso
502+ docker logs ob-cert -bastion
503+ docker logs ob-cert -backend
500504```
501505
502506### Test PAM authorization manually
503507
504508``` bash
505- docker exec llng -bastion curl -s http://sso/pam/authorize \
509+ docker exec ob-cert -bastion curl -s http://sso/pam/authorize \
506510 -H " Authorization: Bearer <token>" \
507511 -H " Content-Type: application/json" \
508512 -d ' {"user":"dwho","server_group":"bastion"}'
@@ -511,7 +515,7 @@ docker exec llng-bastion curl -s http://sso/pam/authorize \
511515### Verify SSH CA trust
512516
513517``` bash
514- docker exec llng -bastion cat /etc/ssh/llng_ca.pub
518+ docker exec ob-cert -bastion cat /etc/ssh/llng_ca.pub
515519curl http://localhost:80/ssh/ca
516520```
517521
@@ -541,7 +545,7 @@ User authentication sessions are stored in the SSO container:
541545
542546``` bash
543547# List active sessions
544- docker exec llng -sso ls -la /var/lib/lemonldap-ng/sessions/
548+ docker exec ob-cert -sso ls -la /var/lib/lemonldap-ng/sessions/
545549
546550# Session files are named by session ID (the cookie value)
547551```
@@ -552,7 +556,7 @@ The SSH Certificate Authority keys are stored in the SSO:
552556
553557``` bash
554558# View CA storage
555- docker exec llng -sso ls -la /var/lib/lemonldap-ng/ssh/
559+ docker exec ob-cert -sso ls -la /var/lib/lemonldap-ng/ssh/
556560
557561# Files:
558562# - ssh-ca (private key)
@@ -566,7 +570,7 @@ When session recording is enabled on the bastion (via `ForceCommand`), recording
566570
567571``` bash
568572# List recordings
569- docker exec llng- bastion ls -la /var/lib/llng- sessions/
573+ docker exec ob-cert- bastion ls -la /var/lib/open-bastion/ sessions/
570574
571575# Recordings use 'script' format and can be replayed with:
572576# scriptreplay timing_file typescript_file
@@ -575,7 +579,7 @@ docker exec llng-bastion ls -la /var/lib/llng-sessions/
575579Note: In this demo, session recording is disabled to allow ProxyJump. To enable it, add to sshd config:
576580
577581```
578- ForceCommand /usr/sbin/llng -session-recorder
582+ ForceCommand /usr/sbin/ob -session-recorder
579583```
580584
581585### PAM Authorization Cache
@@ -584,7 +588,7 @@ Each SSH server caches authorization responses for offline mode:
584588
585589``` bash
586590# Cache location
587- docker exec llng- bastion ls -la /var/cache/pam_llng /
591+ docker exec ob-cert- bastion ls -la /var/cache/open-bastion /
588592
589593# Cache entries are encrypted and expire based on cache_ttl (default: 300s)
590594```
0 commit comments