From: Dietmar Maurer Date: Tue, 22 Apr 2014 09:45:52 +0000 (+0200) Subject: start API for aliases X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=commitdiff_plain;h=81d574a7f927d2e5c6c25de9752ff88ef57eebb4 start API for aliases Allow comments for aliases. --- diff --git a/src/PVE/API2/Firewall/Aliases.pm b/src/PVE/API2/Firewall/Aliases.pm new file mode 100644 index 0000000..86393e6 --- /dev/null +++ b/src/PVE/API2/Firewall/Aliases.pm @@ -0,0 +1,296 @@ +package PVE::API2::Firewall::AliasesBase; + +use strict; +use warnings; +use PVE::Exception qw(raise raise_param_exc); +use PVE::JSONSchema qw(get_standard_option); + +use PVE::Firewall; + +use base qw(PVE::RESTHandler); + +my $api_properties = { + cidr => { + description => "Network/IP specification in CIDR format.", + type => 'string', format => 'IPv4orCIDR', + }, + name => get_standard_option('pve-fw-alias'), + comment => { + type => 'string', + optional => 1, + } +}; + +sub load_config { + my ($class, $param) = @_; + + die "implement this in subclass"; + + #return ($fw_conf, $rules); +} + +sub save_aliases { + my ($class, $param, $fw_conf, $aliases) = @_; + + die "implement this in subclass"; +} + +my $additional_param_hash = {}; + +sub additional_parameters { + my ($class, $new_value) = @_; + + if (defined($new_value)) { + $additional_param_hash->{$class} = $new_value; + } + + # return a copy + my $copy = {}; + my $org = $additional_param_hash->{$class} || {}; + foreach my $p (keys %$org) { $copy->{$p} = $org->{$p}; } + return $copy; +} + +my $aliases_to_list = sub { + my ($aliases) = @_; + + my $list = []; + foreach my $k (sort keys %$aliases) { + push @$list, $aliases->{$k}; + } + return $list; +}; + +sub register_get_aliases { + my ($class) = @_; + + my $properties = $class->additional_parameters(); + + $class->register_method({ + name => 'get_aliases', + path => '', + method => 'GET', + description => "List aliases", + parameters => { + additionalProperties => 0, + properties => $properties, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => { + name => { type => 'string' }, + cidr => { type => 'string' }, + comment => { + type => 'string', + optional => 1, + }, + digest => get_standard_option('pve-config-digest', { optional => 0} ), + }, + }, + links => [ { rel => 'child', href => "{name}" } ], + }, + code => sub { + my ($param) = @_; + + my ($fw_conf, $aliases) = $class->load_config($param); + + my $list = &$aliases_to_list($aliases); + + return PVE::Firewall::copy_list_with_digest($list); + }}); +} + +sub register_create_alias { + my ($class) = @_; + + my $properties = $class->additional_parameters(); + + $properties->{name} = $api_properties->{name}; + $properties->{cidr} = $api_properties->{cidr}; + $properties->{comment} = $api_properties->{comment}; + + $class->register_method({ + name => 'create_alias', + path => '', + method => 'POST', + description => "Create IP or Network Alias.", + protected => 1, + parameters => { + additionalProperties => 0, + properties => $properties, + }, + returns => { type => "null" }, + code => sub { + my ($param) = @_; + + my ($fw_conf, $aliases) = $class->load_config($param); + + my $name = lc($param->{name}); + + raise_param_exc({ name => "alias '$param->{name}' already exists" }) + if defined($aliases->{$name}); + + my $data = { name => $param->{name}, cidr => $param->{cidr} }; + $data->{comment} = $param->{comment} if $param->{comment}; + + $aliases->{$name} = $data; + + $class->save_aliases($param, $fw_conf, $aliases); + + return undef; + }}); +} + +sub register_read_alias { + my ($class) = @_; + + my $properties = $class->additional_parameters(); + + $properties->{name} = $api_properties->{name}; + $properties->{cidr} = $api_properties->{cidr}; + + $class->register_method({ + name => 'read_alias', + path => '{name}', + method => 'GET', + description => "Read alias.", + parameters => { + additionalProperties => 0, + properties => $properties, + }, + returns => { type => "object" }, + code => sub { + my ($param) = @_; + + my ($fw_conf, $aliases) = $class->load_config($param); + + my $name = lc($param->{name}); + + raise_param_exc({ name => "no such alias" }) + if !defined($aliases->{$name}); + + return $aliases->{$name}; + }}); +} + +sub register_update_alias { + my ($class) = @_; + + my $properties = $class->additional_parameters(); + + $properties->{name} = $api_properties->{name}; + $properties->{cidr} = $api_properties->{cidr}; + $properties->{comment} = $api_properties->{comment}; + $properties->{digest} = get_standard_option('pve-config-digest'); + + $class->register_method({ + name => 'update_alias', + path => '{name}', + method => 'PUT', + description => "Update IP or Network alias.", + protected => 1, + parameters => { + additionalProperties => 0, + properties => $properties, + }, + returns => { type => "null" }, + code => sub { + my ($param) = @_; + + my ($fw_conf, $aliases) = $class->load_config($param); + + my $list = &$aliases_to_list($aliases); + + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($list); + + PVE::Tools::assert_if_modified($digest, $param->{digest}); + + my $name = lc($param->{name}); + + raise_param_exc({ name => "no such alias" }) if !$aliases->{$name}; + + my $data = { name => $param->{name}, cidr => $param->{cidr} }; + $data->{comment} = $param->{comment} if $param->{comment}; + + $aliases->{$name} = $data; + + $class->save_aliases($param, $fw_conf, $aliases); + }}); +} + +sub register_delete_alias { + my ($class) = @_; + + my $properties = $class->additional_parameters(); + + $properties->{name} = $api_properties->{name}; + $properties->{cidr} = $api_properties->{cidr}; + $properties->{digest} = get_standard_option('pve-config-digest'); + + $class->register_method({ + name => 'remove_alias', + path => '{name}', + method => 'DELETE', + description => "Remove IP or Network alias.", + protected => 1, + parameters => { + additionalProperties => 0, + properties => $properties, + }, + returns => { type => "null" }, + code => sub { + my ($param) = @_; + + my ($fw_conf, $aliases) = $class->load_config($param); + + my $list = &$aliases_to_list($aliases); + my (undef, $digest) = PVE::Firewall::copy_list_with_digest($list); + PVE::Tools::assert_if_modified($digest, $param->{digest}); + + my $name = lc($param->{name}); + delete $aliases->{$name}; + + $class->save_aliases($param, $fw_conf, $aliases); + + return undef; + }}); +} + +sub register_handlers { + my ($class) = @_; + + $class->register_get_aliases(); + $class->register_create_alias(); + $class->register_read_alias(); + $class->register_update_alias(); + $class->register_delete_alias(); +} + +package PVE::API2::Firewall::ClusterAliases; + +use strict; +use warnings; + +use base qw(PVE::API2::Firewall::AliasesBase); + +sub load_config { + my ($class, $param) = @_; + + my $fw_conf = PVE::Firewall::load_clusterfw_conf(); + my $aliases = $fw_conf->{aliases}; + + return ($fw_conf, $aliases); +} + +sub save_aliases { + my ($class, $param, $fw_conf, $aliases) = @_; + + $fw_conf->{aliases} = $aliases; + PVE::Firewall::save_clusterfw_conf($fw_conf); +} + +__PACKAGE__->register_handlers(); + +1; diff --git a/src/PVE/API2/Firewall/Cluster.pm b/src/PVE/API2/Firewall/Cluster.pm index d104c34..5f6d77d 100644 --- a/src/PVE/API2/Firewall/Cluster.pm +++ b/src/PVE/API2/Firewall/Cluster.pm @@ -6,6 +6,7 @@ use PVE::Exception qw(raise raise_param_exc raise_perm_exc); use PVE::JSONSchema qw(get_standard_option); use PVE::Firewall; +use PVE::API2::Firewall::Aliases; use PVE::API2::Firewall::Rules; use PVE::API2::Firewall::Groups; use PVE::API2::Firewall::IPSet; @@ -31,6 +32,12 @@ __PACKAGE__->register_method ({ path => 'ipset', }); +__PACKAGE__->register_method ({ + subclass => "PVE::API2::Firewall::ClusterAliases", + path => 'aliases', +}); + + __PACKAGE__->register_method({ name => 'index', path => '', @@ -52,6 +59,7 @@ __PACKAGE__->register_method({ my ($param) = @_; my $result = [ + { name => 'aliases' }, { name => 'rules' }, { name => 'options' }, { name => 'groups' }, diff --git a/src/PVE/API2/Firewall/Makefile b/src/PVE/API2/Firewall/Makefile index acbf9a2..7c5988b 100644 --- a/src/PVE/API2/Firewall/Makefile +++ b/src/PVE/API2/Firewall/Makefile @@ -1,4 +1,5 @@ LIB_SOURCES= \ + Aliases.pm \ IPSet.pm \ Rules.pm \ Cluster.pm \ diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 7ac8a4a..8d25326 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -56,6 +56,14 @@ PVE::JSONSchema::register_standard_option('ipset-name', { maxLength => 20, }); +PVE::JSONSchema::register_standard_option('pve-fw-alias', { + description => "Alias name.", + type => 'string', + pattern => '[A-Za-z][A-Za-z0-9\-\_]+', + minLength => 2, + maxLength => 20, +}); + PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => { description => "Log level.", type => 'string', @@ -1214,9 +1222,10 @@ sub ruleset_generate_cmdstr { die "invalid security group name '$source'\n"; } } elsif ($source =~ m/^${ip_alias_pattern}$/){ - die "no such alias $source\n" if !$cluster_conf->{aliases}->{$source}; - push @cmd, "-s $cluster_conf->{aliases}->{$source}"; - + my $alias = lc($source); + my $e = $cluster_conf->{aliases}->{$alias}; + die "no such alias $source\n" if !$e; + push @cmd, "-s $e->{cidr}"; } elsif ($source =~ m/\-/){ push @cmd, "-m iprange --src-range $source"; @@ -1234,9 +1243,10 @@ sub ruleset_generate_cmdstr { die "invalid security group name '$dest'\n"; } } elsif ($dest =~ m/^${ip_alias_pattern}$/){ - die "no such alias $dest" if !$cluster_conf->{aliases}->{$dest}; - push @cmd, "-d $cluster_conf->{aliases}->{$dest}"; - + my $alias = lc($source); + my $e = $cluster_conf->{aliases}->{$alias}; + die "no such alias $dest" if !$e; + push @cmd, "-d $e->{cidr}"; } elsif ($dest =~ m/^(\d+)\.(\d+).(\d+).(\d+)\-(\d+)\.(\d+).(\d+).(\d+)$/){ push @cmd, "-m iprange --dst-range $dest"; @@ -1942,20 +1952,22 @@ sub parse_clusterfw_option { sub parse_clusterfw_alias { my ($line) = @_; - my ($opt, $value); + # we can add single line comments to the end of the line + my $comment = decode('utf8', $1) if $line =~ s/\s*#\s*(.*?)\s*$//; + if ($line =~ m/^(\S+)\s(\S+)$/) { - $opt = lc($1); - if($2){ - $2 =~ s|/32$||; - pve_verify_ipv4_or_cidr($2) if $2; - $value = $2; - } - } else { - chomp $line; - die "can't parse option '$line'\n"; + my ($name, $cidr) = ($1, $2); + $cidr =~ s|/32$||; + pve_verify_ipv4_or_cidr($cidr); + my $data = { + name => $name, + cidr => $cidr, + }; + $data->{comment} = $comment if $comment; + return $data; } - return ($opt, $value); + return undef; } sub parse_vm_fw_rules { @@ -2063,6 +2075,7 @@ sub parse_cluster_fw_rules { my $res = { rules => [], options => {}, + aliases => {}, groups => {}, group_comments => {}, ipset => {} , @@ -2124,8 +2137,8 @@ sub parse_cluster_fw_rules { warn "$prefix: $@" if $@; } elsif ($section eq 'aliases') { eval { - my ($opt, $value) = parse_clusterfw_alias($line); - $res->{aliases}->{$opt} = $value; + my $data = parse_clusterfw_alias($line); + $res->{aliases}->{lc($data->{name})} = $data; }; warn "$prefix: $@" if $@; } elsif ($section eq 'rules') { @@ -2306,7 +2319,11 @@ my $format_aliases = sub { $raw .= "[ALIASES]\n\n"; foreach my $k (keys %$aliases) { - $raw .= "$k $aliases->{$k}\n"; + my $e = $aliases->{$k}; + $raw .= "$e->{name} $e->{cidr}"; + $raw .= " # " . encode('utf8', $e->{comment}) + if $e->{comment} && $e->{comment} !~ m/^\s*$/; + $raw .= "\n"; } $raw .= "\n"; @@ -2442,10 +2459,11 @@ sub generate_ipset { foreach my $entry (@$options) { my $cidr = $entry->{cidr}; if ($cidr =~ m/^${ip_alias_pattern}$/) { - if ($aliases->{$cidr}) { - $entry->{cidr} = $aliases->{$cidr}; + my $alias = lc($cidr); + if ($aliases->{$alias}) { + $entry->{cidr} = $aliases->{$alias}->{cidr}; } else { - warn "no such alias '$cidr'\n" if !$aliases->{$cidr}; + warn "no such alias '$cidr'\n" if !$aliases->{$alias}; } } $nethash->{$entry->{cidr}} = $entry;