salt/utils/win_lgpo_netsh.py
# -*- coding: utf-8 -*-
r'''
A salt util for modifying firewall settings.
.. versionadded:: 2018.3.4
.. versionadded:: 2019.2.0
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
netsh advfirewall prompt.
.. note::
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:
c:\>netsh
netsh>advfirewall
netsh advfirewall>set help
netsh advfirewall>set domain help
Usage:
.. code-block:: python
import salt.utils.win_lgpo_netsh
# Get the inbound/outbound firewall settings for connections on the
# local domain profile
salt.utils.win_lgpo_netsh.get_settings(profile='domain',
section='firewallpolicy')
# Get the inbound/outbound firewall settings for connections on the
# domain profile as defined by local group policy
salt.utils.win_lgpo_netsh.get_settings(profile='domain',
section='firewallpolicy',
store='lgpo')
# Get all firewall settings for connections on the domain profile
salt.utils.win_lgpo_netsh.get_all_settings(profile='domain')
# Get all firewall settings for connections on the domain profile as
# defined by local group policy
salt.utils.win_lgpo_netsh.get_all_settings(profile='domain', store='lgpo')
# Get all firewall settings for all profiles
salt.utils.win_lgpo_netsh.get_all_settings()
# Get all firewall settings for all profiles as defined by local group
# policy
salt.utils.win_lgpo_netsh.get_all_settings(store='lgpo')
# Set the inbound setting for the domain profile to block inbound
# connections
salt.utils.win_lgpo_netsh.set_firewall_settings(profile='domain',
inbound='blockinbound')
# Set the outbound setting for the domain profile to allow outbound
# connections
salt.utils.win_lgpo_netsh.set_firewall_settings(profile='domain',
outbound='allowoutbound')
# Set inbound/outbound settings for the domain profile in the group
# policy to block inbound and allow outbound
salt.utils.win_lgpo_netsh.set_firewall_settings(profile='domain',
inbound='blockinbound',
outbound='allowoutbound',
store='lgpo')
'''
# Import Python libs
from __future__ import absolute_import, unicode_literals, print_function
from textwrap import dedent
import logging
import os
import re
import socket
import tempfile
import salt.modules.cmdmod
from salt.exceptions import CommandExecutionError
from salt.ext.six.moves import zip
log = logging.getLogger(__name__)
__hostname__ = socket.gethostname()
__virtualname__ = 'netsh'
# Although utils are often directly imported, it is also possible to use the
# loader.
def __virtual__():
'''
Only load if on a Windows system
'''
if not salt.utils.platform.is_windows():
return False, 'This utility only available on Windows'
return __virtualname__
def _netsh_file(content):
'''
helper function to get the results of ``netsh -f content.txt``
Running ``netsh`` will drop you into a ``netsh`` prompt where you can issue
``netsh`` commands. You can put a series of commands in an external file and
run them as if from a ``netsh`` prompt using the ``-f`` switch. That's what
this function does.
Args:
content (str):
The contents of the file that will be run by the ``netsh -f``
command
Returns:
str: The text returned by the netsh command
'''
with tempfile.NamedTemporaryFile(mode='w',
prefix='salt-',
suffix='.netsh',
delete=False) as fp:
fp.write(content)
try:
log.debug('%s:\n%s', fp.name, content)
return salt.modules.cmdmod.run('netsh -f {0}'.format(fp.name), python_shell=True)
finally:
os.remove(fp.name)
def _netsh_command(command, store):
if store.lower() not in ('local', 'lgpo'):
raise ValueError('Incorrect store: {0}'.format(store))
# set the store for local or lgpo
if store.lower() == 'local':
netsh_script = dedent('''\
advfirewall
set store local
{0}
'''.format(command))
else:
netsh_script = dedent('''\
advfirewall
set store gpo = {0}
{1}
'''.format(__hostname__, command))
return _netsh_file(content=netsh_script).splitlines()
def get_settings(profile, section, store='local'):
'''
Get the firewall property from the specified profile in the specified store
as returned by ``netsh advfirewall``.
Args:
profile (str):
The firewall profile to query. Valid options are:
- domain
- public
- private
section (str):
The property to query within the selected profile. Valid options
are:
- firewallpolicy : inbound/outbound behavior
- logging : firewall logging settings
- settings : firewall properties
- state : firewalls state (on | off)
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 properties for the specified profile
Raises:
CommandExecutionError: If an error occurs
ValueError: If the parameters are incorrect
'''
# validate input
if profile.lower() not in ('domain', 'public', 'private'):
raise ValueError('Incorrect profile: {0}'.format(profile))
if section.lower() not in ('state', 'firewallpolicy', 'settings', 'logging'):
raise ValueError('Incorrect section: {0}'.format(section))
if store.lower() not in ('local', 'lgpo'):
raise ValueError('Incorrect store: {0}'.format(store))
command = 'show {0}profile {1}'.format(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
if len(results) < 3:
raise CommandExecutionError('Invalid results: {0}'.format(results))
ret = {}
# Skip the first 2 lines. Add everything else to a dictionary
for line in results[3:]:
ret.update(dict(list(zip(*[iter(re.split(r"\s{2,}", line))]*2))))
# Remove spaces from the values so that `Not Configured` is detected
# correctly
for item in ret:
ret[item] = ret[item].replace(' ', '')
# special handling for firewallpolicy
if section == 'firewallpolicy':
inbound, outbound = ret['Firewall Policy'].split(',')
return {'Inbound': inbound, 'Outbound': outbound}
return ret
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
'''
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'):
'''
Gets all properties for all profiles in the specified store
Args:
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 for each profile
'''
return {
'Domain Profile': get_all_settings(profile='domain', store=store),
'Private Profile': get_all_settings(profile='private', store=store),
'Public Profile': get_all_settings(profile='public', store=store)
}
def set_firewall_settings(profile,
inbound=None,
outbound=None,
store='local'):
'''
Set the firewall inbound/outbound settings for the specified profile and
store
Args:
profile (str):
The firewall profile to configure. Valid options are:
- domain
- public
- private
inbound (str):
The inbound setting. If ``None`` is passed, the setting will remain
unchanged. Valid values are:
- blockinbound
- blockinboundalways
- allowinbound
- notconfigured
Default is ``None``
outbound (str):
The outbound setting. If ``None`` is passed, the setting will remain
unchanged. Valid values are:
- allowoutbound
- blockoutbound
- notconfigured
Default is ``None``
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:
bool: ``True`` if successful
Raises:
CommandExecutionError: If an error occurs
ValueError: If the parameters are incorrect
'''
# Input validation
if profile.lower() not in ('domain', 'public', 'private'):
raise ValueError('Incorrect profile: {0}'.format(profile))
if inbound and inbound.lower() not in ('blockinbound',
'blockinboundalways',
'allowinbound',
'notconfigured'):
raise ValueError('Incorrect inbound value: {0}'.format(inbound))
if outbound and outbound.lower() not in ('allowoutbound',
'blockoutbound',
'notconfigured'):
raise ValueError('Incorrect outbound value: {0}'.format(outbound))
if not inbound and not outbound:
raise ValueError('Must set inbound or outbound')
# You have to specify inbound and outbound setting at the same time
# If you're only specifying one, you have to get the current setting for the
# other
if not inbound or not outbound:
ret = get_settings(profile=profile,
section='firewallpolicy',
store=store)
if not inbound:
inbound = ret['Inbound']
if not outbound:
outbound = ret['Outbound']
command = 'set {0}profile firewallpolicy {1},{2}' \
''.format(profile, inbound, outbound)
results = _netsh_command(command=command, store=store)
if results:
raise CommandExecutionError('An error occurred: {0}'.format(results))
return True
def set_logging_settings(profile, setting, value, store='local'):
'''
Configure logging settings for the Windows firewall.
Args:
profile (str):
The firewall profile to configure. Valid options are:
- domain
- public
- private
setting (str):
The logging setting to configure. Valid options are:
- allowedconnections
- droppedconnections
- filename
- maxfilesize
value (str):
The value to apply to the setting. Valid values are dependent upon
the setting being configured. Valid options are:
allowedconnections:
- enable
- disable
- notconfigured
droppedconnections:
- enable
- disable
- notconfigured
filename:
- Full path and name of the firewall log file
- notconfigured
maxfilesize:
- 1 - 32767 (Kb)
- notconfigured
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:
bool: ``True`` if successful
Raises:
CommandExecutionError: If an error occurs
ValueError: If the parameters are incorrect
'''
# Input validation
if profile.lower() not in ('domain', 'public', 'private'):
raise ValueError('Incorrect profile: {0}'.format(profile))
if setting.lower() not in ('allowedconnections',
'droppedconnections',
'filename',
'maxfilesize'):
raise ValueError('Incorrect setting: {0}'.format(setting))
if setting.lower() in ('allowedconnections', 'droppedconnections'):
if value.lower() not in ('enable', 'disable', 'notconfigured'):
raise ValueError('Incorrect value: {0}'.format(value))
# 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
if setting.lower() == 'maxfilesize':
if value.lower() != 'notconfigured':
# Must be a number between 1 and 32767
try:
int(value)
except ValueError:
raise ValueError('Incorrect value: {0}'.format(value))
if not 1 <= int(value) <= 32767:
raise ValueError('Incorrect value: {0}'.format(value))
# Run the command
command = 'set {0}profile logging {1} {2}'.format(profile, setting, value)
results = _netsh_command(command=command, store=store)
# A successful run should return an empty list
if results:
raise CommandExecutionError('An error occurred: {0}'.format(results))
return True
def set_settings(profile, setting, value, store='local'):
'''
Configure firewall settings.
Args:
profile (str):
The firewall profile to configure. Valid options are:
- domain
- public
- private
setting (str):
The firewall setting to configure. Valid options are:
- localfirewallrules
- localconsecrules
- inboundusernotification
- remotemanagement
- unicastresponsetomulticast
value (str):
The value to apply to the setting. Valid options are
- enable
- disable
- notconfigured
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:
bool: ``True`` if successful
Raises:
CommandExecutionError: If an error occurs
ValueError: If the parameters are incorrect
'''
# Input validation
if profile.lower() not in ('domain', 'public', 'private'):
raise ValueError('Incorrect profile: {0}'.format(profile))
if setting.lower() not in ('localfirewallrules',
'localconsecrules',
'inboundusernotification',
'remotemanagement',
'unicastresponsetomulticast'):
raise ValueError('Incorrect setting: {0}'.format(setting))
if value.lower() not in ('enable', 'disable', 'notconfigured'):
raise ValueError('Incorrect value: {0}'.format(value))
# Run the command
command = 'set {0}profile settings {1} {2}'.format(profile, setting, value)
results = _netsh_command(command=command, store=store)
# A successful run should return an empty list
if results:
raise CommandExecutionError('An error occurred: {0}'.format(results))
return True
def set_state(profile, state, store='local'):
'''
Configure the firewall state.
Args:
profile (str):
The firewall profile to configure. Valid options are:
- domain
- public
- private
state (str):
The firewall state. Valid options are:
- on
- off
- notconfigured
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:
bool: ``True`` if successful
Raises:
CommandExecutionError: If an error occurs
ValueError: If the parameters are incorrect
'''
# Input validation
if profile.lower() not in ('domain', 'public', 'private'):
raise ValueError('Incorrect profile: {0}'.format(profile))
if state.lower() not in ('on', 'off', 'notconfigured'):
raise ValueError('Incorrect state: {0}'.format(state))
# Run the command
command = 'set {0}profile state {1}'.format(profile, state)
results = _netsh_command(command=command, store=store)
# A successful run should return an empty list
if results:
raise CommandExecutionError('An error occurred: {0}'.format(results))
return True