Implementing Two-Factor SSH Authentication for Public IP Access on Ubuntu Servers


10 views

When securing an Ubuntu server with both internal and external IP addresses, you might want different authentication requirements for each access point. For public-facing SSH access, two-factor authentication (2FA) adds a critical security layer, while internal connections might maintain simpler authentication for operational efficiency.

Before configuration, ensure you have:

  • Ubuntu Server 18.04 LTS or later
  • SSH server installed and running
  • Root or sudo privileges
  • Google Authenticator PAM module (libpam-google-authenticator)

First, install the necessary package:

sudo apt update
sudo apt install libpam-google-authenticator

Then modify the SSH configuration to apply 2FA only to public IP connections. Edit /etc/ssh/sshd_config:

Match Address [YOUR_PUBLIC_IP]
    AuthenticationMethods publickey,keyboard-interactive
    PasswordAuthentication no
    ChallengeResponseAuthentication yes
    UsePAM yes

Match all
    PasswordAuthentication no
    PubkeyAuthentication yes

For each user needing public access:

google-authenticator
# Follow the interactive prompts to generate QR code
# Save emergency scratch codes securely

Create a custom PAM configuration file at /etc/pam.d/sshd_public:

auth required pam_google_authenticator.so
auth required pam_unix.so use_first_pass
account required pam_unix.so

Then modify /etc/pam.d/sshd to conditionally include this configuration:

auth [success=1 default=ignore] pam_access.so accessfile=/etc/security/public_ips.conf
auth required pam_deny.so
auth include sshd_public

Always maintain a separate SSH connection when testing configuration changes. Verify the setup:

ssh -v user@public_ip
# Should prompt for both SSH key and OTP

ssh -v user@private_ip
# Should only require SSH key

Common issues to check:

  • SELinux/AppArmor permissions on PAM modules
  • Firewall rules blocking UDP/123 for NTP (time sync critical for OTP)
  • Correct file permissions on ~/.google_authenticator

When managing servers with both internal and external interfaces, security policies often need to differ between trusted local networks and public-facing access. Here's how to implement selective 2FA for SSH:

Edit your /etc/ssh/sshd_config with these conditional blocks:

# Public IP specific configuration
Match Address YOUR_PUBLIC_IP
    AuthenticationMethods publickey,keyboard-interactive
    UsePAM yes

# Internal network configuration
Match Address YOUR_INTERNAL_NETWORK
    AuthenticationMethods publickey
    UsePAM no

Install the PAM module:

sudo apt-get install libpam-google-authenticator -y

Configure PAM by editing /etc/pam.d/sshd:

# Only apply to public IP connections
auth [success=1 default=ignore] pam_access.so accessfile=/etc/security/public-ip-access.conf
auth required pam_google_authenticator.so

Create the access control file at /etc/security/public-ip-access.conf:

# Only require 2FA from public IP
+: ALL : YOUR_PUBLIC_IP
-: ALL : ALL

For more flexibility, consider these methods:

1. TCP Wrappers with PAM:

# /etc/hosts.allow
sshd: LOCAL_NETWORK
sshd: PUBLIC_IP : spawn (/usr/bin/logger -t sshd "Public IP login attempt") & : pam_enable

2. Conditional PAM via iptables MARK:

# Mark packets from public IP
sudo iptables -t mangle -A PREROUTING -p tcp --dport 22 -d YOUR_PUBLIC_IP -j MARK --set-mark 0x1

# Then match in PAM:
auth [success=1 default=ignore] pam_exec.so quiet /usr/sbin/iptables -L -t mangle -n | grep -q MARK\ 0x1
auth required pam_google_authenticator.so

Always test with two separate connections:

# Test internal access (should skip 2FA)
ssh user@internal_ip

# Test external access (should prompt for token)
ssh user@public_ip

Check logs for verification:

tail -f /var/log/auth.log | grep -E 'sshd|pam'

If 2FA triggers for internal IPs:

  1. Verify netmasks in access files
  2. Check for conflicting PAM rules
  3. Test with pamtester utility

Remember to restart SSH after changes:

sudo systemctl restart sshd