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


2 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