mirror of
https://github.com/webmin/webmin.git
synced 2026-05-06 15:20:29 +01:00
Fix to validate nftables set usage in rules
* Note: Prevent incompatible nftables sets from being used in rule fields. The rule editor now only offers address sets for address matches and port/service sets for port matches, while save and apply paths validate existing set references before writing or loading rules. This avoids nft datatype mismatch errors such as using inet_proto sets with tcp dport.
This commit is contained in:
@@ -104,11 +104,11 @@ if ($table && $table->{'sets'} && ref($table->{'sets'}) eq 'HASH') {
|
||||
my $label = $s;
|
||||
$label .= " ($set->{'type'})" if ($set->{'type'});
|
||||
my $kind = set_type_kind($set->{'type'});
|
||||
if (!$kind || $kind eq 'addr') {
|
||||
if ($kind && $kind eq 'addr') {
|
||||
push(@addr_set_opts, [ $s, $label ]);
|
||||
$addr_set_seen{$s} = 1;
|
||||
}
|
||||
if (!$kind || $kind eq 'port') {
|
||||
if ($kind && $kind eq 'port') {
|
||||
push(@port_set_opts, [ $s, $label ]);
|
||||
$port_set_seen{$s} = 1;
|
||||
}
|
||||
@@ -234,6 +234,7 @@ if (@port_set_opts > 1) {
|
||||
ui_select("dport_set", $dport_set, \@port_set_opts, 1, 0, 1));
|
||||
}
|
||||
print ui_table_row(hlink($text{'edit_dport'}, "dport"), $dport_row);
|
||||
print ui_table_row(undef, ui_note($text{'edit_ports_note'}, 0), 2);
|
||||
|
||||
print ui_table_end();
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<header>Destination port</header>
|
||||
<p>TCP/UDP destination port number or range (e.g., 80 or 1000-2000).</p>
|
||||
<p>TCP/UDP destination port number or range on the local service being reached (e.g., 22, 80 or 1000-2000).</p>
|
||||
<footer>nft(8)</footer>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<header>Source port</header>
|
||||
<p>TCP/UDP source port number or range (e.g., 22 or 1000-2000).</p>
|
||||
<p>TCP/UDP source port number or range on the remote side of the connection (e.g., 22 or 1000-2000).</p>
|
||||
<footer>nft(8)</footer>
|
||||
|
||||
@@ -69,6 +69,7 @@ save_err=Failed to save rule
|
||||
apply_err=Failed to apply configuration
|
||||
apply_enone=No saved nftables tables were found to apply.
|
||||
apply_eexternal=Cannot apply configuration because table $1 is currently marked as externally managed.
|
||||
apply_esettype=Set $1 in table $2 has type $3, but chain $4 uses it for $5. Use ipv4_addr or ipv6_addr sets for address fields, and inet_service sets for port fields.
|
||||
setup_title=Create Ruleset Profile
|
||||
setup_header=Ruleset profile
|
||||
setup_err=Failed to create ruleset profile
|
||||
@@ -170,13 +171,14 @@ edit_proto=Protocol
|
||||
edit_proto_any=Any
|
||||
edit_saddr=Source address
|
||||
edit_daddr=Destination address
|
||||
edit_sport=Source Port
|
||||
edit_dport=Destination Port
|
||||
edit_sport=Source port
|
||||
edit_dport=Destination port
|
||||
edit_set_none=None
|
||||
edit_saddr_set=Use set $1
|
||||
edit_daddr_set=Use set $1
|
||||
edit_sport_set=Use set $1
|
||||
edit_dport_set=Use set $1
|
||||
edit_ports_note=For inbound services such as SSH, match the destination port. Rules are evaluated from top to bottom, so a later drop will not override an earlier accept.
|
||||
edit_icmp_type=ICMP/ICMPv6 type
|
||||
edit_ct_state=Conntrack state
|
||||
edit_tcp_flags=TCP flags
|
||||
@@ -248,6 +250,7 @@ save_raw_empty=Raw rule cannot be empty.
|
||||
save_raw_multiline=Raw rule must be a single line.
|
||||
save_invalid_rule=Raw rule is invalid: $1
|
||||
save_set_missing=Selected set $1 does not exist.
|
||||
save_set_type=Selected set $1 has type $2, which cannot be used for $3.
|
||||
move_err=Failed to move rule
|
||||
move_failed=Failed to move rule: <pre>$1</pre>
|
||||
move_notable=No such table selected
|
||||
|
||||
@@ -958,6 +958,39 @@ foreach my $r (@{$table->{'rules'}}) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
# validate_set_references(&table)
|
||||
# Returns an error if any structured rule uses a set in an incompatible field
|
||||
sub validate_set_references
|
||||
{
|
||||
my ($table) = @_;
|
||||
return if (!$table || ref($table) ne 'HASH');
|
||||
return if (!$table->{'sets'} || ref($table->{'sets'}) ne 'HASH');
|
||||
return if (!$table->{'rules'} || ref($table->{'rules'}) ne 'ARRAY');
|
||||
foreach my $r (@{$table->{'rules'}}) {
|
||||
next if (!$r || ref($r) ne 'HASH');
|
||||
foreach my $check (
|
||||
[ 'saddr', 'addr', text('edit_saddr') ],
|
||||
[ 'daddr', 'addr', text('edit_daddr') ],
|
||||
[ 'sport', 'port', text('edit_sport') ],
|
||||
[ 'dport', 'port', text('edit_dport') ],
|
||||
) {
|
||||
my ($field, $want, $label) = @$check;
|
||||
my $setname = set_name_from_value($r->{$field});
|
||||
next if (!$setname);
|
||||
my $set = $table->{'sets'}->{$setname};
|
||||
next if (!$set);
|
||||
my $kind = set_type_kind($set->{'type'});
|
||||
if (!$kind || $kind ne $want) {
|
||||
my $type = $set->{'type'} || text('set_type_select');
|
||||
return text('apply_esettype', $setname,
|
||||
nft_table_spec($table), $type,
|
||||
$r->{'chain'} || "-", $label);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
# dump_nftables_save(@tables)
|
||||
# Returns a string representation of the firewall rules
|
||||
@@ -1097,6 +1130,8 @@ foreach my $t (@$active) {
|
||||
$active{table_key($t)} = $t;
|
||||
}
|
||||
foreach my $t (@tables) {
|
||||
my $set_err = validate_set_references($t);
|
||||
return $set_err if ($set_err);
|
||||
my $active_table = $active{table_key($t)};
|
||||
if ($active_table && table_is_externally_managed($active_table)) {
|
||||
return text('apply_eexternal', nft_table_spec($t));
|
||||
|
||||
@@ -17,6 +17,21 @@ foreach my $sfield (qw(saddr_set daddr_set sport_set dport_set)) {
|
||||
error(text('save_set_missing', $in{$sfield}));
|
||||
}
|
||||
}
|
||||
foreach my $check (
|
||||
[ 'saddr_set', 'addr', $text{'edit_saddr'} ],
|
||||
[ 'daddr_set', 'addr', $text{'edit_daddr'} ],
|
||||
[ 'sport_set', 'port', $text{'edit_sport'} ],
|
||||
[ 'dport_set', 'port', $text{'edit_dport'} ],
|
||||
) {
|
||||
my ($sfield, $want, $label) = @$check;
|
||||
next if (!$in{$sfield});
|
||||
my $set = $table->{'sets'}->{$in{$sfield}};
|
||||
my $kind = set_type_kind($set->{'type'});
|
||||
if (!$kind || $kind ne $want) {
|
||||
my $type = $set->{'type'} || $text{'set_type_select'};
|
||||
error(text('save_set_type', $in{$sfield}, $type, $label));
|
||||
}
|
||||
}
|
||||
|
||||
sub join_multi_value
|
||||
{
|
||||
|
||||
@@ -54,6 +54,9 @@ $set->{'raw_lines'} ||= [ ];
|
||||
|
||||
$table->{'sets'}->{$name} = $set;
|
||||
|
||||
my $set_err = validate_set_references($table);
|
||||
error($set_err) if ($set_err);
|
||||
|
||||
my $err = save_table_configuration($table, @tables);
|
||||
error(text('set_failed', $err)) if ($err);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user