Source code for saltext.vcf.states.vcf_nsx_node_services
"""State module for NSX Manager node services.
Currently ships one verb, :func:`http_configured`, which enforces
``service_properties`` fields on ``/api/v1/node/services/http``. This is
the surface used to satisfy STIG 912 DoS-mitigation controls:
.. code-block:: yaml
nsx-http-rate-limits:
vcf_nsx_node_services.http_configured:
- client_api_rate_limit: 100
- client_api_concurrency_limit: 40
- global_api_concurrency_limit: 199
The endpoint is a singleton with total-replacement PUT semantics; the
state reads the current config, diffs only the caller-supplied fields,
and PUTs the merged document so unrelated fields (``redirect_host``,
``connection_timeout``, cipher config, …) are preserved.
"""
from saltext.vcf.clients import nsx_node_services as c
__virtualname__ = "vcf_nsx_node_services"
def __virtual__():
return __virtualname__
def _ret(name):
return {"name": name, "changes": {}, "result": True, "comment": ""}
[docs]
def http_configured(
name,
client_api_rate_limit=None,
client_api_concurrency_limit=None,
global_api_concurrency_limit=None,
connection_timeout=None,
redirect_host=None,
profile=None,
**extra,
):
"""Ensure the NSX HTTP service ``service_properties`` match the supplied fields.
Only the fields the caller passes are considered; ``None`` means
"don't touch". Fields already at the desired value are a no-op. If
any field differs, the state reads the full current config, overlays
the desired fields, and PUTs the merged document (the endpoint is
total-replacement).
"""
ret = _ret(name)
desired = dict(extra)
if client_api_rate_limit is not None:
desired["client_api_rate_limit"] = client_api_rate_limit
if client_api_concurrency_limit is not None:
desired["client_api_concurrency_limit"] = client_api_concurrency_limit
if global_api_concurrency_limit is not None:
desired["global_api_concurrency_limit"] = global_api_concurrency_limit
if connection_timeout is not None:
desired["connection_timeout"] = connection_timeout
if redirect_host is not None:
desired["redirect_host"] = redirect_host
if not desired:
ret["comment"] = "No HTTP service fields supplied; nothing to do"
return ret
current = c.http_get(__opts__, profile=profile) or {}
current_props = (current.get("service_properties") or {}) if isinstance(current, dict) else {}
diffs = {}
for key, want in desired.items():
have = current_props.get(key)
if have != want:
diffs[key] = {"old": have, "new": want}
if not diffs:
ret["comment"] = "NSX HTTP service already matches desired fields"
return ret
if __opts__.get("test"):
ret["result"] = None
ret["changes"] = diffs
ret["comment"] = f"NSX HTTP service would be updated: {sorted(diffs)}"
return ret
# Merge desired fields on top of the current config and PUT the whole
# document. The endpoint is a singleton with total-replacement PUT
# semantics — merging first is what keeps unrelated fields intact.
merged = dict(current)
merged_props = dict(current_props)
merged_props.update(desired)
merged["service_properties"] = merged_props
c.http_put(__opts__, merged, profile=profile)
ret["changes"] = diffs
ret["comment"] = f"NSX HTTP service updated: {sorted(diffs)}"
return ret