Source code for saltext.vcf.clients.vrli_settings

"""VCF Operations for Logs (vRLI) — appliance settings (SSH-driven).

Two 912-Controls requirements on vRLI 9.0.2.0 have **no REST surface**
on this build; both are set on the appliance itself:

* **Session inactivity timeout** — lives in Jetty's ``web.xml`` at
  ``/usr/lib/loginsight/application/etc/3rd_config/web.xml`` as
  ``<session-timeout>N</session-timeout>`` (value in *minutes*). The
  vRLI API's session ``ttl`` field mirrors this same value in seconds.
  We probed ``/api/v2/settings/*``, ``/api/v2/timeouts``,
  ``/api/v2/session-timeout`` and ``/api/v2/security/session-timeout``
  — all 404 on 9.0.2.0.25575214.

* **IPv4 DNS servers** — the read-side is REST
  (``GET /api/v2/cluster/nodes`` returns ``dnsServers`` as a
  space-separated string), but there is **no write endpoint**. DNS is
  applied by ``systemd-networkd`` from
  ``/etc/systemd/network/10-eth0.network`` (static config on the
  appliance) and reflected into ``/run/systemd/resolve/resolv.conf``
  via ``systemd-resolved``. We edit ``10-eth0.network`` and restart
  ``systemd-networkd`` + ``systemd-resolved``.

Both operations require root SSH (see the ``ssh`` sub-block in
``saltext.vcf.vrli`` pillar); the API user has no filesystem access.
"""

import logging
import re

from saltext.vcf.utils import ssh as ssh_util
from saltext.vcf.utils import vrli

log = logging.getLogger(__name__)

WEB_XML = "/usr/lib/loginsight/application/etc/3rd_config/web.xml"
NETWORKD_ETH0 = "/etc/systemd/network/10-eth0.network"

# ---------------------------------------------------------------------------
# Read helpers backed by the REST cluster API (auth'd but no filesystem)
# ---------------------------------------------------------------------------

_CLUSTER_NODES = "/api/v2/cluster/nodes"


