cli: add 'show keystore' command to admin-exec

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2026-02-02 17:50:13 +01:00
parent 08742df166
commit 2b6588eaf4
5 changed files with 243 additions and 18 deletions
+20
View File
@@ -659,6 +659,25 @@ def nacm(args: List[str]) -> None:
print(f"Unknown NACM subcommand: {subcommand}")
def keystore(args: List[str]) -> None:
data = get_json("/ietf-keystore:keystore", "running", quiet=True)
if not data:
user = os.environ.get('USER', 'unknown')
print(f'No keystore data available (check NACM permissions for "{user}").')
return
if RAW_OUTPUT:
print(json.dumps(data, indent=2))
return
if len(args) == 0 or not args[0]:
cli_pretty(data, "show-keystore")
elif len(args) >= 2 and args[0] in ("symmetric", "asymmetric"):
cli_pretty(data, "show-keystore", "-t", args[0], "-n", args[1])
else:
print("Usage: show keystore [symmetric <name> | asymmetric <name>]")
def execute_command(command: str, args: List[str]):
command_mapping = {
'bfd': bfd,
@@ -667,6 +686,7 @@ def execute_command(command: str, args: List[str]):
'dhcp': dhcp,
'hardware': hardware,
'interface': interface,
'keystore': keystore,
'lldp': lldp,
'nacm': nacm,
'ntp': ntp,
+16
View File
@@ -413,6 +413,20 @@ int infix_groups(kcontext_t *ctx)
"| jq -r '.\"ietf-netconf-acm:nacm\".groups.group[].name'");
}
int infix_sym_keys(kcontext_t *ctx)
{
(void)ctx;
return shellf("copy running -x /ietf-keystore:keystore/symmetric-keys "
"| jq -r '.\"ietf-keystore:keystore\".\"symmetric-keys\".\"symmetric-key\"[].name'");
}
int infix_asym_keys(kcontext_t *ctx)
{
(void)ctx;
return shellf("copy running -x /ietf-keystore:keystore/asymmetric-keys "
"| jq -r '.\"ietf-keystore:keystore\".\"asymmetric-keys\".\"asymmetric-key\"[].name'");
}
int kplugin_infix_fini(kcontext_t *ctx)
{
(void)ctx;
@@ -432,6 +446,8 @@ int kplugin_infix_init(kcontext_t *ctx)
kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces));
kplugin_add_syms(plugin, ksym_new("users", infix_users));
kplugin_add_syms(plugin, ksym_new("groups", infix_groups));
kplugin_add_syms(plugin, ksym_new("sym_keys", infix_sym_keys));
kplugin_add_syms(plugin, ksym_new("asym_keys", infix_asym_keys));
kplugin_add_syms(plugin, ksym_new("firewall_zones", infix_firewall_zones));
kplugin_add_syms(plugin, ksym_new("firewall_policies", infix_firewall_policies));
kplugin_add_syms(plugin, ksym_new("firewall_services", infix_firewall_services));
+28
View File
@@ -155,6 +155,20 @@
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="SYM_KEYS">
<COMPL>
<ACTION sym="sym_keys@infix"/>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<PTYPE name="ASYM_KEYS">
<COMPL>
<ACTION sym="asym_keys@infix"/>
</COMPL>
<ACTION sym="STRING"/>
</PTYPE>
<VIEW name="main">
<HOTKEY key="^D" cmd="exit"/>
@@ -353,6 +367,20 @@ echo "Public: $pub"
</ACTION>
</COMMAND>
<COMMAND name="keystore" help="Show keystore keys">
<SWITCH name="subcommands" min="0">
<COMMAND name="symmetric" help="Show details for a symmetric key">
<PARAM name="name" ptype="/SYM_KEYS" help="Key name"/>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore symmetric $KLISH_PARAM_name</ACTION>
</COMMAND>
<COMMAND name="asymmetric" help="Show details for an asymmetric key">
<PARAM name="name" ptype="/ASYM_KEYS" help="Key name"/>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore asymmetric $KLISH_PARAM_name</ACTION>
</COMMAND>
</SWITCH>
<ACTION sym="script" in="tty" out="tty" interrupt="true">show keystore</ACTION>
</COMMAND>
<COMMAND name="nacm" help="Show users and NACM status and groups">
<SWITCH name="subcommands" min="0">
<COMMAND name="group" help="Show details for a specific NACM group">
+133
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
import base64
import json
import argparse
import sys
@@ -1030,6 +1031,7 @@ class DhcpServer:
class Iface:
# Class variable to hold routing-enabled interfaces for the current display session
_routing_ifaces = set()
_keystore = {}
def __init__(self, data):
self.data = data
@@ -3381,6 +3383,131 @@ def show_nacm_user(json):
print("For detailed rules, use: show nacm group <name>")
def _keystore_format_name(key_format):
"""Simplify key-format to a short display name."""
fmt = key_format.split(':')[-1] if ':' in key_format else key_format
return fmt.replace('-key-format', '')
def _keystore_decode_symmetric(key):
"""Decode a symmetric key value for display."""
key_format = key.get('key-format', '')
fmt = _keystore_format_name(key_format)
b64val = key.get('cleartext-symmetric-key', '')
if fmt == 'passphrase' and b64val:
try:
return base64.b64decode(b64val).decode('utf-8')
except Exception:
return b64val
return b64val if b64val else '-'
def _keystore_find_key(keystore, kind, name):
"""Find a key by type and name in the keystore."""
if kind == 'symmetric':
keys = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
else:
keys = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
for key in keys:
if key.get('name') == name:
return key
return None
def _keystore_asym_type(key):
"""Derive asymmetric key algorithm from key format fields."""
for field in ('private-key-format', 'public-key-format'):
fmt = key.get(field, '')
name = fmt.split(':')[-1] if ':' in fmt else fmt
name = name.replace('-private-key-format', '').replace('-public-key-format', '')
if name:
return name
return ''
def show_keystore_detail(keystore, kind, name):
"""Display detailed information about a specific key."""
key = _keystore_find_key(keystore, kind, name)
if not key:
print(f'{kind.capitalize()} key "{name}" not found.')
return
print(f"{'name':<{20}}: {name}")
if kind == 'symmetric':
fmt = _keystore_format_name(key.get('key-format', ''))
value = _keystore_decode_symmetric(key)
print(f"{'format':<{20}}: {fmt}")
print(f"{'value':<{20}}: {value}")
else:
ktype = _keystore_asym_type(key)
if ktype:
print(f"{'algorithm':<{20}}: {ktype}")
pub_fmt = _keystore_format_name(key.get('public-key-format', ''))
if pub_fmt:
print(f"{'public key format':<{20}}: {pub_fmt}")
pub_key = key.get('public-key', '')
if pub_key:
print(f"{'public key':<{20}}: {pub_key}")
def show_keystore(json, kind=None, name=None):
"""Display keystore keys overview or detail for a specific key."""
keystore = json.get("ietf-keystore:keystore", {})
if not keystore:
print("Keystore is empty.")
return
if kind and name:
show_keystore_detail(keystore, kind, name)
return
TABLE_WIDTH = 72
# Symmetric keys
sym_keys_data = keystore.get('symmetric-keys', {}).get('symmetric-key', [])
if sym_keys_data:
Decore.title("Symmetric Keys", TABLE_WIDTH)
table = SimpleTable([
Column('NAME', flexible=True),
Column('FORMAT'),
Column('VALUE', flexible=True)
], min_width=TABLE_WIDTH)
for key in sym_keys_data:
name = key.get('name', '')
fmt = _keystore_format_name(key.get('key-format', ''))
value = _keystore_decode_symmetric(key)
table.row(name, fmt, value)
table.print()
# Asymmetric keys
asym_keys_data = keystore.get('asymmetric-keys', {}).get('asymmetric-key', [])
if asym_keys_data:
Decore.title("Asymmetric Keys", TABLE_WIDTH)
table = SimpleTable([
Column('NAME', flexible=True),
Column('TYPE'),
Column('PUBLIC KEY', flexible=True)
], min_width=TABLE_WIDTH)
for key in asym_keys_data:
name = key.get('name', '')
ktype = _keystore_asym_type(key)
pub_key = key.get('public-key', '')
if len(pub_key) > 40:
pub_key = pub_key[:37] + '...'
table.row(name, ktype, pub_key)
table.print()
if not sym_keys_data and not asym_keys_data:
print("Keystore is empty.")
def show_system(json):
"""System information overivew"""
if not json.get("ietf-system:system-state"):
@@ -5369,6 +5496,10 @@ def main():
subparsers.add_parser('show-nacm-group', help='Show NACM group details')
subparsers.add_parser('show-nacm-user', help='Show NACM user details')
ks_parser = subparsers.add_parser('show-keystore', help='Show keystore keys')
ks_parser.add_argument('-t', '--type', help='Key type (symmetric or asymmetric)')
ks_parser.add_argument('-n', '--name', help='Key name')
subparsers.add_parser('show-ntp', help='Show NTP status') \
.add_argument('-a', '--address', help='Show details for specific address')
subparsers.add_parser('show-ntp-tracking', help='Show NTP tracking status')
@@ -5438,6 +5569,8 @@ def main():
show_nacm_group(json_data)
elif args.command == "show-nacm-user":
show_nacm_user(json_data)
elif args.command == "show-keystore":
show_keystore(json_data, getattr(args, 'type', None), args.name)
elif args.command == "show-ntp":
show_ntp(json_data, args.address)
elif args.command == "show-ntp-tracking":