summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2024-01-22 21:04:24 +0100
committerChristian Breunig <christian@breunig.cc>2024-01-22 21:08:03 +0100
commit064e0b81f0ac0ca19a108d0e05c1756b9a220cc2 (patch)
tree8a20f0ee157dbde0985ca24d4eb74f92e139ba41
parentdc003f5fe9eaa8d0463d9b9606f968bcb9cbbab6 (diff)
downloadvyos-1x-064e0b81f0ac0ca19a108d0e05c1756b9a220cc2.tar.gz
vyos-1x-064e0b81f0ac0ca19a108d0e05c1756b9a220cc2.zip
op-mode: T5975: add missing 2FA OTP commands
-rw-r--r--op-mode-definitions/show-system.xml.in49
-rwxr-xr-xsrc/op_mode/otp.py124
2 files changed, 173 insertions, 0 deletions
diff --git a/op-mode-definitions/show-system.xml.in b/op-mode-definitions/show-system.xml.in
index 85bfdcdba..027eec6de 100644
--- a/op-mode-definitions/show-system.xml.in
+++ b/op-mode-definitions/show-system.xml.in
@@ -102,6 +102,55 @@
<help>Show user accounts</help>
</properties>
<children>
+ <node name="authentication">
+ <properties>
+ <help>Show user account authentication information</help>
+ </properties>
+ <children>
+ <tagNode name="user">
+ <properties>
+ <help>Show configured user</help>
+ <completionHelp>
+ <path>system login user</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <node name="otp">
+ <properties>
+ <help>Show OTP key information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command>
+ <children>
+ <leafNode name="full">
+ <properties>
+ <help>Show full settings, including QR code and commands for VyOS</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="full"</command>
+ </leafNode>
+ <leafNode name="key-b32">
+ <properties>
+ <help>Show OTP authentication secret in Base32 (used in mobile apps)</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="key-b32"</command>
+ </leafNode>
+ <leafNode name="qrcode">
+ <properties>
+ <help>Show OTP authentication QR code</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="qrcode"</command>
+ </leafNode>
+ <leafNode name="uri">
+ <properties>
+ <help>Show OTP authentication otpauth URI</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/otp.py show_login --username="$6" --info="uri"</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+ </node>
<node name="users">
<properties>
<help>Show user account information</help>
diff --git a/src/op_mode/otp.py b/src/op_mode/otp.py
new file mode 100755
index 000000000..6d4298894
--- /dev/null
+++ b/src/op_mode/otp.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+# Copyright 2017, 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+import sys
+import os
+import vyos.opmode
+from jinja2 import Template
+from vyos.configquery import ConfigTreeQuery
+from vyos.xml import defaults
+from vyos.configdict import dict_merge
+from vyos.utils.process import popen
+
+
+users_otp_template = Template("""
+{% if info == "full" %}
+# You can share it with the user, he just needs to scan the QR in his OTP app
+# username: {{username}}
+# OTP KEY: {{key_base32}}
+# OTP URL: {{otp_url}}
+{{qrcode}}
+# To add this OTP key to configuration, run the following commands:
+set system login user {{username}} authentication otp key '{{key_base32}}'
+{% if rate_limit != "3" %}
+set system login user {{username}} authentication otp rate-limit '{{rate_limit}}'
+{% endif %}
+{% if rate_time != "30" %}
+set system login user {{username}} authentication otp rate-time '{{rate_time}}'
+{% endif %}
+{% if window_size != "3" %}
+set system login user {{username}} authentication otp window-size '{{window_size}}'
+{% endif %}
+{% elif info == "key-b32" %}
+# OTP key in Base32 for system user {{username}}:
+{{key_base32}}
+{% elif info == "qrcode" %}
+# QR code for system user '{{username}}'
+{{qrcode}}
+{% elif info == "uri" %}
+# URI for system user '{{username}}'
+{{otp_url}}
+{% endif %}
+""", trim_blocks=True, lstrip_blocks=True)
+
+
+def _check_uname_otp(username:str):
+ """
+ Check if "username" exists and have an OTP key
+ """
+ config = ConfigTreeQuery()
+ base_key = ['system', 'login', 'user', username, 'authentication', 'otp', 'key']
+ if not config.exists(base_key):
+ return None
+ return True
+
+def _get_login_otp(username: str, info:str):
+ """
+ Retrieve user settings from configuration and set some defaults
+ """
+ config = ConfigTreeQuery()
+ base = ['system', 'login', 'user', username]
+ if not config.exists(base):
+ return None
+ user_otp = config.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(['system', 'login', 'user'])
+ user_otp = dict_merge(default_values, user_otp)
+ result = user_otp['authentication']['otp']
+ # Filling in the system and default options
+ result['info'] = info
+ result['hostname'] = os.uname()[1]
+ result['username'] = username
+ result['key_base32'] = result['key']
+ result['otp_length'] = '6'
+ result['interval'] = '30'
+ result['token_type'] = 'hotp-time'
+ if result['token_type'] == 'hotp-time':
+ token_type_acrn = 'totp'
+ result['otp_url'] = ''.join(["otpauth://",token_type_acrn,"/",username,"@",\
+ result['hostname'],"?secret=",result['key_base32'],"&digits=",\
+ result['otp_length'],"&period=",result['interval']])
+ result['qrcode'],err = popen('qrencode -t ansiutf8', input=result['otp_url'])
+ return result
+
+def show_login(raw: bool, username: str, info:str):
+ '''
+ Display OTP parameters for <username>
+ '''
+ check_otp = _check_uname_otp(username)
+ if check_otp:
+ user_otp_params = _get_login_otp(username, info)
+ else:
+ print(f'There is no such user ("{username}") with an OTP key configured')
+ print('You can use the following command to generate a key for a user:\n')
+ print(f'generate system login username {username} otp-key hotp-time')
+ sys.exit(0)
+ if raw:
+ return user_otp_params
+ return users_otp_template.render(user_otp_params)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)