Overview
This document describes how to protect PAM-authenticated services on macOS with RCDevs OpenOTP.
Unlike the Linux integration, this macOS integration does not configure NSS, SpanKey, POSIX LDAP extensions, home-directory creation, or user provisioning. The macOS user account must already exist and must already be resolvable by macOS Directory Services / OpenDirectory. The account may be a local account, an iCloud-backed local account, a mobile account, an LDAP account, or an Active Directory account.
OpenOTP is added only at the PAM authentication layer. macOS remains responsible for account lookup, account state, shell, home directory, sudo authorization, Remote Login access lists, and directory-service integration.
Scope and limitations
This guide applies to macOS services that use PAM, such as:
- OpenSSH server:
/etc/pam.d/sshd - sudo:
/etc/pam.d/sudoand, on newer macOS versions,/etc/pam.d/sudo_local - su:
/etc/pam.d/su - other third-party PAM-aware services that provide their own file in
/etc/pam.d/
This guide does not cover:
- macOS account creation
- LDAP or Active Directory binding
- home-directory creation
- FileVault pre-boot authentication
- recoveryOS authentication
- Apple Account / iCloud enrollment
- granting sudo privileges
Note that some services configured with PAM authentication may not support OTP challenge mode. In this case, OTP challenge mode must be disabled at the Client Policy level on the WebADM server.
RCDevs does not recommend enabling PAM authentication for the screensaver, login, or any other services unless you fully understand the potential impact.
Prerequisites
Before configuring PAM OpenOTP, confirm that:
- A working WebADM / OpenOTP infrastructure is available.
- The macOS host can reach the OpenOTP service URL over HTTPS port 8443.
- The target macOS users already exist on the Mac or are resolvable through macOS Directory Services.
- The corresponding users exist in WebADM and are allowed by the relevant client policy.
- You have an administrator account and a local recovery method before editing PAM files.
Keep one administrator shell open while testing. A broken PAM configuration can prevent sudo, SSH, or console authentication from working.
Verify account resolution on macOS
PAM OpenOTP does not create users. The operating system must already resolve the account.
Check the user with:
id <username>
admin@admins-Mac-mini pam.d % id admin
uid=501(admin) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae),702(com.apple.sharepoint.group.2)
For network users, LDAP users, or Active Directory users, the macOS local username must match an account in a directory configured in WebADM.
Install the PAM OpenOTP plugin
First, download the pam_openotp-x.x.x.pkg from RCDevs website and copy it on you macOS machine.
Then, to install the package, the command look like :
sudo installer -pkg /path/to/pam_openotp-1.0.19.pkg -target /
root@admins-Mac-mini Documents # sudo installer -pkg pam_openotp-1.0.19.pkg -target /
installer: Package name is pam_openotp-1.0.19
installer: Installing at base path /
installer: The install was successful.
Configure the PAM OpenOTP client
The OpenOTP PAM configuration file is located in :
/etc/openotp/openotp.conf
The default configuration file looks like below :
#
# OpenOTP Client Configurations
#
# OpenOTP SOAP service URL(s). This is the only mandatory setting.
# Two server URLs can be configured and separated by a comma inside the same quoted string.
# When two servers are configured, you may choose a request routing policy below.
server_url "https://<server>:8443/openotp/"
#server_url1 "https://<server1>:8443/openotp/"
#server_url2 "https://<server2>:8443/openotp/"
# Request routing policy when two server URLs are defined.
# Ordered: First server is preferred (default). When down, second server is used.
# Balanced: Server is chosen randomly. When down, the other is used.
# Consistent: One specific user ID is always routed to the same server (per user routing).
#server_policy "Ordered"
# Domain name
#domain_name "MyDomain"
# Client ID
#client_id "MacOS"
# Challenge suffix
challenge_suffix ": "
# User settings
#user_settings "OpenOTP.KeyExpire=10"
# Client certificate
#cert_file "openotp.pem"
#cert_password "password"
# Trusted CA (WebADM CA certificate)
# Copy the WebADM CA certificate file in /etc/openotp/ca.crt and set the ca_file to enforce SSL server trust.
#ca_file "ca.crt"
# OpenOTP API key
# Wen OpenOTP is configured with 'Required Client Certificate or API Key', you need to create an
# API key in WebADM and set its value here for communicating with the OpenOTP API.
#api_key "MY_API_KEY"
# SOAP timeout
# This is the SOAP request TCP timeout. The default timeout is 30 seconds.
#soap_timeout 30
You must configure at least the OpenOTP service URL but in this example few settings as been configured like client_id and ca_file :
#
# OpenOTP Client Configurations
#
# OpenOTP SOAP service URL(s). This is the only mandatory setting.
# Two server URLs can be configured and separated by a comma inside the same quoted string.
# When two servers are configured, you may choose a request routing policy below.
#server_url "https://<server>:8443/openotp/"
server_url1 "https://webadm1.rcdevsdocs.com:8443/openotp/"
server_url2 "https://webadm2.rcdevsdocs.com:8443/openotp/"
# Request routing policy when two server URLs are defined.
# Ordered: First server is preferred (default). When down, second server is used.
# Balanced: Server is chosen randomly. When down, the other is used.
# Consistent: One specific user ID is always routed to the same server (per user routing).
#server_policy "Ordered"
# Domain name
#domain_name "MyDomain"
# Client ID
client_id "macOS-PAM"
# Challenge suffix
challenge_suffix ": "
# User settings
#user_settings "OpenOTP.KeyExpire=10"
# Client certificate
#cert_file "openotp.pem"
#cert_password "password"
# Trusted CA (WebADM CA certificate)
# Copy the WebADM CA certificate file in /etc/openotp/ca.crt and set the ca_file to enforce SSL server trust.
ca_file "ca.crt"
# OpenOTP API key
# Wen OpenOTP is configured with 'Required Client Certificate or API Key', you need to create an
# API key in WebADM and set its value here for communicating with the OpenOTP API.
#api_key "MY_API_KEY"
# SOAP timeout
# This is the SOAP request TCP timeout. The default timeout is 30 seconds.
#soap_timeout 30
You can optionnaly configure a client authentication with an API key or a client certificate.
The CA file can be downloaded from https://<webadm>/cacert and located /etc/openotp/ folder.
root@admins-Mac-mini openotp # pwd
/etc/openotp
root@admins-Mac-mini openotp # curl -k https://webadm1.rcdevsdocs.com/cacert -o ca.crt
root@admins-Mac-mini openotp # ls -al
total 24
drwxr-xr-x 5 root wheel 160 May 21 11:21 .
drwxr-xr-x 80 root wheel 2560 May 21 10:03 ..
-rw-r--r-- 1 root wheel 1991 May 21 11:21 ca.crt
-rw-r--r-- 1 root wheel 1844 May 21 10:08 openotp.conf
-rw-r--r-- 1 root wheel 1830 May 20 19:02 openotp.conf.default
root@admins-Mac-mini openotp #
Protect sshd
Configure SSH to use PAM keyboard-interactive authentication
Back up the SSH server configuration:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.rcdevs.bak.$(date +%Y%m%d%H%M%S)
Edit /etc/ssh/sshd_config:
sudo vi /etc/ssh/sshd_config
Recommended settings for password + OpenOTP challenge through PAM:
UsePAM yes
KbdInteractiveAuthentication yes
PasswordAuthentication yes
PAM configuration file for sshd
Back up the PAM file:
sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.rcdevs.bak.$(date +%Y%m%d%H%M%S)
Edit the file:
sudo vi /etc/pam.d/sshd
Default file look like this on macOS
# sshd: auth account password session
auth optional pam_krb5.so use_kcminit
auth optional pam_ntlm.so try_first_pass
auth optional pam_mount.so try_first_pass
auth required pam_opendirectory.so try_first_pass
account required pam_nologin.so
account required pam_sacl.so sacl_service=ssh
account required pam_opendirectory.so
password required pam_opendirectory.so
session required pam_launchd.so
session optional pam_mount.so
I'm adding the PAM instruction after the last auth line like below :
# sshd: auth account password session
auth optional pam_krb5.so use_kcminit
auth optional pam_ntlm.so try_first_pass
auth optional pam_mount.so try_first_pass
auth required pam_opendirectory.so try_first_pass
auth required pam_openotp.so
account required pam_nologin.so
account required pam_sacl.so sacl_service=ssh
account required pam_opendirectory.so
password required pam_opendirectory.so
session required pam_launchd.so
session optional pam_mount.so
Keep the existing macOS auth, account and session lines. They are used for macOS account validation, Remote Login access control, and session setup.
Protect sudo
PAM OpenOTP does not grant sudo rights. The user must already be allowed to run sudo through local admin membership, /etc/sudoers, /etc/sudoers.d/, or an enterprise policy.
On the tested version of macOS, the sudo PAM configuration file is /etc/pam.d/sudo.
Back up /etc/pam.d/sudo:
sudo cp /etc/pam.d/su /etc/pam.d/sudo.rcdevs.bak.$(date +%Y%m%d%H%M%S)
Edit the file:
sudo vi /etc/pam.d/sudo
The default file looks like this:
# sudo: auth account password session
auth include sudo_local
auth sufficient pam_smartcard.so
auth required pam_opendirectory.so
account required pam_permit.so
password required pam_deny.so
session required pam_permit.so
The modified file, which includes OpenOTP authentication, looks like this:
# sudo: auth account password session
auth include sudo_local
auth sufficient pam_smartcard.so
auth required pam_opendirectory.so
auth required pam_openotp.so client_id=sudo
account required pam_permit.so
password required pam_deny.so
session required pam_permit.so
Note: In this example, a Client ID is configured directly in the PAM configuration file. This overrides the default client_id in /etc/openotp/openotp.conf, allowing a different Client Policy to be applied based on the PAM service being called.
Protect su
Back up /etc/pam.d/su:
sudo cp /etc/pam.d/su /etc/pam.d/su.rcdevs.bak.$(date +%Y%m%d%H%M%S)
Edit the file:
sudo vi /etc/pam.d/su
The default file looks like this:
# su: auth account session
auth sufficient pam_rootok.so
auth required pam_opendirectory.so
account required pam_group.so no_warn group=admin,wheel ruser root_only fail_safe
account required pam_opendirectory.so no_check_shell
password required pam_opendirectory.so
session required pam_launchd.so
The modified file, which includes OpenOTP authentication, looks like this:
# su: auth account session
auth sufficient pam_rootok.so
auth required pam_opendirectory.so
auth required pam_openotp.so client_id=su
account required pam_group.so no_warn group=admin,wheel ruser root_only fail_safe
account required pam_opendirectory.so no_check_shell
password required pam_opendirectory.so
session required pam_launchd.so
Note: In this example, a Client ID is configured directly in the PAM configuration file. This overrides the default client_id in /etc/openotp/openotp.conf, allowing a different Client Policy to be applied based on the PAM service being called.
Test:
su - <username>
Client policies
A client policy can be configured globally based on the client_id configuration in /etc/openotp/openotp.conf, or per macOS service. A custom Client ID can be configured in the OpenOTP PAM client configuration, as shown in the previous example.
Client policies can restrict which users or groups may authenticate, which networks are allowed, which MFA methods are accepted, and whether challenge mode is supported.
Refer to Policies & Conditional Access documentation to create and configure your client policies. For PAM services that do not support OTP challenge mode, disable it in the Client Policy configuration.
Uninstall macOS PAM plugin
Before removing the PAM-macOS plugin, ensure that you remove the OpenOTP library from your PAM configuration file or restore the backup file.
You can then execute the following command to completely remove the plugin.
# 1. Verify the installed files
pkgutil --files com.rcdevs.pam-openotp
# 2. Remove the PAM module
sudo rm -f /usr/local/lib/pam/pam_openotp.so
# 3. Remove empty directories if they are now unused
sudo rmdir /usr/local/lib/pam 2>/dev/null
sudo rmdir /usr/local/lib 2>/dev/null
# 4. Remove the package receipt
sudo pkgutil --forget com.rcdevs.pam-openotp
# 5. Remove configuration folder
sudo rm -rf /etc/openotp/
# 5. Verify removal
pkgutil --pkgs | grep -i openotp
ls -l /usr/local/lib/pam/
Troubleshooting
For authentication issues on the OpenOTP server side, consult /opt/webadm/logs/webadm.log.
For issues on macOS, you can use a command similar to the following from the command line:
log stream | grep -E "sshd-session|kernel|openotp|pam"
