diff --git a/salt/modules/win_snmp.py b/salt/modules/win_snmp.py index 6b5d25ce5c..316fa92e5e 100644 --- a/salt/modules/win_snmp.py +++ b/salt/modules/win_snmp.py @@ -16,7 +16,10 @@ import salt.utils _HKEY = 'HKLM' _SNMP_KEY = r'SYSTEM\CurrentControlSet\Services\SNMP\Parameters' _AGENT_KEY = r'{0}\RFC1156Agent'.format(_SNMP_KEY) +_COMMUNITIES_KEY = r'{0}\ValidCommunities'.format(_SNMP_KEY) +_PERMISSION_TYPES = {'None': 1, 'Notify': 2, 'Read Only': 4, 'Read Write': 8, + 'Read Create': 16} _SERVICE_TYPES = {'None': 0, 'Physical': 1, 'Datalink and subnetwork': 2, 'Internet': 4, 'End-to-end': 8, 'Applications': 64} @@ -51,6 +54,22 @@ def get_agent_service_types(): return _SERVICE_TYPES.keys() +def get_permission_types(): + ''' + Get the permission types that can be configured for communities. + + :return: A list of the permission types. + :rtype: list + + CLI Example: + + .. code-block:: bash + + salt '*' win_snmp.get_permission_types + ''' + return _PERMISSION_TYPES.keys() + + def get_agent_settings(): ''' Determine the value of the SNMP sysContact, sysLocation, and sysServices settings. @@ -212,3 +231,104 @@ def set_auth_traps_enabled(status=True): return True _LOG.error('Unable to configure %s with value: %s', vname, vdata) return False + + +def get_community_names(): + ''' + Get the current accepted SNMP community names and their permissions. + + :return: A dictionary of community names and permissions. + :rtype: dict + + CLI Example: + + .. code-block:: bash + + salt '*' win_snmp.get_community_names + ''' + ret = dict() + current_values = __salt__['reg.list_values'](_HKEY, _COMMUNITIES_KEY, include_default=False) + + # The communities are stored as the community name with a numeric permission value. Convert + # the numeric value to the text equivalent, as present in the Windows SNMP service GUI. + for current_value in current_values: + permissions = str() + for permission_name in _PERMISSION_TYPES: + if current_value['vdata'] == _PERMISSION_TYPES[permission_name]: + permissions = permission_name + break + ret[current_value['vname']] = permissions + + if not ret: + _LOG.debug('Unable to find existing communities.') + return ret + + +def set_community_names(communities): + ''' + Manage the SNMP accepted community names and their permissions. + + :param str communities: A dictionary of SNMP community names and permissions. + The possible permissions can be found via win_snmp.get_permission_types. + + :return: A boolean representing whether the change succeeded. + :rtype: bool + + CLI Example: + + .. code-block:: bash + + salt '*' win_snmp.set_community_names communities="{'TestCommunity': 'Read Only'}' + ''' + values = dict() + + current_communities = get_community_names() + + if communities == current_communities: + _LOG.debug('Communities already contain the provided values.') + return True + + for vname in communities: + if not communities[vname]: + communities[vname] = 'None' + try: + vdata = _PERMISSION_TYPES[communities[vname]] + except KeyError: + message = ("Invalid permission '{0}' specified. Valid permissions:" + ' {1}').format(communities[vname], _PERMISSION_TYPES.keys()) + raise SaltInvocationError(message) + values[vname] = vdata + + # Check current communities. + for current_vname in current_communities: + if current_vname in values: + # Modify existing communities that have a different permission value. + if current_communities[current_vname] != values[current_vname]: + __salt__['reg.set_value'](_HKEY, _COMMUNITIES_KEY, current_vname, values[current_vname], 'REG_DWORD') + else: + # Remove current communities that weren't provided. + __salt__['reg.delete_value'](_HKEY, _COMMUNITIES_KEY, current_vname) + + # Create any new communities. + for vname in values: + if vname not in current_communities: + __salt__['reg.set_value'](_HKEY, _COMMUNITIES_KEY, vname, values[vname], 'REG_DWORD') + + # Get the fields post-change so that we can verify tht all values + # were modified successfully. Track the ones that weren't. + new_communities = get_community_names() + failed_communities = dict() + + for new_vname in new_communities: + if new_vname not in communities: + failed_communities[new_vname] = None + + for vname in communities: + if communities[vname] != new_communities[vname]: + failed_communities[vname] = communities[vname] + + if failed_communities: + _LOG.error('Unable to configure communities: %s', failed_communities) + return False + _LOG.debug('Communities configured successfully: %s', communities.keys()) + return True diff --git a/salt/states/win_snmp.py b/salt/states/win_snmp.py index 3442b80ca2..2d6075c2d7 100644 --- a/salt/states/win_snmp.py +++ b/salt/states/win_snmp.py @@ -30,9 +30,8 @@ def agent_settings(name, contact, location, services=None): 'comment': str(), 'result': None} - ret_settings = dict() - ret_settings['changes'] = {} - ret_settings['failures'] = {} + ret_settings = {'changes': dict(), + 'failures': dict()} if not services: services = ['None'] @@ -105,3 +104,78 @@ def auth_traps_enabled(name, status=True): ret['result'] = __salt__['win_snmp.set_auth_traps_enabled'](status=status) return ret + + +def community_names(name, communities=None): + ''' + Manage the SNMP accepted community names and their permissions. + + :param str communities: A dictionary of SNMP communities and permissions. + ''' + ret = {'name': name, + 'changes': dict(), + 'comment': str(), + 'result': None} + + ret_communities = {'changes': dict(), + 'failures': dict()} + + if not communities: + communities = dict() + + current_communities = __salt__['win_snmp.get_community_names']() + + # Note any existing communities that should be removed. + for current_vname in current_communities: + if current_vname not in communities: + ret_communities['changes'][current_vname] = {'old': current_communities[current_vname], + 'new': None} + + # Note any new communities or existing communities that should be changed. + for vname in communities: + current_vdata = None + if vname in current_communities: + current_vdata = current_communities[vname] + if communities[vname] != current_vdata: + ret_communities['changes'][vname] = {'old': current_vdata, + 'new': communities[vname]} + + if not ret_communities['changes']: + ret['comment'] = 'Communities already contain the provided values.' + ret['result'] = True + return ret + elif __opts__['test']: + ret['comment'] = 'Communities will be changed.' + ret['changes'] = ret_communities + return ret + + __salt__['win_snmp.set_community_names'](communities=communities) + new_communities = __salt__['win_snmp.get_community_names']() + + # Verify that any communities that needed to be removed were removed. + for new_vname in new_communities: + if new_vname not in communities: + ret_communities['failures'][new_vname] = {'old': current_communities[new_vname], + 'new': new_communities[new_vname]} + ret_communities['changes'].pop(new_vname, None) + + # Verify that any new communities or existing communities that + # needed to be changed were changed. + for vname in communities: + new_vdata = None + if vname in new_communities: + new_vdata = new_communities[vname] + if communities[vname] != new_vdata: + ret_communities['failures'][vname] = {'old': current_communities[vname], + 'new': new_vdata} + ret_communities['changes'].pop(vname, None) + + if ret_communities['failures']: + ret['comment'] = 'Some communities failed to change.' + ret['changes'] = ret_communities + ret['result'] = False + else: + ret['comment'] = 'Set communities to contain the provided values.' + ret['changes'] = ret_communities['changes'] + ret['result'] = True + return ret