Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions nftables-formula/nftables/files/nftables.j2
Original file line number Diff line number Diff line change
@@ -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 %}
36 changes: 36 additions & 0 deletions nftables-formula/nftables/init.sls
Original file line number Diff line number Diff line change
@@ -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 %}
116 changes: 116 additions & 0 deletions nftables-formula/nftables/macros.jinja
Original file line number Diff line number Diff line change
@@ -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 -%}
9 changes: 9 additions & 0 deletions nftables-formula/nftables/map.jinja
Original file line number Diff line number Diff line change
@@ -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 -%}
56 changes: 56 additions & 0 deletions nftables-formula/pillar.example
Original file line number Diff line number Diff line change
@@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there needs to be a layer for inet/ip/ip6 here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate, do you mean some abstraction which will generate sets for the different families? Currently I just build sets the same way as natively in nftables (see the macros file https://github.com/openSUSE/salt-formulas/pull/61/files/730c44859d4d76bb02a4c3b17dc371161695382f), which intends specifying the family as part of the set type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you decide if a set should go into

table ip6 nat {
}
table ip nat {
}

which is might be needed if you want to use sets in nat rules . those can not live in table inet ...

Copy link
Member Author

@tacerus tacerus Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That depends on where you include the file you define the set in. In the example pillar, a configuration file sets.conf is placed in /etc/nftables.conf.d/salt/passive/, which is then included in the nat table via line 34.

Edit: Maybe I should make it more clear that the key names underneath config are just example file names. The example has sets in a file called sets, but it could just as well be multiple files mysets1, mysets2, .. all containing sets, which can then be included in different tables.

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