From 765f84386b6e94984ff79db2eab36d51f759159b Mon Sep 17 00:00:00 2001 From: goodNETnick Date: Thu, 22 Sep 2022 02:03:04 -0400 Subject: system login: T874: add 2FA support for local and ssh authentication --- data/templates/login/pam_otp_ga.conf.j2 | 7 +++ data/templates/ssh/sshd_config.j2 | 4 +- debian/vyos-1x.postinst | 10 ++++ interface-definitions/system-login.xml.in | 76 ++++++++++++++++++++++++++++++ smoketest/scripts/cli/test_system_login.py | 16 +++++++ src/conf_mode/system-login.py | 15 +++++- 6 files changed, 125 insertions(+), 3 deletions(-) create mode 100644 data/templates/login/pam_otp_ga.conf.j2 diff --git a/data/templates/login/pam_otp_ga.conf.j2 b/data/templates/login/pam_otp_ga.conf.j2 new file mode 100644 index 000000000..4c1f411d1 --- /dev/null +++ b/data/templates/login/pam_otp_ga.conf.j2 @@ -0,0 +1,7 @@ +{% if authentication.otp.key is vyos_defined %} +{{ authentication.otp.key }} +" RATE_LIMIT {{ authentication.otp.rate_limit }} {{ authentication.otp.rate_time }} +" WINDOW_SIZE {{ authentication.otp.window_size }} +" DISALLOW_REUSE +" TOTP_AUTH +{% endif %} diff --git a/data/templates/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2 index e7dbca581..93c6735dd 100644 --- a/data/templates/ssh/sshd_config.j2 +++ b/data/templates/ssh/sshd_config.j2 @@ -17,7 +17,6 @@ PubkeyAuthentication yes IgnoreRhosts yes HostbasedAuthentication no PermitEmptyPasswords no -ChallengeResponseAuthentication no X11Forwarding yes X11DisplayOffset 10 PrintMotd no @@ -30,6 +29,7 @@ PermitRootLogin no PidFile /run/sshd/sshd.pid AddressFamily any DebianBanner no +PasswordAuthentication no # # User configurable section @@ -48,7 +48,7 @@ Port {{ value }} LogLevel {{ loglevel | upper }} # Specifies whether password authentication is allowed -PasswordAuthentication {{ "no" if disable_password_authentication is vyos_defined else "yes" }} +ChallengeResponseAuthentication {{ "no" if disable_password_authentication is vyos_defined else "yes" }} {% if listen_address is vyos_defined %} # Specifies the local addresses sshd should listen on diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst index 6879b6e4f..dc64e7a42 100644 --- a/debian/vyos-1x.postinst +++ b/debian/vyos-1x.postinst @@ -21,6 +21,16 @@ if ! grep -q '^openvpn' /etc/passwd; then adduser --quiet --firstuid 100 --system --group --shell /usr/sbin/nologin openvpn fi +# Add 2FA support for SSH +sudo grep -qF -- "auth required pam_google_authenticator.so nullok" "/etc/pam.d/sshd" || \ +sudo sed -i '/^@include common-auth/a # Check OTP 2FA, if configured for the user\nauth required pam_google_authenticator.so nullok' /etc/pam.d/sshd \ +/ + +# Add 2FA support for local authentication +sudo grep -qF -- "auth required pam_google_authenticator.so nullok" "/etc/pam.d/login" || \ +sudo sed -i '/^@include common-auth/a # Check OTP 2FA, if configured for the user\nauth required pam_google_authenticator.so nullok' /etc/pam.d/login \ +/ + # Add RADIUS operator user for RADIUS authenticated users to map to if ! grep -q '^radius_user' /etc/passwd; then adduser --quiet --firstuid 1000 --disabled-login --ingroup vyattaop \ diff --git a/interface-definitions/system-login.xml.in b/interface-definitions/system-login.xml.in index 24eeee355..79c7c4791 100644 --- a/interface-definitions/system-login.xml.in +++ b/interface-definitions/system-login.xml.in @@ -8,6 +8,62 @@ 400 + + + Global authentication settings + + + + + 2FA OTP authentication parameters + + + + + Number of attempts. Limit logins to N per every M seconds + + u32:1-10 + Number of attempts. Limit logins to N per every M seconds + + + + + Number of login attempts must me between 1 and 10 + + 3 + + + + Time interval. Limit logins to N per every M seconds + + u32:15-600 + Time interval. Limit logins to N per every M seconds + + + + + Rate limit time interval must be between 15 and 600 seconds + + 30 + + + + Set window of concurrently valid codes + + u32:1-21 + Set window of concurrently valid codes + + + + + Window of concurrently valid codes must be between 1 and 21 + + 3 + + + + + Local user account information @@ -36,6 +92,26 @@ ! + + + 2FA OTP authentication parameters + + + + + Token Key Secret key for the token algorithm (see RFC 4226) + + txt + OTP key (base32 encoded secret) + + + [a-zA-Z2-7]{20,10000} + + Key must only include base32 characters and be at least 26 characters long + + + + Plaintext password used for encryption diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py index 1131b6f93..a99721d66 100755 --- a/smoketest/scripts/cli/test_system_login.py +++ b/smoketest/scripts/cli/test_system_login.py @@ -97,6 +97,22 @@ class TestSystemLogin(VyOSUnitTestSHIM.TestCase): # b'Linux LR1.wue3 5.10.61-amd64-vyos #1 SMP Fri Aug 27 08:55:46 UTC 2021 x86_64 GNU/Linux\n' self.assertTrue(len(stdout) > 40) + def test_system_login_otp(self): + otp_user = 'otp-test_user' + otp_password = 'SuperTestPassword' + otp_key = '76A3ZS6HFHBTOK2H4NDHTIVFPQ' + + self.cli_set(base_path + ['user', otp_user, 'authentication', 'plaintext-password', otp_password]) + self.cli_set(base_path + ['user', otp_user, 'authentication', 'otp', 'key', otp_key]) + + self.cli_commit() + + # Check if OTP key was written properly + tmp = cmd(f'sudo head -1 /home/{otp_user}/.google_authenticator') + self.assertIn(otp_key, tmp) + + self.cli_delete(base_path + ['user', otp_user]) + def test_system_user_ssh_key(self): ssh_user = 'ssh-test_user' public_keys = 'vyos_test@domain-foo.com' diff --git a/src/conf_mode/system-login.py b/src/conf_mode/system-login.py index 3dcbc995c..fc2723ece 100755 --- a/src/conf_mode/system-login.py +++ b/src/conf_mode/system-login.py @@ -245,7 +245,20 @@ def apply(login): user_config, permission=0o600, formater=lambda _: _.replace(""", '"'), user=user, group='users') - + #OTP 2FA key file generation + if dict_search('authentication.otp.key', user_config): + user_config['authentication']['otp']['key'] = user_config['authentication']['otp']['key'].upper() + user_config['authentication']['otp']['rate_limit'] = login['authentication']['otp']['rate_limit'] + user_config['authentication']['otp']['rate_time'] = login['authentication']['otp']['rate_time'] + user_config['authentication']['otp']['window_size'] = login['authentication']['otp']['window_size'] + render(f'{home_dir}/.google_authenticator', 'login/pam_otp_ga.conf.j2', + user_config, permission=0o600, + formater=lambda _: _.replace(""", '"'), + user=user, group='users') + #OTP 2FA key file deletion + elif os.path.exists(f'{home_dir}/.google_authenticator'): + os.remove(f'{home_dir}/.google_authenticator') + except Exception as e: raise ConfigError(f'Adding user "{user}" raised exception: "{e}"') -- cgit v1.2.3