From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id A3BC493F83 for ; Wed, 21 Feb 2024 13:24:43 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8743516C1D for ; Wed, 21 Feb 2024 13:24:43 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [94.136.29.106]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Wed, 21 Feb 2024 13:24:41 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id CF4AF44489 for ; Wed, 21 Feb 2024 13:24:40 +0100 (CET) From: Dominik Csapak To: pmg-devel@lists.proxmox.com Date: Wed, 21 Feb 2024 13:24:30 +0100 Message-Id: <20240221122439.1281024-5-d.csapak@proxmox.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20240221122439.1281024-1-d.csapak@proxmox.com> References: <20240221122439.1281024-1-d.csapak@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.020 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record T_SCC_BODY_TEXT_LINE -0.01 - Subject: [pmg-devel] [PATCH pmg-api v2 04/10] add rule attributes and/invert (for each relevant type) X-BeenThere: pmg-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Mail Gateway development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 21 Feb 2024 12:24:43 -0000 like with the objectgroups, add an attributes table for groups, and an 'and'/'invert' attribute for each relevant object type (what/when/from/to). This is intended to modify the behaviour for the matching regarding object groups, so that one has more choice in the logical matching. Also adds the new table to cluster sync, backup and factory reset. Signed-off-by: Dominik Csapak --- changes from v1: * add comment for rollback * remove unnecessary transaction for 'load_rules' (we didn't actually load the attributes there) * delete attrubutes on rule delete * add handling to cluster sync, backup and factory reset * use a regex for checking the props in the db instead of using a loop label for early exit src/PMG/API2/ObjectGroupHelpers.pm | 8 ++ src/PMG/API2/Rules.pm | 53 +++++++++++- src/PMG/Backup.pm | 1 + src/PMG/Cluster.pm | 2 + src/PMG/DBTools.pm | 16 ++++ src/PMG/RuleDB.pm | 130 ++++++++++++++++++++++++----- 6 files changed, 186 insertions(+), 24 deletions(-) diff --git a/src/PMG/API2/ObjectGroupHelpers.pm b/src/PMG/API2/ObjectGroupHelpers.pm index a08a6a3..3060157 100644 --- a/src/PMG/API2/ObjectGroupHelpers.pm +++ b/src/PMG/API2/ObjectGroupHelpers.pm @@ -31,6 +31,14 @@ sub format_rule { active => $rule->{active}, direction => $rule->{direction}, }; + my $types = [qw(what when from to)]; + my $attributes = [qw(and invert)]; + for my $type ($types->@*) { + for my $attribute ($attributes->@*) { + my $opt = "${type}-${attribute}"; + $data->{$opt} = $rule->{$opt} if defined($rule->{$opt}); + } + } $cond_create_group->($data, 'from', $from); $cond_create_group->($data, 'to', $to); diff --git a/src/PMG/API2/Rules.pm b/src/PMG/API2/Rules.pm index c48370f..1ebadc2 100644 --- a/src/PMG/API2/Rules.pm +++ b/src/PMG/API2/Rules.pm @@ -149,6 +149,54 @@ my $rule_params = { type => 'boolean', optional => 1, }, + 'what-and' => { + description => "Flag to 'and' combine WHAT group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'what-invert' => { + description => "Flag to invert WHAT group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'when-and' => { + description => "Flag to 'and' combine WHEN group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'when-invert' => { + description => "Flag to invert WHEN group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'from-and' => { + description => "Flag to 'and' combine FROM group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'from-invert' => { + description => "Flag to invert FROM group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'to-and' => { + description => "Flag to 'and' combine TO group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, + 'to-invert' => { + description => "Flag to invert TO group matches.", + type => 'boolean', + default => 0, + optional => 1, + }, }; sub get_rule_params { @@ -203,7 +251,10 @@ __PACKAGE__->register_method ({ my $rule = $rdb->load_rule($id); - for my $key (qw(name active direction priority)) { + my $keys = ["name", "priority"]; + push $keys->@*, keys get_rule_params()->%*; + + for my $key ($keys->@*) { $rule->{$key} = $param->{$key} if defined($param->{$key}); } diff --git a/src/PMG/Backup.pm b/src/PMG/Backup.pm index 9fc91f8..5891afb 100644 --- a/src/PMG/Backup.pm +++ b/src/PMG/Backup.pm @@ -95,6 +95,7 @@ sub dumpdb { dump_table($dbh, 'attribut', $ofh); dump_table($dbh, 'objectgroup_attributes', $ofh); + dump_table($dbh, 'rule_attributes', $ofh); dump_table($dbh, 'object', $ofh, 'object_id_seq', 'id'); dump_table($dbh, 'objectgroup', $ofh, 'objectgroup_id_seq', 'id'); dump_table($dbh, 'rule', $ofh, 'rule_id_seq', 'id'); diff --git a/src/PMG/Cluster.pm b/src/PMG/Cluster.pm index ac50cff..f468618 100644 --- a/src/PMG/Cluster.pm +++ b/src/PMG/Cluster.pm @@ -533,6 +533,7 @@ sub sync_ruledb_from_master { $ldb->do("DELETE FROM Object"); $ldb->do("DELETE FROM Attribut"); $ldb->do("DELETE FROM Objectgroup_Attributes"); + $ldb->do("DELETE FROM Rule_Attributes"); eval { $rdb->begin_work; @@ -545,6 +546,7 @@ sub sync_ruledb_from_master { PMG::DBTools::copy_table($ldb, $rdb, "ObjectGroup"); PMG::DBTools::copy_table($ldb, $rdb, "Object", 'value'); PMG::DBTools::copy_table($ldb, $rdb, "Attribut", 'value'); + PMG::DBTools::copy_table($ldb, $rdb, "Rule_Attributes"); PMG::DBTools::copy_table($ldb, $rdb, "Objectgroup_Attributes"); }; diff --git a/src/PMG/DBTools.pm b/src/PMG/DBTools.pm index 3c8d181..3e814dc 100644 --- a/src/PMG/DBTools.pm +++ b/src/PMG/DBTools.pm @@ -295,6 +295,18 @@ my $userprefs_ctablecmd = <<__EOD; __EOD +my $rule_attributes_cmd = <<__EOD; + CREATE TABLE Rule_Attributes ( + Rule_ID INTEGER NOT NULL, + Name VARCHAR(20) NOT NULL, + Value BYTEA NULL, + PRIMARY KEY (Rule_ID, Name) + ); + + CREATE INDEX Rule_Attributes_Rule_ID_Index ON Rule_Attributes(Rule_ID); + +__EOD + my $object_group_attributes_cmd = <<__EOD; CREATE TABLE Objectgroup_Attributes ( Objectgroup_ID INTEGER NOT NULL, @@ -452,6 +464,8 @@ sub create_ruledb { $virusinfo_stat_ctablecmd; + $rule_attributes_cmd; + $object_group_attributes_cmd; EOD ); @@ -508,6 +522,7 @@ sub upgradedb { 'CStatistic', $cstatistic_ctablecmd, 'ClusterInfo', $clusterinfo_ctablecmd, 'VirusInfo', $virusinfo_stat_ctablecmd, + 'Rule_Attributes', $rule_attributes_cmd, 'Objectgroup_Attributes', $object_group_attributes_cmd, }; @@ -620,6 +635,7 @@ sub init_ruledb { $dbh->do( "DELETE FROM Rule;" ." DELETE FROM RuleGroup;" + ." DELETE FROM Rule_Attributes;" ." DELETE FROM Objectgroup_Attributes;" ." DELETE FROM Attribut WHERE Object_ID NOT IN ($glids);" ." DELETE FROM Object WHERE ID NOT IN ($glids);" diff --git a/src/PMG/RuleDB.pm b/src/PMG/RuleDB.pm index 0b112b4..e5fe56e 100644 --- a/src/PMG/RuleDB.pm +++ b/src/PMG/RuleDB.pm @@ -668,6 +668,35 @@ sub delete_object { return 1; } +sub update_rule_attributes { + my ($self, $rule) = @_; + + my $types = [qw(what when from to)]; + my $attributes = [qw(and invert)]; + + for my $type ($types->@*) { + for my $attribute ($attributes->@*) { + my $prop = "$type-$attribute"; + + # only save the values if they're set to 1 + if ($rule->{$prop}) { + $self->{dbh}->do( + "INSERT INTO Rule_Attributes (Rule_ID, Name, Value) " . + "VALUES (?, ?, ?) ". + "ON CONFLICT (Rule_ID, Name) DO UPDATE SET Value = ?", undef, + $rule->{id}, $prop, $rule->{$prop}, $rule->{$prop}, + ); + } else { + $self->{dbh}->do( + "DELETE FROM Rule_Attributes " . + "WHERE Rule_ID = ? AND Name = ?", undef, + $rule->{id}, $prop, + ); + } + } + } +} + sub save_rule { my ($self, $rule) = @_; @@ -682,28 +711,53 @@ sub save_rule { my $rulename = encode('UTF-8', $rule->{name}); if (defined($rule->{id})) { + $self->{dbh}->begin_work; + + eval { + $self->{dbh}->do( + "UPDATE Rule " . + "SET Name = ?, Priority = ?, Active = ?, Direction = ? " . + "WHERE ID = ?", undef, + $rulename, $rule->{priority}, $rule->{active}, + $rule->{direction}, $rule->{id}); - $self->{dbh}->do( - "UPDATE Rule " . - "SET Name = ?, Priority = ?, Active = ?, Direction = ? " . - "WHERE ID = ?", undef, - $rulename, $rule->{priority}, $rule->{active}, - $rule->{direction}, $rule->{id}); + $self->update_rule_attributes($rule); - return $rule->{id}; + $self->{dbh}->commit; + }; + if (my $err = $@) { + $self->{dbh}->rollback; + syslog('err', $err); + return undef; + } } else { - my $sth = $self->{dbh}->prepare( - "INSERT INTO Rule (Name, Priority, Active, Direction) " . - "VALUES (?, ?, ?, ?);"); + $self->{dbh}->begin_work; + + eval { + my $sth = $self->{dbh}->prepare( + "INSERT INTO Rule (Name, Priority, Active, Direction) " . + "VALUES (?, ?, ?, ?);"); + + $sth->execute($rulename, $rule->priority, $rule->active, + $rule->direction); + + + $rule->{id} = PMG::Utils::lastid($self->{dbh}, 'rule_id_seq'); - $sth->execute($rulename, $rule->priority, $rule->active, - $rule->direction); + $self->update_rule_attributes($rule); - return $rule->{id} = PMG::Utils::lastid($self->{dbh}, 'rule_id_seq'); + $self->{dbh}->commit; + }; + + if (my $err = $@) { + $self->{dbh}->rollback; + syslog('err', $err); + return undef; + } } - return undef; + return $rule->{id}; } sub delete_rule { @@ -718,6 +772,8 @@ sub delete_rule { "WHERE ID = ?", undef, $ruleid); $self->{dbh}->do("DELETE FROM RuleGroup " . "WHERE Rule_ID = ?", undef, $ruleid); + $self->{dbh}->do("DELETE FROM Rule_Attributes " . + "WHERE Rule_ID = ?", undef, $ruleid); $self->{dbh}->commit; }; @@ -829,24 +885,52 @@ sub rule_remove_group { return 1; } +sub load_rule_attributes { + my ($self, $rule) = @_; + + my $types = [qw(what when from to)]; + my $attributes = [qw(and invert)]; + + my $attribute_sth = $self->{dbh}->prepare("SELECT * FROM Rule_Attributes WHERE Rule_ID = ?"); + $attribute_sth->execute($rule->{id}); + + while (my $ref = $attribute_sth->fetchrow_hashref()) { + if ($ref->{name} =~ m/^((?:what|when|from|to)-(?:and|invert))$/) { + my $prop = $1; + $rule->{$prop} = $ref->{value}; + } + } +} + sub load_rule { my ($self, $id) = @_; defined($id) || die "undefined id: ERROR"; - my $sth = $self->{dbh}->prepare( - "SELECT * FROM Rule where id = ? ORDER BY Priority DESC"); + $self->{dbh}->begin_work; - my $rules = (); + my $rule; + + eval { + my $sth = $self->{dbh}->prepare( + "SELECT * FROM Rule where id = ? ORDER BY Priority DESC"); - $sth->execute($id); + $sth->execute($id); - my $ref = $sth->fetchrow_hashref(); - die "rule '$id' does not exist\n" if !defined($ref); + my $ref = $sth->fetchrow_hashref(); + die "rule '$id' does not exist\n" if !defined($ref); + + $rule = PMG::RuleDB::Rule->new($ref->{name}, $ref->{priority}, + $ref->{active}, $ref->{direction}); + $rule->{id} = $ref->{id}; + + $self->load_rule_attributes($rule); + }; + my $err = $@; + + $self->{dbh}->rollback; # finish transaction - my $rule = PMG::RuleDB::Rule->new($ref->{name}, $ref->{priority}, - $ref->{active}, $ref->{direction}); - $rule->{id} = $ref->{id}; + die $err if $err; return $rule; } -- 2.30.2