Before deploying the PAM module on your servers, you need to configure LemonLDAP::NG.
Copy the plugins from the llng-plugin directory to your LemonLDAP::NG installation:
sudo cp -r llng-plugin/usr/share/* /usr/share/This installs the 3 Open Bastion plugins for LemonLDAP::NG:
- PamAccess - Main plugin: token generation interface and authorization endpoints (
/pam/authorize,/pam/bastion-token) - OIDCDeviceAuthorization - Server enrollment via OAuth 2.0 Device Authorization Grant (RFC 8628)
- SSHCA (optional) - SSH Certificate Authority for certificate-based authentication
In the LLNG Manager, create a new OIDC Relying Party:
- Go to OpenID Connect Relying Parties → Add
- Configure:
- Client ID:
pam-access - Client secret: Generate a strong secret
- Allowed grant types: Enable
device_code(for server enrollment) - Allowed scopes:
openid,pam:server
- Client ID:
Use customPlugins inside lemonldap-ng.ini, section [portal]:
- without SSHCA:
[portal]
customPlugins = ::Plugin::OIDCDeviceAuthorization, ::Plugins::PamAccess- with SSHCA
[portal]
customPlugins = ::Plugin::OIDCDeviceAuthorization, ::Plugins::PamAccess, ::Plugins::SSHCAAdditional and optional parameters that can be inserted into lemonldap-ng.ini, section [portal]:
| Parameter | Default | Description |
|---|---|---|
oidcServiceDeviceAuthorizationExpiration |
600 (10mn) |
Device authorization expiration time |
oidcServiceDeviceAuthorizationPollingInterval |
5 |
Polling interval in seconds |
oidcServiceDeviceAuthorizationUserCodeLength |
8 |
Length of user code |
portalDisplayPamAccess |
0 |
Set to 1 (or a rule) to display PAM tab |
pamAccessRp |
pam-access |
OIDC Relying Party name |
pamAccessTokenDuration |
600 (10mn) |
Token duration |
pamAccessMaxDuration |
3600 (1h) |
Maximum token duration |
pamAccessExportedVars |
{} |
Exported variables |
pamAccessOfflineTtl |
86400 (1d) |
Offline cache TTL |
pamAccessSshRules |
{} |
SSH access rules |
pamAccessServerGroups |
{} |
Server groups configuration |
pamAccessSudoRules |
{} |
Sudo rules |
pamAccessOfflineEnabled |
0 |
Enable offline mode |
pamAccessHeartbeatInterval |
300 (5mn) |
Heartbeat interval |
pamAccessManagedGroups |
{} |
Unix groups managed by LLNG per server group (see Group Synchronization) |
When offline mode is enabled, the server-side cache is protected by Cache Brute-Force Protection.
| Parameter | Default | Description |
|---|---|---|
portalDisplaySshCa |
0 |
Set to 1 (or a rule) to display SSHCA tab |
sshCaCertMaxValidity |
365 (1y) |
Maximum certificate validity |
sshCaSerialPath |
"" |
Path for certificate serial storage |
sshCaPrincipalSources |
$uid |
Principal sources |
sshCaKrlPath |
"" |
Path for Key Revocation List |
If you're using the SSH CA plugin for key-based authentication, you need to generate a CA key pair and import it into LemonLDAP::NG.
# Generate Ed25519 CA key pair (recommended)
openssl genpkey -algorithm ed25519 -out ssh-ca.key
openssl pkey -in ssh-ca.key -pubout -out ssh-ca.pub
# Display keys for import into LLNG Manager
echo "=== Private Key (copy this) ==="
cat ssh-ca.key
echo "=== Public Key (copy this) ==="
cat ssh-ca.pubAlternatively, for compatibility with older systems, use RSA:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out ssh-ca.key
openssl pkey -in ssh-ca.key -pubout -out ssh-ca.pub- Go to General Parameters → Keys → Add a key
- Set a key name (e.g.,
ssh-ca) - Paste the private key content into Private key
- Paste the public key content into Public key
- Save the configuration
Then configure the SSH CA plugin to use this key inside lemonldap-ng.ini, section [portal]:
[portal]
sshCaKeyRef = ssh-caInsert this into lemonldap-ng.ini, section [portal]:
[portal]
keys = { ssh-ca => { keyPublic => "<public key value>", keyPrivate => "<private key value>" } }
sshCaKeyRef = ssh-casudo mkdir -p /var/lib/lemonldap-ng/ssh
sudo chown www-data:www-data /var/lib/lemonldap-ng/sshThese directories store the certificate serial number counter and the Key Revocation List (KRL).
sudo systemctl restart lemonldap-ng-fastcgi-server
# or
sudo systemctl restart apache2 # if using mod_perlServer groups allow different authorization rules for different server categories.
General Parameters > Plugins > PAM Access > Server Groups
production => $hGroup->{ops}
staging => $hGroup->{ops} or $hGroup->{dev}
dev => $hGroup->{dev}
default => 1
In /etc/open-bastion/openbastion.conf:
server_group = productionOr during enrollment:
sudo ob-enroll -g productionThe group synchronization feature (#38) allows LemonLDAP::NG to manage Unix supplementary groups on target servers. When a user connects via SSH, their Unix groups are synchronized with the groups defined in LLNG.
In lemonldap-ng.ini, configure which groups LLNG should manage for each server group:
pamAccessManagedGroups = {
production => 'docker,developers,readonly',
staging => 'developers,testers',
bastion => 'operators,auditors',
default => ''
}- Groups listed in
pamAccessManagedGroupswill be created automatically on the server if they don't exist - Users are added to groups they're assigned to in LLNG
- Users are removed from managed groups they're no longer assigned to in LLNG
- Groups NOT in
pamAccessManagedGroupsare never modified (local groups are preserved)
sequenceDiagram
participant Client as SSH Client
participant Server as Server (PAM)
participant LLNG as LemonLDAP::NG
Client->>Server: ssh user@server
Server->>LLNG: /pam/authorize
LLNG-->>Server: {groups: ["dev","docker"],<br/>managed_groups: ["dev","docker","qa"]}
Note over Server: Filter by local whitelist<br/>(if configured)
Note over Server: Sync groups:<br/>• Add user to "dev", "docker"<br/>• Remove from "qa" (managed but not assigned)
Server-->>Client: Session established
- Principle of least privilege: Don't include privileged groups (sudo, wheel, admin) in
managed_groups - Audit trail: All group modifications are logged with event type
GROUP_SYNC - Offline behavior: Group sync uses cached group information when LLNG is unreachable
- File protection: Group modifications use system tools (
groupadd,gpasswd) which handle/etc/groupand/etc/gshadowatomically
Administrators can optionally configure a local whitelist of groups allowed to be managed on each server. This provides defense-in-depth by restricting which groups LLNG can actually modify, regardless of what managed_groups it sends.
In /etc/open-bastion/openbastion.conf:
# Only allow these groups to be managed by LLNG on this server
allowed_managed_groups = docker,developers,readonlyWhen configured:
- Groups must be in BOTH
pamAccessManagedGroups(from LLNG) ANDallowed_managed_groups(local) to be synced - Groups sent by LLNG but not in the local whitelist are silently ignored
- This allows local administrators to have final control over which groups can be managed
Use cases:
- Restrict LLNG to manage only specific groups on sensitive servers
- Allow different group policies per server even within the same server group
- Provide a safety net against misconfigured LLNG policies
# Developer groups differ by environment
pamAccessManagedGroups = {
production => 'app-users,readonly', # Read-only in prod
staging => 'app-users,developers,docker', # Full dev access in staging
bastion => 'operators' # Bastion operators only
}When a user moves from staging to production access, their docker and developers group memberships are automatically removed on production servers.
- PAM Authentication Modes - Configure PAM on servers
- Configuration Reference - All configuration options
- Admin Guide - Complete administration guide