mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
Use Powershell instead of netsh for firewall settings
This commit is contained in:
parent
5028305cd3
commit
344a3d8c2f
4 changed files with 363 additions and 181 deletions
2
changelog/61534.fixed.md
Normal file
2
changelog/61534.fixed.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Fixed the win_lgpo_netsh salt util to handle non-English systems. This was a
|
||||||
|
rewrite to use PowerShell instead of netsh to make the changes on the system
|
|
@ -6,16 +6,24 @@ A salt util for modifying firewall settings.
|
||||||
|
|
||||||
This util allows you to modify firewall settings in the local group policy in
|
This util allows you to modify firewall settings in the local group policy in
|
||||||
addition to the normal firewall settings. Parameters are taken from the
|
addition to the normal firewall settings. Parameters are taken from the
|
||||||
netsh advfirewall prompt.
|
netsh advfirewall prompt. This utility has been adapted to use powershell
|
||||||
|
instead of the ``netsh`` command to make it compatible with non-English systems.
|
||||||
|
It maintains the ``netsh`` commands and parameters, but it is using powershell
|
||||||
|
under the hood.
|
||||||
|
|
||||||
|
.. versionchanged:: 3008.0
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
More information can be found in the advfirewall context in netsh. This can
|
More information can be found in the advfirewall context in netsh. This can
|
||||||
be access by opening a netsh prompt. At a command prompt type the following:
|
be accessed by opening a netsh prompt. At a command prompt type the
|
||||||
|
following:
|
||||||
|
|
||||||
c:\>netsh
|
.. code-block:: powershell
|
||||||
netsh>advfirewall
|
|
||||||
netsh advfirewall>set help
|
c:\>netsh
|
||||||
netsh advfirewall>set domain help
|
netsh>advfirewall
|
||||||
|
netsh advfirewall>set help
|
||||||
|
netsh advfirewall>set domain help
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
||||||
|
@ -66,19 +74,40 @@ Usage:
|
||||||
store='lgpo')
|
store='lgpo')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import socket
|
import socket
|
||||||
import tempfile
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
import salt.modules.cmdmod
|
import salt.utils.platform
|
||||||
|
import salt.utils.win_pwsh
|
||||||
from salt.exceptions import CommandExecutionError
|
from salt.exceptions import CommandExecutionError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
ON_OFF = {
|
||||||
__hostname__ = socket.gethostname()
|
0: "OFF",
|
||||||
|
1: "ON",
|
||||||
|
2: "NotConfigured",
|
||||||
|
"off": "False",
|
||||||
|
"on": "True",
|
||||||
|
"notconfigured": "NotConfigured",
|
||||||
|
}
|
||||||
|
|
||||||
|
ENABLE_DISABLE = {
|
||||||
|
0: "Disable",
|
||||||
|
1: "Enable",
|
||||||
|
2: "NotConfigured",
|
||||||
|
"disable": 0,
|
||||||
|
"enable": 1,
|
||||||
|
"notconfigured": 2,
|
||||||
|
}
|
||||||
|
OUTBOUND = {
|
||||||
|
0: "NotConfigured",
|
||||||
|
2: "AllowOutbound",
|
||||||
|
4: "BlockOutbound",
|
||||||
|
"notconfigured": "NotConfigured",
|
||||||
|
"allowoutbound": "Allow",
|
||||||
|
"blockoutbound": "Block",
|
||||||
|
}
|
||||||
|
|
||||||
__virtualname__ = "netsh"
|
__virtualname__ = "netsh"
|
||||||
|
__hostname__ = socket.gethostname()
|
||||||
|
|
||||||
|
|
||||||
# Although utils are often directly imported, it is also possible to use the
|
# Although utils are often directly imported, it is also possible to use the
|
||||||
|
@ -93,60 +122,42 @@ def __virtual__():
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
|
|
||||||
|
|
||||||
def _netsh_file(content):
|
def _get_inbound_text(rule, action):
|
||||||
"""
|
"""
|
||||||
helper function to get the results of ``netsh -f content.txt``
|
The "Inbound connections" setting is a combination of 2 parameters:
|
||||||
|
|
||||||
Running ``netsh`` will drop you into a ``netsh`` prompt where you can issue
|
- AllowInboundRules
|
||||||
``netsh`` commands. You can put a series of commands in an external file and
|
- DefaultInboundAction
|
||||||
run them as if from a ``netsh`` prompt using the ``-f`` switch. That's what
|
|
||||||
this function does.
|
|
||||||
|
|
||||||
Args:
|
The settings are as follows:
|
||||||
|
|
||||||
content (str):
|
Rules Action
|
||||||
The contents of the file that will be run by the ``netsh -f``
|
2 2 AllowInbound
|
||||||
command
|
2 4 BlockInbound
|
||||||
|
0 4 BlockInboundAlways
|
||||||
Returns:
|
2 0 NotConfigured
|
||||||
str: The text returned by the netsh command
|
|
||||||
"""
|
"""
|
||||||
with tempfile.NamedTemporaryFile(
|
settings = {
|
||||||
mode="w", prefix="salt-", suffix=".netsh", delete=False, encoding="utf-8"
|
0: {
|
||||||
) as fp:
|
4: "BlockInboundAlways",
|
||||||
fp.write(content)
|
},
|
||||||
try:
|
2: {
|
||||||
log.debug("%s:\n%s", fp.name, content)
|
0: "NotConfigured",
|
||||||
return salt.modules.cmdmod.run(f"netsh -f {fp.name}", python_shell=True)
|
2: "AllowInbound",
|
||||||
finally:
|
4: "BlockInbound",
|
||||||
os.remove(fp.name)
|
},
|
||||||
|
}
|
||||||
|
return settings[rule][action]
|
||||||
|
|
||||||
|
|
||||||
def _netsh_command(command, store):
|
def _get_inbound_settings(text):
|
||||||
if store.lower() not in ("local", "lgpo"):
|
settings = {
|
||||||
raise ValueError(f"Incorrect store: {store}")
|
"allowinbound": (2, 2),
|
||||||
# set the store for local or lgpo
|
"blockinbound": (2, 4),
|
||||||
if store.lower() == "local":
|
"blockinboundalways": (0, 4),
|
||||||
netsh_script = dedent(
|
"notconfigured": (2, 0),
|
||||||
"""\
|
}
|
||||||
advfirewall
|
return settings[text.lower()]
|
||||||
set store local
|
|
||||||
{}
|
|
||||||
""".format(
|
|
||||||
command
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
netsh_script = dedent(
|
|
||||||
"""\
|
|
||||||
advfirewall
|
|
||||||
set store gpo = {}
|
|
||||||
{}
|
|
||||||
""".format(
|
|
||||||
__hostname__, command
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return _netsh_file(content=netsh_script).splitlines()
|
|
||||||
|
|
||||||
|
|
||||||
def get_settings(profile, section, store="local"):
|
def get_settings(profile, section, store="local"):
|
||||||
|
@ -195,70 +206,54 @@ def get_settings(profile, section, store="local"):
|
||||||
raise ValueError(f"Incorrect section: {section}")
|
raise ValueError(f"Incorrect section: {section}")
|
||||||
if store.lower() not in ("local", "lgpo"):
|
if store.lower() not in ("local", "lgpo"):
|
||||||
raise ValueError(f"Incorrect store: {store}")
|
raise ValueError(f"Incorrect store: {store}")
|
||||||
command = f"show {profile}profile {section}"
|
|
||||||
# run it
|
|
||||||
results = _netsh_command(command=command, store=store)
|
|
||||||
# sample output:
|
|
||||||
# Domain Profile Settings:
|
|
||||||
# ----------------------------------------------------------------------
|
|
||||||
# LocalFirewallRules N/A (GPO-store only)
|
|
||||||
# LocalConSecRules N/A (GPO-store only)
|
|
||||||
# InboundUserNotification Disable
|
|
||||||
# RemoteManagement Disable
|
|
||||||
# UnicastResponseToMulticast Enable
|
|
||||||
|
|
||||||
# if it's less than 3 lines it failed
|
# Build the powershell command
|
||||||
if len(results) < 3:
|
cmd = ["Get-NetFirewallProfile"]
|
||||||
raise CommandExecutionError(f"Invalid results: {results}")
|
if profile:
|
||||||
ret = {}
|
cmd.append(profile)
|
||||||
# Skip the first 2 lines. Add everything else to a dictionary
|
if store and store.lower() == "lgpo":
|
||||||
for line in results[3:]:
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
ret.update(dict(list(zip(*[iter(re.split(r"\s{2,}", line))] * 2))))
|
|
||||||
|
|
||||||
# Remove spaces from the values so that `Not Configured` is detected
|
# Run the command
|
||||||
# correctly
|
settings = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
for item in ret:
|
|
||||||
ret[item] = ret[item].replace(" ", "")
|
|
||||||
|
|
||||||
# special handling for firewallpolicy
|
# A successful run should return a dictionary
|
||||||
if section == "firewallpolicy":
|
if not settings:
|
||||||
inbound, outbound = ret["Firewall Policy"].split(",")
|
raise CommandExecutionError("LGPO NETSH: An unknown error occurred")
|
||||||
return {"Inbound": inbound, "Outbound": outbound}
|
|
||||||
|
|
||||||
return ret
|
# Remove the junk
|
||||||
|
for setting in list(settings.keys()):
|
||||||
|
if setting.startswith("Cim"):
|
||||||
|
settings.pop(setting)
|
||||||
|
|
||||||
|
# Make it look like netsh output
|
||||||
|
ret_settings = {
|
||||||
|
"firewallpolicy": {
|
||||||
|
"Inbound": _get_inbound_text(
|
||||||
|
settings["AllowInboundRules"], settings["DefaultInboundAction"]
|
||||||
|
),
|
||||||
|
"Outbound": OUTBOUND[settings["DefaultOutboundAction"]],
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"State": ON_OFF[settings["Enabled"]],
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"FileName": settings["LogFileName"],
|
||||||
|
"LogAllowedConnections": ENABLE_DISABLE[settings["LogAllowed"]],
|
||||||
|
"LogDroppedConnections": ENABLE_DISABLE[settings["LogBlocked"]],
|
||||||
|
"MaxFileSize": settings["LogMaxSizeKilobytes"],
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"InboundUserNotification": ENABLE_DISABLE[settings["NotifyOnListen"]],
|
||||||
|
"LocalConSecRules": ENABLE_DISABLE[settings["AllowLocalIPsecRules"]],
|
||||||
|
"LocalFirewallRules": ENABLE_DISABLE[settings["AllowLocalFirewallRules"]],
|
||||||
|
"UnicastResponseToMulticast": ENABLE_DISABLE[
|
||||||
|
settings["AllowUnicastResponseToMulticast"]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def get_all_settings(profile, store="local"):
|
return ret_settings[section.lower()]
|
||||||
"""
|
|
||||||
Gets all the properties for the specified profile in the specified store
|
|
||||||
|
|
||||||
Args:
|
|
||||||
|
|
||||||
profile (str):
|
|
||||||
The firewall profile to query. Valid options are:
|
|
||||||
|
|
||||||
- domain
|
|
||||||
- public
|
|
||||||
- private
|
|
||||||
|
|
||||||
store (str):
|
|
||||||
The store to use. This is either the local firewall policy or the
|
|
||||||
policy defined by local group policy. Valid options are:
|
|
||||||
|
|
||||||
- lgpo
|
|
||||||
- local
|
|
||||||
|
|
||||||
Default is ``local``
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: A dictionary containing the specified settings
|
|
||||||
"""
|
|
||||||
ret = dict()
|
|
||||||
ret.update(get_settings(profile=profile, section="state", store=store))
|
|
||||||
ret.update(get_settings(profile=profile, section="firewallpolicy", store=store))
|
|
||||||
ret.update(get_settings(profile=profile, section="settings", store=store))
|
|
||||||
ret.update(get_settings(profile=profile, section="logging", store=store))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_profiles(store="local"):
|
def get_all_profiles(store="local"):
|
||||||
|
@ -286,6 +281,82 @@ def get_all_profiles(store="local"):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_settings(profile, store="local"):
|
||||||
|
"""
|
||||||
|
Gets all the properties for the specified profile in the specified store
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
profile (str):
|
||||||
|
The firewall profile to query. Valid options are:
|
||||||
|
|
||||||
|
- domain
|
||||||
|
- public
|
||||||
|
- private
|
||||||
|
|
||||||
|
store (str):
|
||||||
|
The store to use. This is either the local firewall policy or the
|
||||||
|
policy defined by local group policy. Valid options are:
|
||||||
|
|
||||||
|
- lgpo
|
||||||
|
- local
|
||||||
|
|
||||||
|
Default is ``local``
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the specified settings
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CommandExecutionError: If an error occurs
|
||||||
|
ValueError: If the parameters are incorrect
|
||||||
|
"""
|
||||||
|
# validate input
|
||||||
|
if profile.lower() not in ("domain", "public", "private"):
|
||||||
|
raise ValueError(f"Incorrect profile: {profile}")
|
||||||
|
if store.lower() not in ("local", "lgpo"):
|
||||||
|
raise ValueError(f"Incorrect store: {store}")
|
||||||
|
|
||||||
|
# Build the powershell command
|
||||||
|
cmd = ["Get-NetFirewallProfile"]
|
||||||
|
if profile:
|
||||||
|
cmd.append(profile)
|
||||||
|
if store and store.lower() == "lgpo":
|
||||||
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
|
|
||||||
|
# Run the command
|
||||||
|
settings = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
|
|
||||||
|
# A successful run should return a dictionary
|
||||||
|
if not settings:
|
||||||
|
raise CommandExecutionError("LGPO NETSH: An unknown error occurred")
|
||||||
|
|
||||||
|
# Remove the junk
|
||||||
|
for setting in list(settings.keys()):
|
||||||
|
if setting.startswith("Cim"):
|
||||||
|
settings.pop(setting)
|
||||||
|
|
||||||
|
# Make it look like netsh output
|
||||||
|
ret_settings = {
|
||||||
|
"FileName": settings["LogFileName"],
|
||||||
|
"Inbound": _get_inbound_text(
|
||||||
|
settings["AllowInboundRules"], settings["DefaultInboundAction"]
|
||||||
|
),
|
||||||
|
"InboundUserNotification": ENABLE_DISABLE[settings["NotifyOnListen"]],
|
||||||
|
"LocalConSecRules": ENABLE_DISABLE[settings["AllowLocalIPsecRules"]],
|
||||||
|
"LocalFirewallRules": ENABLE_DISABLE[settings["AllowLocalFirewallRules"]],
|
||||||
|
"LogAllowedConnections": ENABLE_DISABLE[settings["LogAllowed"]],
|
||||||
|
"LogDroppedConnections": ENABLE_DISABLE[settings["LogBlocked"]],
|
||||||
|
"MaxFileSize": settings["LogMaxSizeKilobytes"],
|
||||||
|
"Outbound": OUTBOUND[settings["DefaultOutboundAction"]],
|
||||||
|
"State": ON_OFF[settings["Enabled"]],
|
||||||
|
"UnicastResponseToMulticast": ON_OFF[
|
||||||
|
settings["AllowUnicastResponseToMulticast"]
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret_settings
|
||||||
|
|
||||||
|
|
||||||
def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
|
def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
|
||||||
"""
|
"""
|
||||||
Set the firewall inbound/outbound settings for the specified profile and
|
Set the firewall inbound/outbound settings for the specified profile and
|
||||||
|
@ -307,7 +378,7 @@ def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
|
||||||
- blockinbound
|
- blockinbound
|
||||||
- blockinboundalways
|
- blockinboundalways
|
||||||
- allowinbound
|
- allowinbound
|
||||||
- notconfigured
|
- notconfigured <=== lgpo only
|
||||||
|
|
||||||
Default is ``None``
|
Default is ``None``
|
||||||
|
|
||||||
|
@ -317,7 +388,7 @@ def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
|
||||||
|
|
||||||
- allowoutbound
|
- allowoutbound
|
||||||
- blockoutbound
|
- blockoutbound
|
||||||
- notconfigured
|
- notconfigured <=== lgpo only
|
||||||
|
|
||||||
Default is ``None``
|
Default is ``None``
|
||||||
|
|
||||||
|
@ -355,21 +426,34 @@ def set_firewall_settings(profile, inbound=None, outbound=None, store="local"):
|
||||||
raise ValueError(f"Incorrect outbound value: {outbound}")
|
raise ValueError(f"Incorrect outbound value: {outbound}")
|
||||||
if not inbound and not outbound:
|
if not inbound and not outbound:
|
||||||
raise ValueError("Must set inbound or outbound")
|
raise ValueError("Must set inbound or outbound")
|
||||||
|
if store == "local":
|
||||||
|
if inbound and inbound.lower() == "notconfigured":
|
||||||
|
msg = "Cannot set local inbound policies as NotConfigured"
|
||||||
|
raise CommandExecutionError(msg)
|
||||||
|
if outbound and outbound.lower() == "notconfigured":
|
||||||
|
msg = "Cannot set local outbound policies as NotConfigured"
|
||||||
|
raise CommandExecutionError(msg)
|
||||||
|
|
||||||
# You have to specify inbound and outbound setting at the same time
|
# Build the powershell command
|
||||||
# If you're only specifying one, you have to get the current setting for the
|
cmd = ["Set-NetFirewallProfile"]
|
||||||
# other
|
if profile:
|
||||||
if not inbound or not outbound:
|
cmd.append(profile)
|
||||||
ret = get_settings(profile=profile, section="firewallpolicy", store=store)
|
if store and store.lower() == "lgpo":
|
||||||
if not inbound:
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
inbound = ret["Inbound"]
|
|
||||||
if not outbound:
|
|
||||||
outbound = ret["Outbound"]
|
|
||||||
|
|
||||||
command = f"set {profile}profile firewallpolicy {inbound},{outbound}"
|
# Get inbound settings
|
||||||
|
if inbound:
|
||||||
|
in_rule, in_action = _get_inbound_settings(inbound.lower())
|
||||||
|
cmd.extend(["-AllowInboundRules", in_rule, "-DefaultInboundAction", in_action])
|
||||||
|
|
||||||
results = _netsh_command(command=command, store=store)
|
if outbound:
|
||||||
|
out_rule = OUTBOUND[outbound.lower()]
|
||||||
|
cmd.extend(["-DefaultOutboundAction", out_rule])
|
||||||
|
|
||||||
|
# Run the command
|
||||||
|
results = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
|
|
||||||
|
# A successful run should return an empty list
|
||||||
if results:
|
if results:
|
||||||
raise CommandExecutionError(f"An error occurred: {results}")
|
raise CommandExecutionError(f"An error occurred: {results}")
|
||||||
|
|
||||||
|
@ -442,6 +526,10 @@ def set_logging_settings(profile, setting, value, store="local"):
|
||||||
# Input validation
|
# Input validation
|
||||||
if profile.lower() not in ("domain", "public", "private"):
|
if profile.lower() not in ("domain", "public", "private"):
|
||||||
raise ValueError(f"Incorrect profile: {profile}")
|
raise ValueError(f"Incorrect profile: {profile}")
|
||||||
|
if store == "local":
|
||||||
|
if str(value).lower() == "notconfigured":
|
||||||
|
msg = "Cannot set local policies as NotConfigured"
|
||||||
|
raise CommandExecutionError(msg)
|
||||||
if setting.lower() not in (
|
if setting.lower() not in (
|
||||||
"allowedconnections",
|
"allowedconnections",
|
||||||
"droppedconnections",
|
"droppedconnections",
|
||||||
|
@ -449,13 +537,21 @@ def set_logging_settings(profile, setting, value, store="local"):
|
||||||
"maxfilesize",
|
"maxfilesize",
|
||||||
):
|
):
|
||||||
raise ValueError(f"Incorrect setting: {setting}")
|
raise ValueError(f"Incorrect setting: {setting}")
|
||||||
|
settings = {"filename": ["-LogFileName", value]}
|
||||||
if setting.lower() in ("allowedconnections", "droppedconnections"):
|
if setting.lower() in ("allowedconnections", "droppedconnections"):
|
||||||
if value.lower() not in ("enable", "disable", "notconfigured"):
|
if value.lower() not in ("enable", "disable", "notconfigured"):
|
||||||
raise ValueError(f"Incorrect value: {value}")
|
raise ValueError(f"Incorrect value: {value}")
|
||||||
|
settings.update(
|
||||||
|
{
|
||||||
|
"allowedconnections": ["-LogAllowed", ENABLE_DISABLE[value.lower()]],
|
||||||
|
"droppedconnections": ["-LogBlocked", ENABLE_DISABLE[value.lower()]],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Consider adding something like the following to validate filename
|
# TODO: Consider adding something like the following to validate filename
|
||||||
# https://stackoverflow.com/questions/9532499/check-whether-a-path-is-valid-in-python-without-creating-a-file-at-the-paths-ta
|
# https://stackoverflow.com/questions/9532499/check-whether-a-path-is-valid-in-python-without-creating-a-file-at-the-paths-ta
|
||||||
if setting.lower() == "maxfilesize":
|
if setting.lower() == "maxfilesize":
|
||||||
if value.lower() != "notconfigured":
|
if str(value).lower() != "notconfigured":
|
||||||
# Must be a number between 1 and 32767
|
# Must be a number between 1 and 32767
|
||||||
try:
|
try:
|
||||||
int(value)
|
int(value)
|
||||||
|
@ -463,9 +559,18 @@ def set_logging_settings(profile, setting, value, store="local"):
|
||||||
raise ValueError(f"Incorrect value: {value}")
|
raise ValueError(f"Incorrect value: {value}")
|
||||||
if not 1 <= int(value) <= 32767:
|
if not 1 <= int(value) <= 32767:
|
||||||
raise ValueError(f"Incorrect value: {value}")
|
raise ValueError(f"Incorrect value: {value}")
|
||||||
# Run the command
|
settings.update({"maxfilesize": ["-LogMaxSizeKilobytes", value]})
|
||||||
command = f"set {profile}profile logging {setting} {value}"
|
|
||||||
results = _netsh_command(command=command, store=store)
|
# Build the powershell command
|
||||||
|
cmd = ["Set-NetFirewallProfile"]
|
||||||
|
if profile:
|
||||||
|
cmd.append(profile)
|
||||||
|
if store and store.lower() == "lgpo":
|
||||||
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
|
|
||||||
|
cmd.extend(settings[setting.lower()])
|
||||||
|
|
||||||
|
results = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
|
|
||||||
# A successful run should return an empty list
|
# A successful run should return an empty list
|
||||||
if results:
|
if results:
|
||||||
|
@ -493,7 +598,6 @@ def set_settings(profile, setting, value, store="local"):
|
||||||
- localfirewallrules
|
- localfirewallrules
|
||||||
- localconsecrules
|
- localconsecrules
|
||||||
- inboundusernotification
|
- inboundusernotification
|
||||||
- remotemanagement
|
|
||||||
- unicastresponsetomulticast
|
- unicastresponsetomulticast
|
||||||
|
|
||||||
value (str):
|
value (str):
|
||||||
|
@ -526,16 +630,42 @@ def set_settings(profile, setting, value, store="local"):
|
||||||
"localfirewallrules",
|
"localfirewallrules",
|
||||||
"localconsecrules",
|
"localconsecrules",
|
||||||
"inboundusernotification",
|
"inboundusernotification",
|
||||||
"remotemanagement",
|
|
||||||
"unicastresponsetomulticast",
|
"unicastresponsetomulticast",
|
||||||
):
|
):
|
||||||
raise ValueError(f"Incorrect setting: {setting}")
|
raise ValueError(f"Incorrect setting: {setting}")
|
||||||
if value.lower() not in ("enable", "disable", "notconfigured"):
|
if value.lower() not in ("enable", "disable", "notconfigured"):
|
||||||
raise ValueError(f"Incorrect value: {value}")
|
raise ValueError(f"Incorrect value: {value}")
|
||||||
|
if setting.lower() in ["localfirewallrules", "localconsecrules"]:
|
||||||
|
if store.lower() != "lgpo":
|
||||||
|
msg = f"{setting} can only be set using Group Policy"
|
||||||
|
raise CommandExecutionError(msg)
|
||||||
|
if setting.lower() == "inboundusernotification" and store.lower() != "lgpo":
|
||||||
|
if value.lower() == "notconfigured":
|
||||||
|
msg = "NotConfigured is only valid when setting group policy"
|
||||||
|
raise CommandExecutionError(msg)
|
||||||
|
|
||||||
# Run the command
|
# Build the powershell command
|
||||||
command = f"set {profile}profile settings {setting} {value}"
|
cmd = ["Set-NetFirewallProfile"]
|
||||||
results = _netsh_command(command=command, store=store)
|
if profile:
|
||||||
|
cmd.append(profile)
|
||||||
|
if store and store.lower() == "lgpo":
|
||||||
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
"localfirewallrules": [
|
||||||
|
"-AllowLocalFirewallRules",
|
||||||
|
ENABLE_DISABLE[value.lower()],
|
||||||
|
],
|
||||||
|
"localconsecrules": ["-AllowLocalIPsecRules", ENABLE_DISABLE[value.lower()]],
|
||||||
|
"inboundusernotification": ["-NotifyOnListen", ENABLE_DISABLE[value.lower()]],
|
||||||
|
"unicastresponsetomulticast": [
|
||||||
|
"-AllowUnicastResponseToMulticast",
|
||||||
|
ENABLE_DISABLE[value.lower()],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
cmd.extend(settings[setting.lower()])
|
||||||
|
|
||||||
|
results = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
|
|
||||||
# A successful run should return an empty list
|
# A successful run should return an empty list
|
||||||
if results:
|
if results:
|
||||||
|
@ -546,7 +676,7 @@ def set_settings(profile, setting, value, store="local"):
|
||||||
|
|
||||||
def set_state(profile, state, store="local"):
|
def set_state(profile, state, store="local"):
|
||||||
"""
|
"""
|
||||||
Configure the firewall state.
|
Enable or disable the firewall profile.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
||||||
|
@ -583,12 +713,22 @@ def set_state(profile, state, store="local"):
|
||||||
# Input validation
|
# Input validation
|
||||||
if profile.lower() not in ("domain", "public", "private"):
|
if profile.lower() not in ("domain", "public", "private"):
|
||||||
raise ValueError(f"Incorrect profile: {profile}")
|
raise ValueError(f"Incorrect profile: {profile}")
|
||||||
if state.lower() not in ("on", "off", "notconfigured"):
|
if not isinstance(state, bool):
|
||||||
raise ValueError(f"Incorrect state: {state}")
|
if state.lower() not in ("on", "off", "notconfigured"):
|
||||||
|
raise ValueError(f"Incorrect state: {state}")
|
||||||
|
else:
|
||||||
|
state = "On" if state else "Off"
|
||||||
|
|
||||||
# Run the command
|
# Build the powershell command
|
||||||
command = f"set {profile}profile state {state}"
|
cmd = ["Set-NetFirewallProfile"]
|
||||||
results = _netsh_command(command=command, store=store)
|
if profile:
|
||||||
|
cmd.append(profile)
|
||||||
|
if store and store.lower() == "lgpo":
|
||||||
|
cmd.extend(["-PolicyStore", "localhost"])
|
||||||
|
|
||||||
|
cmd.extend(["-Enabled", ON_OFF[state.lower()]])
|
||||||
|
|
||||||
|
results = salt.utils.win_pwsh.run_dict(cmd)
|
||||||
|
|
||||||
# A successful run should return an empty list
|
# A successful run should return an empty list
|
||||||
if results:
|
if results:
|
||||||
|
|
67
salt/utils/win_pwsh.py
Normal file
67
salt/utils/win_pwsh.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import salt.modules.cmdmod
|
||||||
|
import salt.utils.json
|
||||||
|
import salt.utils.platform
|
||||||
|
from salt.exceptions import CommandExecutionError
|
||||||
|
|
||||||
|
__virtualname__ = "win_pwsh"
|
||||||
|
|
||||||
|
|
||||||
|
def __virtual__():
|
||||||
|
"""
|
||||||
|
Only load if windows
|
||||||
|
"""
|
||||||
|
if not salt.utils.platform.is_windows():
|
||||||
|
return False, "This utility will only run on Windows"
|
||||||
|
|
||||||
|
return __virtualname__
|
||||||
|
|
||||||
|
|
||||||
|
def run_dict(cmd, cwd=None):
|
||||||
|
"""
|
||||||
|
Execute the powershell command and return the data as a dictionary
|
||||||
|
|
||||||
|
.. versionadded:: 3006.9
|
||||||
|
|
||||||
|
Args:
|
||||||
|
|
||||||
|
cmd (str,list): The powershell command to run
|
||||||
|
|
||||||
|
cwd (str): The current working directory
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary containing the output of the powershell command
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
CommandExecutionError:
|
||||||
|
If an error is encountered or the command does not complete
|
||||||
|
successfully
|
||||||
|
"""
|
||||||
|
if isinstance(cmd, list):
|
||||||
|
cmd = " ".join(map(str, cmd))
|
||||||
|
if "convertto-json" not in cmd.lower():
|
||||||
|
cmd = f"{cmd} | ConvertTo-Json"
|
||||||
|
if "progresspreference" not in cmd.lower():
|
||||||
|
cmd = f"$ProgressPreference = 'SilentlyContinue'; {cmd}"
|
||||||
|
ret = salt.modules.cmdmod.run_all(cmd=cmd, shell="powershell", cwd=cwd)
|
||||||
|
|
||||||
|
if "pid" in ret:
|
||||||
|
del ret["pid"]
|
||||||
|
|
||||||
|
if ret.get("stderr", ""):
|
||||||
|
error = ret["stderr"].splitlines()[0]
|
||||||
|
raise CommandExecutionError(error, info=ret)
|
||||||
|
|
||||||
|
if "retcode" not in ret or ret["retcode"] != 0:
|
||||||
|
# run_all logs an error to log.error, fail hard back to the user
|
||||||
|
raise CommandExecutionError("Issue executing PowerShell cmd", info=ret)
|
||||||
|
|
||||||
|
# Sometimes Powershell returns an empty string, which isn't valid JSON
|
||||||
|
if ret["stdout"] == "":
|
||||||
|
ret["stdout"] = "{}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
ret = salt.utils.json.loads(ret["stdout"], strict=False)
|
||||||
|
except ValueError:
|
||||||
|
raise CommandExecutionError("No JSON results from PowerShell", info=ret)
|
||||||
|
|
||||||
|
return ret
|
|
@ -63,7 +63,6 @@ def test_get_settings_settings_local():
|
||||||
assert "InboundUserNotification" in ret
|
assert "InboundUserNotification" in ret
|
||||||
assert "LocalConSecRules" in ret
|
assert "LocalConSecRules" in ret
|
||||||
assert "LocalFirewallRules" in ret
|
assert "LocalFirewallRules" in ret
|
||||||
assert "RemoteManagement" in ret
|
|
||||||
assert "UnicastResponseToMulticast" in ret
|
assert "UnicastResponseToMulticast" in ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +73,6 @@ def test_get_settings_settings_lgpo():
|
||||||
assert "InboundUserNotification" in ret
|
assert "InboundUserNotification" in ret
|
||||||
assert "LocalConSecRules" in ret
|
assert "LocalConSecRules" in ret
|
||||||
assert "LocalFirewallRules" in ret
|
assert "LocalFirewallRules" in ret
|
||||||
assert "RemoteManagement" in ret
|
|
||||||
assert "UnicastResponseToMulticast" in ret
|
assert "UnicastResponseToMulticast" in ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,7 +97,6 @@ def test_get_all_settings_local():
|
||||||
assert "InboundUserNotification" in ret
|
assert "InboundUserNotification" in ret
|
||||||
assert "LocalConSecRules" in ret
|
assert "LocalConSecRules" in ret
|
||||||
assert "LocalFirewallRules" in ret
|
assert "LocalFirewallRules" in ret
|
||||||
assert "RemoteManagement" in ret
|
|
||||||
assert "UnicastResponseToMulticast" in ret
|
assert "UnicastResponseToMulticast" in ret
|
||||||
assert "State" in ret
|
assert "State" in ret
|
||||||
|
|
||||||
|
@ -115,7 +112,6 @@ def test_get_all_settings_lgpo():
|
||||||
assert "InboundUserNotification" in ret
|
assert "InboundUserNotification" in ret
|
||||||
assert "LocalConSecRules" in ret
|
assert "LocalConSecRules" in ret
|
||||||
assert "LocalFirewallRules" in ret
|
assert "LocalFirewallRules" in ret
|
||||||
assert "RemoteManagement" in ret
|
|
||||||
assert "UnicastResponseToMulticast" in ret
|
assert "UnicastResponseToMulticast" in ret
|
||||||
assert "State" in ret
|
assert "State" in ret
|
||||||
|
|
||||||
|
@ -356,7 +352,7 @@ def test_set_firewall_logging_maxfilesize_local():
|
||||||
new = win_lgpo_netsh.get_settings(
|
new = win_lgpo_netsh.get_settings(
|
||||||
profile="domain", section="logging", store="local"
|
profile="domain", section="logging", store="local"
|
||||||
)["MaxFileSize"]
|
)["MaxFileSize"]
|
||||||
assert new == "16384"
|
assert new == 16384
|
||||||
finally:
|
finally:
|
||||||
ret = win_lgpo_netsh.set_logging_settings(
|
ret = win_lgpo_netsh.set_logging_settings(
|
||||||
profile="domain", setting="maxfilesize", value=current, store="local"
|
profile="domain", setting="maxfilesize", value=current, store="local"
|
||||||
|
@ -491,32 +487,6 @@ def test_set_firewall_settings_notification_lgpo_notconfigured():
|
||||||
assert ret is True
|
assert ret is True
|
||||||
|
|
||||||
|
|
||||||
def test_set_firewall_settings_remotemgmt_local_enable():
|
|
||||||
current = win_lgpo_netsh.get_settings(
|
|
||||||
profile="domain", section="settings", store="local"
|
|
||||||
)["RemoteManagement"]
|
|
||||||
try:
|
|
||||||
ret = win_lgpo_netsh.set_settings(
|
|
||||||
profile="domain",
|
|
||||||
setting="remotemanagement",
|
|
||||||
value="enable",
|
|
||||||
store="local",
|
|
||||||
)
|
|
||||||
assert ret is True
|
|
||||||
new = win_lgpo_netsh.get_settings(
|
|
||||||
profile="domain", section="settings", store="local"
|
|
||||||
)["RemoteManagement"]
|
|
||||||
assert new == "Enable"
|
|
||||||
finally:
|
|
||||||
ret = win_lgpo_netsh.set_settings(
|
|
||||||
profile="domain",
|
|
||||||
setting="remotemanagement",
|
|
||||||
value=current,
|
|
||||||
store="local",
|
|
||||||
)
|
|
||||||
assert ret is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_firewall_settings_unicast_local_disable():
|
def test_set_firewall_settings_unicast_local_disable():
|
||||||
current = win_lgpo_netsh.get_settings(
|
current = win_lgpo_netsh.get_settings(
|
||||||
profile="domain", section="settings", store="local"
|
profile="domain", section="settings", store="local"
|
||||||
|
@ -566,13 +536,16 @@ def test_set_firewall_state_local_notconfigured():
|
||||||
profile="domain", section="state", store="local"
|
profile="domain", section="state", store="local"
|
||||||
)["State"]
|
)["State"]
|
||||||
try:
|
try:
|
||||||
pytest.raises(
|
ret = win_lgpo_netsh.set_state(
|
||||||
CommandExecutionError,
|
|
||||||
win_lgpo_netsh.set_state,
|
|
||||||
profile="domain",
|
profile="domain",
|
||||||
state="notconfigured",
|
state="notconfigured",
|
||||||
store="local",
|
store="local",
|
||||||
)
|
)
|
||||||
|
assert ret is True
|
||||||
|
new = win_lgpo_netsh.get_settings(
|
||||||
|
profile="domain", section="state", store="local"
|
||||||
|
)["State"]
|
||||||
|
assert new == "NotConfigured"
|
||||||
finally:
|
finally:
|
||||||
ret = win_lgpo_netsh.set_state(profile="domain", state=current, store="local")
|
ret = win_lgpo_netsh.set_state(profile="domain", state=current, store="local")
|
||||||
assert ret is True
|
assert ret is True
|
||||||
|
|
Loading…
Add table
Reference in a new issue