diff --git a/salt/modules/capirca_acl.py b/salt/modules/capirca_acl.py index fb3509705dd7..d354da4f393c 100644 --- a/salt/modules/capirca_acl.py +++ b/salt/modules/capirca_acl.py @@ -316,6 +316,10 @@ def _clean_term_opts(term_opts): # firstly we'll process special fields like source_service or destination_services # which will inject values directly in the source or destination port and protocol if field == 'source_service' and value: + if isinstance(value, six.string_types): + value = _make_it_list(clean_opts, field, value) + log.debug('Processing special source services:') + log.debug(value) for service in value: if service and service in _services: # if valid source_service @@ -326,7 +330,15 @@ def _clean_term_opts(term_opts): clean_opts['protocol'] = _make_it_list(clean_opts, 'protocol', _services[service]['protocol']) + log.debug('Built source_port field, after processing special source services:') + log.debug(clean_opts.get('source_port')) + log.debug('Built protocol field, after processing special source services:') + log.debug(clean_opts.get('protocol')) elif field == 'destination_service' and value: + if isinstance(value, six.string_types): + value = _make_it_list(clean_opts, field, value) + log.debug('Processing special destination services:') + log.debug(value) for service in value: if service and service in _services: # if valid destination_service @@ -337,6 +349,10 @@ def _clean_term_opts(term_opts): clean_opts['protocol'] = _make_it_list(clean_opts, 'protocol', _services[service]['protocol']) + log.debug('Built source_port field, after processing special destination services:') + log.debug(clean_opts.get('destination_service')) + log.debug('Built protocol field, after processing special destination services:') + log.debug(clean_opts.get('protocol')) # not a special field, but it has to be a valid one elif field in _TERM_FIELDS and value and value != _TERM_FIELDS[field]: # if not a special field type @@ -352,6 +368,20 @@ def _clean_term_opts(term_opts): return clean_opts +def _lookup_element(lst, key): + ''' + Find an dictionary in a list of dictionaries, given its main key. + ''' + if not lst: + return {} + for ele in lst: + if not ele or not isinstance(ele, dict): + continue + if ele.keys()[0] == key: + return ele.values()[0] + return {} + + def _get_pillar_cfg(pillar_key, pillarenv=None, saltenv=None): @@ -361,24 +391,61 @@ def _get_pillar_cfg(pillar_key, pillar_cfg = __salt__['pillar.get'](pillar_key, pillarenv=pillarenv, saltenv=saltenv) - if not isinstance(pillar_cfg, dict): - return {} return pillar_cfg -def _merge_dict(source, dest): +def _cleanup(lst): + ''' + Return a list of non-empty dictionaries. + ''' + clean = [] + for ele in lst: + if ele and isinstance(ele, dict): + clean.append(ele) + return clean + + +def _merge_list_of_dict(first, second, prepend=True): ''' - Merge dictionaries. + Merge lists of dictionaries. + Each element of the list is a dictionary having one single key. + That key is then used as unique lookup. + The first element list has higher priority than the second. + When there's an overlap between the two lists, + it won't change the position, but the content. ''' - if not source: - source = dest - elif source.keys() != dest.keys(): - source_keys = set(source.keys()) - dest_keys = set(dest.keys()) - merge_keys = dest_keys - source_keys - for key in merge_keys: - source[key] = dest[key] - return source + first = _cleanup(first) + second = _cleanup(second) + if not first and not second: + return [] + if not first and second: + return second + if first and not second: + return first + # Determine overlaps + # So we dont change the position of the existing terms/filters + overlaps = [] + merged = [] + appended = [] + for ele in first: + if _lookup_element(second, ele.keys()[0]): + overlaps.append(ele) + elif prepend: + merged.append(ele) + elif not prepend: + appended.append(ele) + for ele in second: + ele_key = ele.keys()[0] + if _lookup_element(overlaps, ele_key): + # If theres an overlap, get the value from the first + # But inserted into the right position + ele_val_first = _lookup_element(first, ele_key) + merged.append({ele_key: ele_val_first}) + else: + merged.append(ele) + if not prepend: + merged.extend(appended) + return merged def _get_term_object(filter_name, @@ -399,8 +466,9 @@ def _get_term_object(filter_name, term.name = term_name term_opts = {} if merge_pillar: - term_pillar_key = ':'.join((pillar_key, filter_name, term_name)) - term_opts = _get_pillar_cfg(term_pillar_key, + term_opts = get_term_pillar(filter_name, + term_name, + pillar_key=pillar_key, saltenv=saltenv, pillarenv=pillarenv) log.debug('Merging with pillar data:') @@ -436,8 +504,12 @@ def _get_policy_object(platform, policy = _Policy() policy_filters = [] if not filters: - filters = {} - for filter_name, filter_config in six.iteritems(filters): + filters = [] + for filter_ in filters: + if not filter_ or not isinstance(filter_, dict): + continue # go to the next filter + filter_name = filter_.keys()[0] + filter_config = filter_.values()[0] header = aclgen.policy.Header() # same header everywhere target_opts = [ platform, @@ -451,14 +523,17 @@ def _get_policy_object(platform, target = aclgen.policy.Target(target_opts) header.AddObject(target) filter_terms = [] - for term_name, term_fields in six.iteritems(filter_config): - term = _get_term_object(filter_name, - term_name, - pillar_key=pillar_key, - pillarenv=pillarenv, - saltenv=saltenv, - merge_pillar=merge_pillar, - **term_fields) + for term_ in filter_config.get('terms', []): + if term_ and isinstance(term_, dict): + term_name = term_.keys()[0] + term_fields = term_.values()[0] + term = _get_term_object(filter_name, + term_name, + pillar_key=pillar_key, + pillarenv=pillarenv, + saltenv=saltenv, + merge_pillar=merge_pillar, + **term_fields) filter_terms.append(term) policy_filters.append( (header, filter_terms) @@ -555,8 +630,9 @@ def get_term_config(platform, .. code-block:: yaml firewall: - my-filter: - my-term: + - my-filter: + terms: + - my-term: source_port: 1234 source_address: - 1.2.3.4/32 @@ -755,25 +831,24 @@ def get_term_config(platform, .. code-block:: text - ! $Id:$ - ! $Date:$ - ! $Revision:$ + ! $Date: 2017/03/22 $ no ip access-list filter-name ip access-list filter-name - remark $Id:$ remark term-name permit ip host 1.2.3.4 host 5.6.7.8 exit ''' - terms = { + terms = [] + term = { term_name: { } } - terms[term_name].update(term_fields) - terms[term_name].update({ + term[term_name].update(term_fields) + term[term_name].update({ 'source_service': _make_it_list({}, 'source_service', source_service), 'destination_service': _make_it_list({}, 'destination_service', destination_service), }) + terms.append(term) if not filter_options: filter_options = [] return get_filter_config(platform, @@ -795,6 +870,7 @@ def get_filter_config(platform, filter_name, filter_options=None, terms=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -820,10 +896,16 @@ def get_filter_config(platform, .. _options: https://github.com/google/capirca/wiki/Policy-format#header-section terms - Dictionary of terms for this policy filter. + List of terms for this policy filter. If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of terms generated by merging + the terms from ``terms`` with those defined in the pillar (if any): new terms are prepended + at the beginning, while existing ones will preserve the position. To add the new terms + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -836,7 +918,7 @@ def get_filter_config(platform, :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. merge_pillar: ``True`` - Merge the CLI variables with the pillar. Default: ``True`` + Merge the CLI variables with the pillar. Default: ``True``. only_lower_merge: ``False`` Specify if it should merge only the terms fields. Otherwise it will try @@ -882,34 +964,41 @@ def get_filter_config(platform, .. code-block:: yaml netacl: - my-filter: - my-term: - source_port: [1234, 1235] - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept + - my-filter: + terms: + - my-term: + source_port: [1234, 1235] + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept ''' if not filter_options: filter_options = [] if not terms: - terms = {} + terms = [] if merge_pillar and not only_lower_merge: - filter_pillar_key = ':'.join((pillar_key, filter_name)) - filter_pillar_cfg = _get_pillar_cfg(filter_pillar_key) + acl_pillar_cfg = _get_pillar_cfg(pillar_key, + saltenv=saltenv, + pillarenv=pillarenv) + filter_pillar_cfg = _lookup_element(acl_pillar_cfg, filter_name) filter_options = filter_options or filter_pillar_cfg.pop('options', None) - terms = _merge_dict(terms, filter_pillar_cfg) - # merge the passed variable with the pillar data - # any filter term not defined here, will be appended from the pillar - # new terms won't be removed - filters = { + if filter_pillar_cfg: + # Only when it was able to find the filter in the ACL config + pillar_terms = filter_pillar_cfg.get('terms', []) # No problem if empty in the pillar + terms = _merge_list_of_dict(terms, pillar_terms, prepend=prepend) + # merge the passed variable with the pillar data + # any filter term not defined here, will be appended from the pillar + # new terms won't be removed + filters = [] + filters.append({ filter_name: { - 'options': _make_it_list({}, filter_name, filter_options) + 'options': _make_it_list({}, filter_name, filter_options), + 'terms': terms } - } - filters[filter_name].update(terms) + }) return get_policy_config(platform, filters=filters, pillar_key=pillar_key, @@ -925,6 +1014,7 @@ def get_filter_config(platform, def get_policy_config(platform, filters=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -941,10 +1031,16 @@ def get_policy_config(platform, The name of the Capirca platform. filters - Dictionary of filters for this policy. + List of filters for this policy. If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of filters generated by merging + the filters from ``filters`` with those defined in the pillar (if any): new filters are prepended + at the beginning, while existing ones will preserve the position. To add the new filters + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -994,16 +1090,22 @@ def get_policy_config(platform, ** $Revision:$ ** */ - filter my-other-filter { - interface-specific; - term dummy-term { + filter my-filter { + term my-term { from { - protocol [ tcp udp ]; + source-port [ 1234 1235 ]; } then { reject; } } + term my-other-term { + from { + protocol tcp; + source-port 5678-5680; + } + then accept; + } } } } @@ -1016,23 +1118,16 @@ def get_policy_config(platform, ** $Revision:$ ** */ - filter my-filter { + filter my-other-filter { interface-specific; - term my-term { + term dummy-term { from { - source-port [ 1234 1235 ]; + protocol [ tcp udp ]; } then { reject; } } - term my-other-term { - from { - protocol tcp; - source-port 5678-5680; - } - then accept; - } } } } @@ -1042,32 +1137,38 @@ def get_policy_config(platform, .. code-block:: yaml netacl: - my-filter: - my-term: - source_port: [1234, 1235] - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept - my-other-filter: - dummy-term: - protocol: - - tcp - - udp - action: reject + - my-filter: + options: + - not-interface-specific + terms: + - my-term: + source_port: [1234, 1235] + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept + - my-other-filter: + terms: + - dummy-term: + protocol: + - tcp + - udp + action: reject ''' if not filters: - filters = {} + filters = [] if merge_pillar and not only_lower_merge: # the pillar key for the policy config is the `pillar_key` itself - policy_pillar_cfg = _get_pillar_cfg(pillar_key) + policy_pillar_cfg = _get_pillar_cfg(pillar_key, + saltenv=saltenv, + pillarenv=pillarenv) # now, let's merge everything witht the pillar data # again, this will not remove any extra filters/terms # but it will merge with the pillar data # if this behaviour is not wanted, the user can set `merge_pillar` as `False` - filters = _merge_dict(filters, policy_pillar_cfg) + filters = _merge_list_of_dict(filters, policy_pillar_cfg, prepend=prepend) policy_object = _get_policy_object(platform, filters=filters, pillar_key=pillar_key, @@ -1080,3 +1181,67 @@ def get_policy_config(platform, revision_no=revision_no, revision_date=revision_date, revision_date_format=revision_date_format) + + +def get_filter_pillar(filter_name, + pillar_key='acl', + pillarenv=None, + saltenv=None): + ''' + Helper that can be used inside a state SLS, + in order to get the filter configuration given its name. + + filter_name + The name of the filter. + + pillar_key + The root key of the whole policy config. + + pillarenv + Query the master to generate fresh pillar data on the fly, + specifically from the requested pillar environment. + + saltenv + Included only for compatibility with + :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. + ''' + pillar_cfg = _get_pillar_cfg(pillar_key, + pillarenv=pillarenv, + saltenv=saltenv) + return _lookup_element(pillar_cfg, filter_name) + + +def get_term_pillar(filter_name, + term_name, + pillar_key='acl', + pillarenv=None, + saltenv=None): + ''' + Helper that can be used inside a state SLS, + in order to get the term configuration given its name, + under a certain filter uniquely identified by its name. + + filter_name + The name of the filter. + + term_name + The name of the term. + + pillar_key: ``acl`` + The root key of the whole policy config. Default: ``acl``. + + pillarenv + Query the master to generate fresh pillar data on the fly, + specifically from the requested pillar environment. + + saltenv + Included only for compatibility with + :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. + ''' + filter_pillar_cfg = get_filter_pillar(filter_name, + pillar_key=pillar_key, + pillarenv=pillarenv, + saltenv=saltenv) + term_pillar_cfg = filter_pillar_cfg.get('terms', []) + term_opts = _lookup_element(term_pillar_cfg, term_name) + return term_opts diff --git a/salt/modules/napalm_acl.py b/salt/modules/napalm_acl.py index 597664ebad64..2b186065e49f 100644 --- a/salt/modules/napalm_acl.py +++ b/salt/modules/napalm_acl.py @@ -154,8 +154,9 @@ def load_term_config(filter_name, .. code-block:: yaml firewall: - my-filter: - my-term: + - my-filter: + terms: + - my-term: source_port: 1234 source_address: - 1.2.3.4/32 @@ -174,6 +175,8 @@ def load_term_config(filter_name, merge_pillar: ``True`` Merge the CLI variables with the pillar. Default: ``True``. + The properties specified through the CLI have higher priority than the pillar. + revision_id Add a comment in the term config having the description for the changes applied. @@ -217,13 +220,13 @@ def load_term_config(filter_name, **term_fields Term attributes. To see what fields are supported, please consult the list of supported keywords_. - Some platforms have few other optional_ keyworkds. + Some platforms have few other optional_ keywords. .. _keywords: https://github.com/google/capirca/wiki/Policy-format#keywords .. _optional: https://github.com/google/capirca/wiki/Policy-format#optionally-supported-keywords .. note:: - The following fields are accepted: + The following fields are accepted (some being platform-specific): - action - address @@ -377,9 +380,7 @@ def load_term_config(filter_name, [edit firewall] + family inet { + /* - + ** $Id:$ - + ** $Date:$ - + ** $Revision:$ + + ** $Date: 2017/03/22 $ + ** + */ + filter filter-name { @@ -402,9 +403,7 @@ def load_term_config(filter_name, family inet { replace: /* - ** $Id:$ - ** $Date:$ - ** $Revision:$ + ** $Date: 2017/03/22 $ ** */ filter filter-name { @@ -455,6 +454,7 @@ def load_term_config(filter_name, def load_filter_config(filter_name, filter_options=None, terms=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -471,6 +471,15 @@ def load_filter_config(filter_name, ''' Generate and load the configuration of a policy filter. + .. note:: + + The order of the terms is very important. The configuration loaded + on the device respects the order defined in the ``terms`` and/or + inside the pillar. + + When merging the ``terms`` with the pillar data, consider the + ``prepend`` argument to make sure the order is correct! + filter_name The name of the policy filter. @@ -481,10 +490,16 @@ def load_filter_config(filter_name, .. _options: https://github.com/google/capirca/wiki/Policy-format#header-section terms - Dictionary of terms for this policy filter. + List of terms for this policy filter. If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of terms generated by merging + the terms from ``terms`` with those defined in the pillar (if any): new terms are prepended + at the beginning, while existing ones will preserve the position. To add the new terms + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -497,7 +512,10 @@ def load_filter_config(filter_name, :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. merge_pillar: ``True`` - Merge the CLI variables with the pillar. Default: ``True`` + Merge the CLI variables with the pillar. Default: ``True``. + + The merge logic depends on the ``prepend`` argument and + the CLI has higher priority than the pillar. only_lower_merge: ``False`` Specify if it should merge only the terms fields. Otherwise it will try @@ -528,6 +546,7 @@ def load_filter_config(filter_name, as ``loaded_config`` contaning the raw configuration loaded on the device. The output is a dictionary having the same form as :mod:`net.load_config `. + CLI Example: .. code-block:: bash @@ -547,9 +566,7 @@ def load_filter_config(filter_name, [edit firewall] + family inet { + /* - + ** $Id:$ - + ** $Date:$ - + ** $Revision:$ + + ** $Date: 2017/03/22 $ + ** + */ + filter my-filter { @@ -576,9 +593,7 @@ def load_filter_config(filter_name, family inet { replace: /* - ** $Id:$ - ** $Date:$ - ** $Revision:$ + ** $Date: 2017/03/22 $ ** */ filter my-filter { @@ -609,24 +624,26 @@ def load_filter_config(filter_name, .. code-block:: yaml netacl: - my-filter: - my-term: - source_port: [1234, 1235] - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept + - my-filter: + terms: + - my-term: + source_port: [1234, 1235] + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept ''' if not filter_options: filter_options = [] if not terms: - terms = {} + terms = [] platform = _get_capirca_platform() filter_config = __salt__['capirca.get_filter_config'](platform, filter_name, terms=terms, + prepend=prepend, filter_options=filter_options, pillar_key=pillar_key, pillarenv=pillarenv, @@ -646,6 +663,7 @@ def load_filter_config(filter_name, @proxy_napalm_wrap def load_policy_config(filters=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -662,11 +680,26 @@ def load_policy_config(filters=None, ''' Generate and load the configuration of the whole policy. + .. note:: + + The order of the filters and their terms is very important. + The configuration loaded on the device respects the order + defined in the ``filters`` and/or inside the pillar. + + When merging the ``filters`` with the pillar data, consider the + ``prepend`` argument to make sure the order is correct! + filters - Dictionary of filters for this policy. + List of filters for this policy. If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of filters generated by merging + the filters from ``filters`` with those defined in the pillar (if any): new filters are prepended + at the beginning, while existing ones will preserve the position. To add the new filters + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -681,6 +714,9 @@ def load_policy_config(filters=None, merge_pillar: ``True`` Merge the CLI variables with the pillar. Default: ``True``. + The merge logic depends on the ``prepend`` argument and + the CLI has higher priority than the pillar. + only_lower_merge: ``False`` Specify if it should merge only the filters and terms fields. Otherwise it will try to merge everything at the policy level. Default: ``False``. @@ -732,34 +768,23 @@ def load_policy_config(filters=None, @@ -1228,9 +1228,24 @@ ! +ipv4 access-list my-filter - + 10 remark $Id:$ - + 20 remark my-term - + 30 deny tcp host 1.2.3.4 eq 1234 any - + 40 deny udp host 1.2.3.4 eq 1234 any - + 50 deny tcp host 1.2.3.4 eq 1235 any - + 60 deny udp host 1.2.3.4 eq 1235 any - + 70 remark my-other-term - + 80 permit tcp any range 5678 5680 any + + 10 remark my-term + + 20 deny tcp host 1.2.3.4 eq 1234 any + + 30 deny udp host 1.2.3.4 eq 1234 any + + 40 deny tcp host 1.2.3.4 eq 1235 any + + 50 deny udp host 1.2.3.4 eq 1235 any + + 60 remark my-other-term + + 70 permit tcp any range 5678 5680 any +! +! +ipv4 access-list block-icmp - + 10 remark $Id:$ - + 20 remark first-term - + 30 deny icmp any any + + 10 remark first-term + + 20 deny icmp any any ! loaded_config: - ! $Id:$ - ! $Date:$ - ! $Revision:$ - no ipv4 access-list block-icmp - ipv4 access-list block-icmp - remark $Id:$ - remark first-term - deny icmp any any - exit + ! $Date: 2017/03/22 $ no ipv4 access-list my-filter ipv4 access-list my-filter - remark $Id:$ remark my-term deny tcp host 1.2.3.4 eq 1234 any deny udp host 1.2.3.4 eq 1234 any @@ -768,6 +793,11 @@ def load_policy_config(filters=None, remark my-other-term permit tcp any range 5678 5680 any exit + no ipv4 access-list block-icmp + ipv4 access-list block-icmp + remark first-term + deny icmp any any + exit result: True @@ -776,30 +806,33 @@ def load_policy_config(filters=None, .. code-block:: yaml acl: - my-filter: - my-term: - source_port: [1234, 1235] - protocol: - - tcp - - udp - source_address: 1.2.3.4 - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept - block-icmp: - first-term: - protocol: - - icmp - action: reject + - my-filter: + terms: + - my-term: + source_port: [1234, 1235] + protocol: + - tcp + - udp + source_address: 1.2.3.4 + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept + - block-icmp: + terms: + - first-term: + protocol: + - icmp + action: reject ''' if not filters: - filters = {} + filters = [] platform = _get_capirca_platform() policy_config = __salt__['capirca.get_policy_config'](platform, filters=filters, + prepend=prepend, pillar_key=pillar_key, pillarenv=pillarenv, saltenv=saltenv, @@ -814,3 +847,65 @@ def load_policy_config(filters=None, commit=commit, debug=debug, inherit_napalm_device=napalm_device) # pylint: disable=undefined-variable + + +def get_filter_pillar(filter_name, + pillar_key='acl', + pillarenv=None, + saltenv=None): + ''' + Helper that can be used inside a state SLS, + in order to get the filter configuration given its name. + + filter_name + The name of the filter. + + pillar_key + The root key of the whole policy config. + + pillarenv + Query the master to generate fresh pillar data on the fly, + specifically from the requested pillar environment. + + saltenv + Included only for compatibility with + :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. + ''' + return __salt__['capirca.get_filter_pillar'](filter_name, + pillar_key=pillar_key, + pillarenv=pillarenv, + saltenv=saltenv) + + +def get_term_pillar(filter_name, + term_name, + pillar_key='acl', + pillarenv=None, + saltenv=None): + ''' + Helper that can be used inside a state SLS, + in order to get the term configuration given its name, + under a certain filter uniquely identified by its name. + + filter_name + The name of the filter. + + term_name + The name of the term. + + pillar_key: ``acl`` + The root key of the whole policy config. Default: ``acl``. + + pillarenv + Query the master to generate fresh pillar data on the fly, + specifically from the requested pillar environment. + + saltenv + Included only for compatibility with + :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. + ''' + return __salt__['capirca.get_term_pillar'](filter_name, + term_name, + pillar_key=pillar_key, + pillarenv=pillarenv, + saltenv=saltenv) diff --git a/salt/states/netacl.py b/salt/states/netacl.py index 5c97b48d77ee..b69361c59836 100644 --- a/salt/states/netacl.py +++ b/salt/states/netacl.py @@ -132,6 +132,8 @@ def term(name, merge_pillar: ``False`` Merge the CLI variables with the pillar. Default: ``False``. + The properties specified through the state arguments have higher priority than the pillar. + revision_id Add a comment in the term config having the description for the changes applied. @@ -323,7 +325,7 @@ def term(name, .. code-block:: yaml - edge01.sfo04: + edge01.bjm01: ---------- ID: update_icmp_first_term Function: netacl.term @@ -353,7 +355,7 @@ def term(name, + } + } - Summary for edge01.sfo04 + Summary for edge01.bjm01 ------------ Succeeded: 1 (unchanged=1, changed=1) Failed: 0 @@ -366,19 +368,20 @@ def term(name, .. code-block:: yaml firewall: - block-icmp: - first-term: - protocol: - - icmp - action: reject + - block-icmp: + terms: + - first-term: + protocol: + - icmp + action: reject State SLS example: - .. code-block:: yaml + .. code-block:: jinja {%- set filter_name = 'block-icmp' -%} {%- set term_name = 'first-term' -%} - {%- set my_term_cfg = pillar['acl'][filter_name][term_name] -%} + {%- set my_term_cfg = salt.netacl.get_term_pillar(filter_name, term_name) -%} update_icmp_first_term: netacl.term: @@ -388,9 +391,28 @@ def term(name, - term_name: {{ term_name }} - {{ my_term_cfg | json }} - When passing retrieved pillar data into the state file, it is strongly - recommended to use the json serializer explicitly (`` | json``), - instead of relying on the default Python serializer. + Or directly referencing the pillar keys: + + .. code-block:: yaml + + update_icmp_first_term: + netacl.term: + - filter_name: block-icmp + - filter_options: + - not-interface-specific + - term_name: first-term + - merge_pillar: true + + .. note:: + The first method allows the user to eventually apply complex manipulation + and / or retrieve the data from external services before passing the + data to the state. The second one is more straighforward, for less + complex cases when loading the data directly from the pillar is sufficient. + + .. note:: + When passing retrieved pillar data into the state file, it is strongly + recommended to use the json serializer explicitly (`` | json``), + instead of relying on the default Python serializer. ''' ret = salt.utils.napalm.default_ret(name) test = __opts__['test'] or test @@ -420,6 +442,7 @@ def filter(name, # pylint: disable=redefined-builtin filter_name, filter_options=None, terms=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -449,6 +472,12 @@ def filter(name, # pylint: disable=redefined-builtin If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of terms generated by merging + the terms from ``terms`` with those defined in the pillar (if any): new terms are prepended + at the beginning, while existing ones will preserve the position. To add the new terms + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -461,7 +490,15 @@ def filter(name, # pylint: disable=redefined-builtin :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. merge_pillar: ``False`` - Merge the CLI variables with the pillar. Default: ``False`` + Merge ``terms`` with the corresponding value from the pillar. Default: ``False``. + + .. note:: + By default this state does not merge, to avoid any unexpected behaviours. + + The merge logic depends on the ``prepend`` argument. + + The terms specified through the ``terms`` argument have higher priority + than the pillar. only_lower_merge: ``False`` Specify if it should merge only the terms fields. Otherwise it will try @@ -547,31 +584,47 @@ def filter(name, # pylint: disable=redefined-builtin .. code-block:: yaml acl: - my-filter: - options: - - inet6 - my-term: - source_port: [1234, 1235] - protocol: - - tcp - - udp - source_address: 1.2.3.4 - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept + - my-filter: + options: + - inet6 + terms: + - my-term: + source_port: [1234, 1235] + protocol: + - tcp + - udp + source_address: 1.2.3.4 + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept State SLS Example: + .. code-block:: jinja + + {%- set filter_name = 'my-filter' -%} + {%- set my_filter_cfg = salt.netacl.get_filter_pillar(filter_name, pillar_key='firewall') -%} + my_first_filter_state: + netacl.filter: + - filter_name: {{ filter_name }} + - options: {{ my_filter_cfg['options'] | json }} + - terms: {{ my_filter_cfg['terms'] | json }} + - revision_date: false + - revision_no: 5 + - debug: true + + Or: + .. code-block:: yaml - {% set my_filter_cfg = pillar.get('acl').get('my-filter') -%} - my-filter_state: + my_first_filter_state: netacl.filter: - filter_name: my-filter - - terms: {{ my_filter_cfg | json }} + - merge_pillar: true + - pillar_key: firewall - revision_date: false - revision_no: 5 - debug: true @@ -580,19 +633,27 @@ def filter(name, # pylint: disable=redefined-builtin the configuration chunk referring to ``my-term`` has been ignored as it referred to IPv4 only (from ``source_address`` field). - When passing retrieved pillar data into the state file, it is strongly - recommended to use the json serializer explicitly (`` | json``), - instead of relying on the default Python serializer. + .. note:: + The first method allows the user to eventually apply complex manipulation + and / or retrieve the data from external services before passing the + data to the state. The second one is more straighforward, for less + complex cases when loading the data directly from the pillar is sufficient. + + .. note:: + When passing retrieved pillar data into the state file, it is strongly + recommended to use the json serializer explicitly (`` | json``), + instead of relying on the default Python serializer. ''' ret = salt.utils.napalm.default_ret(name) test = __opts__['test'] or test if not filter_options: filter_options = [] if not terms: - terms = {} + terms = [] loaded = __salt__['netacl.load_filter_config'](filter_name, filter_options=filter_options, terms=terms, + prepend=prepend, pillar_key=pillar_key, pillarenv=pillarenv, saltenv=saltenv, @@ -610,6 +671,7 @@ def filter(name, # pylint: disable=redefined-builtin def managed(name, filters=None, + prepend=True, pillar_key='acl', pillarenv=None, saltenv=None, @@ -630,6 +692,12 @@ def managed(name, If not specified or empty, will try to load the configuration from the pillar, unless ``merge_pillar`` is set as ``False``. + prepend: ``True`` + When ``merge_pillar`` is set as ``True``, the final list of filters generated by merging + the filters from ``filters`` with those defined in the pillar (if any): new filters are prepended + at the beginning, while existing ones will preserve the position. To add the new filters + at the end of the list, set this argument to ``False``. + pillar_key: ``acl`` The key in the pillar containing the default attributes values. Default: ``acl``. @@ -642,7 +710,15 @@ def managed(name, :conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored. merge_pillar: ``False`` - Merge the CLI variables with the pillar. Default: ``False``. + Merge the ``filters`` wil the corresponding values from the pillar. Default: ``False``. + + .. note:: + By default this state does not merge, to avoid any unexpected behaviours. + + The merge logic depends on the ``prepend`` argument. + + The filters specified through the ``filters`` argument have higher priority + than the pillar. only_lower_merge: ``False`` Specify if it should merge only the filters and terms fields. Otherwise it will try @@ -701,23 +777,6 @@ def managed(name, + ** $Revision: 2 $ + ** + */ - + filter block-icmp { - + interface-specific; - + term first-term { - + from { - + protocol icmp; - + } - + then { - + reject; - + } - + } - + } - + /* - + ** $Id: netacl_example $ - + ** $Date: 2017/07/03 $ - + ** $Revision: 2 $ - + ** - + */ + filter my-filter { + interface-specific; + term my-term { @@ -740,6 +799,23 @@ def managed(name, + then accept; + } + } + + /* + + ** $Id: netacl_example $ + + ** $Date: 2017/07/03 $ + + ** $Revision: 2 $ + + ** + + */ + + filter block-icmp { + + interface-specific; + + term first-term { + + from { + + protocol icmp; + + } + + then { + + reject; + + } + + } + + } + } loaded: firewall { @@ -751,16 +827,27 @@ def managed(name, ** $Revision: 2 $ ** */ - filter block-icmp { + filter my-filter { interface-specific; - term first-term { + term my-term { from { - protocol icmp; + source-address { + 1.2.3.4/32; + } + protocol [ tcp udp ]; + source-port [ 1234 1235 ]; } then { reject; } } + term my-other-term { + from { + protocol tcp; + source-port 5678-5680; + } + then accept; + } } } } @@ -773,27 +860,16 @@ def managed(name, ** $Revision: 2 $ ** */ - filter my-filter { + filter block-icmp { interface-specific; - term my-term { + term first-term { from { - source-address { - 1.2.3.4/32; - } - protocol [ tcp udp ]; - source-port [ 1234 1235 ]; + protocol icmp; } then { reject; } } - term my-other-term { - from { - protocol tcp; - source-port 5678-5680; - } - then accept; - } } } } @@ -811,28 +887,30 @@ def managed(name, .. code-block:: yaml firewall: - my-filter: - my-term: - source_port: [1234, 1235] - protocol: - - tcp - - udp - source_address: 1.2.3.4 - action: reject - my-other-term: - source_port: - - [5678, 5680] - protocol: tcp - action: accept - block-icmp: - first-term: - protocol: - - icmp - action: reject + - my-filter: + terms: + - my-term: + source_port: [1234, 1235] + protocol: + - tcp + - udp + source_address: 1.2.3.4 + action: reject + - my-other-term: + source_port: + - [5678, 5680] + protocol: tcp + action: accept + - block-icmp: + terms: + - first-term: + protocol: + - icmp + action: reject Example SLS file: - .. code-block:: yaml + .. code-block:: jinja {%- set fw_filters = pillar.get('firewall', {}) -%} netacl_example: @@ -841,15 +919,34 @@ def managed(name, - revision_no: 2 - debug: true - When passing retrieved pillar data into the state file, it is strongly - recommended to use the json serializer explicitly (`` | json``), - instead of relying on the default Python serializer. + Or: + + .. code-block:: yaml + + netacl_example: + netacl.managed: + - pillar_key: firewall + - merge_pillar: true + - revision_no: 2 + - debug: true + + .. note:: + The first method allows the user to eventually apply complex manipulation + and / or retrieve the data from external services before passing the + data to the state. The second one is more straighforward, for less + complex cases when loading the data directly from the pillar is sufficient. + + .. note:: + When passing retrieved pillar data into the state file, it is strongly + recommended to use the json serializer explicitly (`` | json``), + instead of relying on the default Python serializer. ''' ret = salt.utils.napalm.default_ret(name) test = __opts__['test'] or test if not filters: - filters = {} + filters = [] loaded = __salt__['netacl.load_policy_config'](filters=filters, + prepend=prepend, pillar_key=pillar_key, pillarenv=pillarenv, saltenv=saltenv,