diff --git a/nftables-formula/nftables/files/nftables.j2 b/nftables-formula/nftables/files/nftables.j2 new file mode 100644 index 00000000..f1a0e083 --- /dev/null +++ b/nftables-formula/nftables/files/nftables.j2 @@ -0,0 +1,45 @@ +{%- from 'nftables/map.jinja' import dir, nft, nft_chains, nft_variables, nft_sets, nft_maps, nft_vmaps -%} +{{ pillar.get('managed_by_salt_formula', '# Managed by the nftables formula') }} + +{#- -#} +{{ nft_variables(config.get('variables', {})) }} + +{#- -#} +{{ nft_sets(config.get('sets', {})) }} + +{#- -#} +{%- if config.get('mode', 'active') == 'passive' %} +{%- if 'chains' in config %} +{{ nft_chains(config['chains']) }} +{%- endif %} +{%- if 'maps' in config %} +{{ nft_maps(config['maps']) }} +{%- endif -%} +{%- if 'vmaps' in config %} +{{ nft_vmaps(config['vmaps']) }} +{%- endif %} +{%- endif %} + +{#- -#} +{%- for table, table_config in config.get('tables', {}).items() %} +table {{ table }}{{ ' ' ~ table_config['type'] if 'type' in table_config else '' }} { + {%- if 'include' in table_config %} + {%- if table_config['include'] is string %} + {%- set table_includes = [table_config['include']] %} + {%- elif table_config['include'] is iterable and table_config['include'] is not mapping %} + {%- set table_includes = table_config['include'] %} + {%- else %} + {%- do salt.log.error('nftables: illegal value for include in table ' ~ table) %} + {%- endif %} + {%- for table_include in table_includes %} + {%- if table_include in nft and nft[table_include].get('mode') == 'passive' %} + {%- set lowinclude = dir ~ '/passive/' ~ table_include ~ '.conf' -%} + {%- else %} + {%- set lowinclude = table_include -%} + {%- endif %} + include "{{ lowinclude }}" + {%- endfor %} + {%- endif -%} + {{ nft_chains(table_config.get('chains', {})) | indent(2) }} +} +{%- endfor %} diff --git a/nftables-formula/nftables/init.sls b/nftables-formula/nftables/init.sls new file mode 100644 index 00000000..d8afba94 --- /dev/null +++ b/nftables-formula/nftables/init.sls @@ -0,0 +1,36 @@ +{%- from 'nftables/map.jinja' import nft, dir %} + +#nftables_packages: +# pkg.installed: +# - pkgs: +# - nftables-service + +nftables_directory: + file.directory: + - names: + - {{ dir }}/active + - {{ dir }}/passive + - makedirs: true + +nftables_config_base: + file.managed: + - name: /etc/nftables.conf + - contents: | + #!/usr/sbin/nft -f + include "{{ dir }}/active/*.conf" + +{%- for category, config in nft.items() %} + +nftables_config_{{ category }}: + file.managed: + - name: + {%- set file = category ~ '.conf' -%} + {%- if 'priority' in config -%} + {%- set file = config['priority'] ~ '_' ~ file -%} + {%- endif -%} + {{ ' ' ~ dir }}/{{ config.get('mode', 'active') }}/{{ file }} + - template: jinja + - source: salt://{{ slspath }}/files/nftables.j2 + - context: + config: {{ config }} +{%- endfor %} diff --git a/nftables-formula/nftables/macros.jinja b/nftables-formula/nftables/macros.jinja new file mode 100644 index 00000000..6a54aa4c --- /dev/null +++ b/nftables-formula/nftables/macros.jinja @@ -0,0 +1,116 @@ +{%- macro nft_sets(sets) -%} +{%- for set, set_config in sets.items() %} +set {{ set }} = + {%- if 'type' in set_config %} + type {{ set_config['type'] }} + {%- endif %} + {{ nft_flags(set_config.get('flags', none)) }} + {%- if set_config.get('auto-merge', False) %} + auto-merge + {%- endif %} + {%- if set_config.get('counter', False) %} + counter + {%- endif %} + {%- if 'policy' in set_config %} + policy {{ set_config['policy'] }} + {%- endif %} + {%- if 'timeout' in set_config %} + timeout {{ set_config['timeout'] }} + {%- endif %} + elements = { + {%- for element in set_config.get('elements', []) %} + {{ element }}, + {%- endfor %} + } +{%- endfor %} +{%- endmacro -%} + +{%- macro nft_variables(variables) -%} +{%- for variable, contents in variables.items() %} +define {{ variable }} ={{ ' ' }} + {%- if contents is string -%} + {{ contents }} + {%- elif contents is iterable and contents is not mapping -%} + { + {%- for value in contents %} + {{ value }}, + {%- endfor %} +} + {%- endif -%} +{%- endfor -%} +{%- endmacro -%} + +{%- macro nft_chains(chains) -%} +{%- for chain, chain_config in chains.items() %} +chain {{ chain }} { + {%- if 'type' in chain_config %} + type {{ chain_config['type'] }} + {%- if 'hook' in chain_config -%} + {{ ' hook ' ~ chain_config['hook'] }} + {%- endif -%} + {%- if 'priority' in chain_config -%} + {{ ' priority ' ~ chain_config['priority'] }} + {%- endif -%} + {%- endif %} + {%- if 'policy' in chain_config %} + policy {{ chain_config['policy'] }} + {%- endif %} + {%- if 'jump' in chain_config %} + {%- if chain_config['jump'] is string %} + {%- set chain_jumps = [chain_config['jump']] %} + {%- elif chain_config['jump'] is iterable and chain_config['jump'] is not mapping %} + {%- set chain_jumps = chain_config['jump'] %} + {%- else %} + {%- do salt.log.error('nftables: illegal value for jump in chain ' ~ chain) %} + {%- endif %} + {%- for chain_jump in chain_jumps %} + jump {{ chain_jump }} + {%- endfor %} + {%- endif %} + {%- if 'maps' in chain_config %} + {{ nft_maps(chain_config['maps']) | indent(2) }} + {%- endif -%} + {%- if 'vmaps' in chain_config %} + {{ nft_vmaps(chain_config['vmaps']) | indent(2) }} + {%- endif %} + {%- for entry in chain_config.get('rules', []) %} + {{ entry }} + {%- endfor %} + {%- if chain_config.get('counter', False) %} + counter + {%- endif %} + {%- if 'log' in chain_config %} + {%- set log_config = chain_config['log'] %} + log{{ ' prefix "' ~ log_config['prefix'] ~ '"' if 'prefix' in log_config else '' }}{{ nft_flags(log_config.get('flags', none), ' ') }} + {%- endif %} +} +{%- endfor -%} +{%- endmacro -%} + +{%- macro nft_flags(flags, prefix='') -%} +{%- if flags is not none -%} +{%- if flags is string or ( flags is iterable and flags is not mapping ) -%} +{{ prefix }}flags {{ flags if flags is string else ' '.join(flags) }} +{%- else -%} +{%- do salt.log.error('nftables: illegal flags') -%} +{%- endif -%} +{%- endif %} +{%- endmacro -%} + +{%- macro _nft_maps(maps, type) -%} +{%- for map, map_config in maps.items() %} +{{ map }} {{ type }} { + {%- for map_from, map_to in map_config.items() %} + {{ map_from }} : {{ map_to }} + {%- endfor %} +} +{%- endfor %} +{%- endmacro -%} + +{%- macro nft_maps(maps) -%} +{{ _nft_maps(maps, 'map') }} +{%- endmacro -%} + +{%- macro nft_vmaps(vmaps) -%} +{{ _nft_maps(vmaps, 'vmap') }} +{%- endmacro -%} \ No newline at end of file diff --git a/nftables-formula/nftables/map.jinja b/nftables-formula/nftables/map.jinja new file mode 100644 index 00000000..0788632b --- /dev/null +++ b/nftables-formula/nftables/map.jinja @@ -0,0 +1,9 @@ +{%- set mypillar = salt['pillar.get']('nftables', {}) -%} +{%- set nft = mypillar.get('config', {}) -%} +{%- set dir = '/etc/nftables.conf.d/salt' -%} +{%- from 'nftables/macros.jinja' import nft_chains, nft_variables, nft_sets, nft_maps, nft_vmaps -%} +{%- set nft_chains = nft_chains -%} +{%- set nft_sets = nft_sets -%} +{%- set nft_variables = nft_variables -%} +{%- set nft_maps = nft_maps -%} +{%- set nft_vmaps = nft_vmaps -%} diff --git a/nftables-formula/pillar.example b/nftables-formula/pillar.example new file mode 100644 index 00000000..3559c23f --- /dev/null +++ b/nftables-formula/pillar.example @@ -0,0 +1,56 @@ +nftables: + config: + testchain: + # passive configurations will not be included in the top nftables.conf and can be referenced in lower "includes" + mode: passive + chains: + forward: + type: filter + rules: + - ct state established, related accept + sets: + mode: passive + sets: + myv6net: + type: ipv6_addr + flags: interval + auto-merge: true + elements: + - 127.0.0.1/128 + - $mylan + variables: + priority: 01 + variables: + vpn_ranges: + - 192.168.0.0/24 + - 192.168.1.0/24 + myhost: 192.168.0.1/32 + tables: + priority: 02 + tables: + nat: + # if the include matches the name of a passive configuration, it will be expanded to its path + # multiple includes can be passed using a list + include: sets + inet: + type: filter + chains: + input: + type: filter + hook: input + priority: 0 + policy: drop + counter: true + log: + prefix: Denied Inbound + flags: all + maps: + dport: + 80: 127.0.0.1 + vmaps: + iif: + eth0: jump testchain + rules: + - iif external jump input_external + - ct state established, related accept + - ct state invalid accept