[docs] def get_cluster_nodes(opts, profile=None): """Return ``/api/v2/cluster/nodes`` verbatim. Each node dict includes ``dnsServers`` (space-separated IPv4 + IPv6), ``gateway``, ``netmask``, ``ip`` — all read-only. """ return vrli.api_get(opts, _CLUSTER_NODES, profile=profile).get("nodes", []) or []
[docs] def get_dns_servers(opts, profile=None): """Return the IPv4 DNS servers reported by the API for the primary node.""" for node in get_cluster_nodes(opts, profile=profile): if node.get("isPrimary"): return _split_ipv4(node.get("dnsServers", "")) # No primary flag on a single-node deployment — take the first node. nodes = get_cluster_nodes(opts, profile=profile) if nodes: return _split_ipv4(nodes[0].get("dnsServers", "")) return []
def _split_ipv4(dns_field): """Extract IPv4 addresses from the space-separated ``dnsServers`` field.""" if not dns_field: return [] ipv4_re = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$") return [addr for addr in dns_field.split() if ipv4_re.match(addr)] # --------------------------------------------------------------------------- # Session inactivity timeout — SSH read/write on web.xml # ---------------------------------------------------------------------------
[docs] def get_session_timeout(opts, profile=None): """Return the current session inactivity timeout in seconds. Reads ``<session-timeout>`` from ``web.xml`` (which stores minutes) over SSH and multiplies by 60. Requires root SSH access. """ ssh_cfg = vrli.get_ssh_config(opts, profile=profile) rc, out, err = ssh_util.run( ssh_cfg, f"grep -oP '(?<=<session-timeout>)\\d+(?=</session-timeout>)' {WEB_XML}" ) if rc != 0 or not out.strip(): raise RuntimeError( f"failed to read session-timeout from {WEB_XML}: rc={rc} err={err.strip()!r}" ) minutes = int(out.strip().splitlines()[0]) return minutes * 60
[docs] def set_session_timeout(opts, seconds, profile=None): """Rewrite ``<session-timeout>`` in ``web.xml`` and restart the Jetty daemon. *seconds* is rounded down to whole minutes (Jetty stores minutes). A restart of the ``loginsight`` service is triggered because the web descriptor is only read at daemon startup. """ minutes = max(1, int(seconds) // 60) ssh_cfg = vrli.get_ssh_config(opts, profile=profile) # sed the value in place, then restart. Use a marker-safe sed pattern. sed = ( f"sed -i -E 's|<session-timeout>[0-9]+</session-timeout>|" f"<session-timeout>{minutes}</session-timeout>|' {WEB_XML}" ) rc, _out, err = ssh_util.run(ssh_cfg, sed) if rc != 0: raise RuntimeError( f"failed to rewrite session-timeout in {WEB_XML}: rc={rc} err={err.strip()!r}" ) # Verify. verify_rc, verify_out, verify_err = ssh_util.run( ssh_cfg, f"grep -oP '(?<=<session-timeout>)\\d+(?=</session-timeout>)' {WEB_XML}" ) if verify_rc != 0 or verify_out.strip().splitlines()[0:1] != [str(minutes)]: raise RuntimeError( f"session-timeout write verification failed: rc={verify_rc} out={verify_out!r} " f"err={verify_err.strip()!r}" ) # Restart loginsight so Jetty re-reads the descriptor. Backgrounded # so the SSH channel closes cleanly before the API cycles. ssh_util.run(ssh_cfg, "systemctl restart loginsight &", timeout=15) return {"session_timeout_seconds": minutes * 60, "restart_requested": True}
# --------------------------------------------------------------------------- # IPv4 DNS — SSH read/write on the systemd-networkd unit # --------------------------------------------------------------------------- def _read_networkd_dns(ssh_cfg): """Return the current IPv4 DNS= entries from 10-eth0.network.""" _rc, out, _err = ssh_util.run(ssh_cfg, f"grep -E '^DNS=' {NETWORKD_ETH0} || true") servers = [] for line in out.splitlines(): val = line.split("=", 1)[1].strip() if re.match(r"^\d{1,3}(?:\.\d{1,3}){3}$", val): servers.append(val) return servers
[docs] def get_dns_servers_from_appliance(opts, profile=None): """SSH-read of the configured IPv4 nameservers. Prefer :func:`get_dns_servers` (REST) for read-only queries; this variant is exposed for parity with :func:`set_dns_servers` and for verification after a write. """ ssh_cfg = vrli.get_ssh_config(opts, profile=profile) return _read_networkd_dns(ssh_cfg)
[docs] def set_dns_servers(opts, servers, profile=None): """Set the IPv4 DNS servers by rewriting 10-eth0.network. Existing IPv6 ``DNS=`` lines (if any) are preserved; only IPv4 entries are replaced. Applies the change with ``networkctl reload`` and ``systemctl restart systemd-resolved``. """ for addr in servers: if not re.match(r"^\d{1,3}(?:\.\d{1,3}){3}$", addr): raise ValueError(f"not an IPv4 address: {addr!r}") ssh_cfg = vrli.get_ssh_config(opts, profile=profile) # Strip every existing IPv4 DNS= line, then append the desired set. # printf with shell-splat quoting is fragile — use single-quoted # DNS= tokens inside an ``echo`` loop instead. ipv4_line = r"^DNS=[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$" lines = " ".join(f"'DNS={a}'" for a in servers) cmd = ( f"cp {NETWORKD_ETH0} {NETWORKD_ETH0}.bak && " f"sed -i -E '/{ipv4_line}/d' {NETWORKD_ETH0} && " f'for l in {lines}; do echo "$l" >> {NETWORKD_ETH0}; done && ' f"networkctl reload && systemctl restart systemd-resolved" ) rc, out, err = ssh_util.run(ssh_cfg, cmd, timeout=60) if rc != 0: raise RuntimeError( f"failed to rewrite DNS in {NETWORKD_ETH0}: rc={rc} out={out.strip()!r} " f"err={err.strip()!r}" ) return {"dns_servers": list(servers), "applied": True}