diff --git a/roles/database/files/sql/creation/fworch-create-constraints.sql b/roles/database/files/sql/creation/fworch-create-constraints.sql index 3efb515f8..b605f75cf 100755 --- a/roles/database/files/sql/creation/fworch-create-constraints.sql +++ b/roles/database/files/sql/creation/fworch-create-constraints.sql @@ -14,6 +14,7 @@ Alter Table "changelog_service" add Constraint "alt_key_changelog_service" UNIQU Alter Table "changelog_user" add Constraint "alt_key_changelog_user" UNIQUE ("abs_change_id"); Alter Table "import_changelog" add Constraint "Alter_Key14" UNIQUE ("import_changelog_nr","control_id"); Alter Table "import_control" add Constraint "control_id_stop_time_unique" UNIQUE ("stop_time","control_id"); +ALTER TABLE ldap_connection ADD CONSTRAINT ldap_connection_server_unique UNIQUE (ldap_server, ldap_port, active); Alter Table "object" add Constraint "obj_altkey" UNIQUE ("mgm_id","zone_id","obj_uid","obj_create"); ALTER TABLE object ADD CONSTRAINT object_obj_ip_is_host CHECK (is_single_ip(obj_ip)); ALTER TABLE object ADD CONSTRAINT object_obj_ip_end_is_host CHECK (is_single_ip(obj_ip_end)); diff --git a/roles/database/files/sql/idempotent/fworch-api-funcs.sql b/roles/database/files/sql/idempotent/fworch-api-funcs.sql index 3ae33e4f8..c9c89fa30 100644 --- a/roles/database/files/sql/idempotent/fworch-api-funcs.sql +++ b/roles/database/files/sql/idempotent/fworch-api-funcs.sql @@ -608,27 +608,44 @@ $$ LANGUAGE 'plpgsql' STABLE; CREATE OR REPLACE FUNCTION public.get_rulebase_for_owner(rulebase_row rulebase, ownerid integer) - RETURNS SETOF rule - LANGUAGE plpgsql - STABLE -AS $function$ - BEGIN - RETURN QUERY - SELECT r.* FROM rule r - LEFT JOIN rule_from rf ON (r.rule_id=rf.rule_id) - LEFT JOIN objgrp_flat rf_of ON (rf.obj_id=rf_of.objgrp_flat_id) - LEFT JOIN object rf_o ON (rf_of.objgrp_flat_member_id=rf_o.obj_id) - LEFT JOIN owner_network ON - (ip_ranges_overlap(rf_o.obj_ip, rf_o.obj_ip_end, ip, ip_end, rf.negated != r.rule_src_neg)) - WHERE r.rulebase_id = rulebase_row.id AND owner_id = ownerid AND rule_head_text IS NULL - UNION - SELECT r.* FROM rule r - LEFT JOIN rule_to rt ON (r.rule_id=rt.rule_id) - LEFT JOIN objgrp_flat rt_of ON (rt.obj_id=rt_of.objgrp_flat_id) - LEFT JOIN object rt_o ON (rt_of.objgrp_flat_member_id=rt_o.obj_id) - LEFT JOIN owner_network ON - (ip_ranges_overlap(rt_o.obj_ip, rt_o.obj_ip_end, ip, ip_end, rt.negated != r.rule_dst_neg)) - WHERE r.rulebase_id = rulebase_row.id AND owner_id = ownerid AND rule_head_text IS NULL - ORDER BY rule_name; - END; -$function$ +RETURNS SETOF rule +LANGUAGE sql +STABLE +AS $$ + SELECT r.* + FROM rule r + WHERE r.rulebase_id = rulebase_row.id + AND r.rule_head_text IS NULL + AND ( + r.rule_id IN ( + SELECT rf.rule_id + FROM rule_from rf + JOIN objgrp_flat of1 ON of1.objgrp_flat_id = rf.obj_id + JOIN object o1 ON o1.obj_id = of1.objgrp_flat_member_id + JOIN owner_network onet1 ON onet1.owner_id = ownerid + WHERE rf.rule_id = r.rule_id + AND ip_ranges_overlap( + o1.obj_ip, o1.obj_ip_end, + onet1.ip, onet1.ip_end, + rf.negated <> r.rule_src_neg + ) + ) + OR + r.rule_id IN ( + SELECT rt.rule_id + FROM rule_to rt + JOIN objgrp_flat of2 ON of2.objgrp_flat_id = rt.obj_id + JOIN object o2 ON o2.obj_id = of2.objgrp_flat_member_id + JOIN owner_network onet2 ON onet2.owner_id = ownerid + WHERE rt.rule_id = r.rule_id + AND ip_ranges_overlap( + o2.obj_ip, o2.obj_ip_end, + onet2.ip, onet2.ip_end, + rt.negated <> r.rule_dst_neg + ) + ) + ) + ORDER BY r.rule_name ASC; +$$; + + diff --git a/roles/database/files/sql/idempotent/fworch-encryption.sql b/roles/database/files/sql/idempotent/fworch-encryption.sql index cd1e82d88..6d0e75f03 100644 --- a/roles/database/files/sql/idempotent/fworch-encryption.sql +++ b/roles/database/files/sql/idempotent/fworch-encryption.sql @@ -130,34 +130,59 @@ $$ LANGUAGE plpgsql; SELECT * FROM encryptPasswords (getMainKey()); -- function for adding local ldap data with encrypted pwds into ldap_connection + +-- assumes ldap_connection(active boolean NOT NULL [DEFAULT true]) + CREATE OR REPLACE FUNCTION insertLocalLdapWithEncryptedPasswords( - serverName TEXT, - port INTEGER, - userSearchPath TEXT, - roleSearchPath TEXT, - groupSearchPath TEXT, - groupWritePath TEXT, - tenantLevel INTEGER, - searchUser TEXT, - searchUserPwd TEXT, - writeUser TEXT, - writeUserPwd TEXT, - ldapType INTEGER -) RETURNS VOID AS $$ -DECLARE - t_key TEXT; - t_encryptedReadPwd TEXT; - t_encryptedWritePwd TEXT; -BEGIN - IF NOT EXISTS (SELECT * FROM ldap_connection WHERE ldap_server = serverName) - THEN - SELECT INTO t_key * FROM getMainKey(); - SELECT INTO t_encryptedReadPwd * FROM encryptText(searchUserPwd, t_key); - SELECT INTO t_encryptedWritePwd * FROM encryptText(writeUserPwd, t_key); - INSERT INTO ldap_connection - (ldap_server, ldap_port, ldap_searchpath_for_users, ldap_searchpath_for_roles, ldap_searchpath_for_groups, ldap_writepath_for_groups, - ldap_tenant_level, ldap_search_user, ldap_search_user_pwd, ldap_write_user, ldap_write_user_pwd, ldap_type) - VALUES (serverName, port, userSearchPath, roleSearchPath, groupSearchPath, groupWritePath, tenantLevel, searchUser, t_encryptedReadPwd, writeUser, t_encryptedWritePwd, ldapType); - END IF; -END; -$$ LANGUAGE plpgsql; + serverName text, + port integer, + userSearchPath text, + roleSearchPath text, + groupSearchPath text, + groupWritePath text, + tenantLevel integer, + searchUser text, + searchUserPwd text, + writeUser text, + writeUserPwd text, + ldapType integer, + activeFlag boolean DEFAULT true -- ← include active explicitly +) RETURNS void +LANGUAGE sql +VOLATILE +SECURITY DEFINER +SET search_path = public +AS $$ +WITH k AS (SELECT getMainKey() AS mk) +INSERT INTO ldap_connection ( + ldap_server, + ldap_port, + ldap_searchpath_for_users, + ldap_searchpath_for_roles, + ldap_searchpath_for_groups, + ldap_writepath_for_groups, + ldap_tenant_level, + ldap_search_user, + ldap_search_user_pwd, + ldap_write_user, + ldap_write_user_pwd, + ldap_type, + active +) +SELECT + serverName, + port, + userSearchPath, + roleSearchPath, + groupSearchPath, + groupWritePath, + tenantLevel, + searchUser, + encryptText(searchUserPwd, k.mk), + writeUser, + encryptText(writeUserPwd, k.mk), + ldapType, + activeFlag +FROM k +ON CONFLICT (ldap_server, ldap_port, active) DO NOTHING; +$$; diff --git a/roles/database/files/upgrade/9.0.sql b/roles/database/files/upgrade/9.0.sql index 725ad8031..a9082bc58 100644 --- a/roles/database/files/upgrade/9.0.sql +++ b/roles/database/files/upgrade/9.0.sql @@ -49,37 +49,66 @@ $$ LANGUAGE plpgsql VOLATILE; Alter table "ldap_connection" ADD COLUMN IF NOT EXISTS "ldap_writepath_for_groups" Varchar; +ALTER TABLE ldap_connection ADD CONSTRAINT ldap_connection_server_unique UNIQUE (ldap_server, ldap_port, active); + +-- assumes ldap_connection(active boolean NOT NULL [DEFAULT true]) + +-- assumes ldap_connection(active boolean NOT NULL [DEFAULT true]) + CREATE OR REPLACE FUNCTION insertLocalLdapWithEncryptedPasswords( - serverName TEXT, - port INTEGER, - userSearchPath TEXT, - roleSearchPath TEXT, - groupSearchPath TEXT, - groupWritePath TEXT, - tenantLevel INTEGER, - searchUser TEXT, - searchUserPwd TEXT, - writeUser TEXT, - writeUserPwd TEXT, - ldapType INTEGER -) RETURNS VOID AS $$ -DECLARE - t_key TEXT; - t_encryptedReadPwd TEXT; - t_encryptedWritePwd TEXT; -BEGIN - IF NOT EXISTS (SELECT * FROM ldap_connection WHERE ldap_server = serverName) - THEN - SELECT INTO t_key * FROM getMainKey(); - SELECT INTO t_encryptedReadPwd * FROM encryptText(searchUserPwd, t_key); - SELECT INTO t_encryptedWritePwd * FROM encryptText(writeUserPwd, t_key); - INSERT INTO ldap_connection - (ldap_server, ldap_port, ldap_searchpath_for_users, ldap_searchpath_for_roles, ldap_searchpath_for_groups, ldap_writepath_for_groups, - ldap_tenant_level, ldap_search_user, ldap_search_user_pwd, ldap_write_user, ldap_write_user_pwd, ldap_type) - VALUES (serverName, port, userSearchPath, roleSearchPath, groupSearchPath, groupWritePath, tenantLevel, searchUser, t_encryptedReadPwd, writeUser, t_encryptedWritePwd, ldapType); - END IF; -END; -$$ LANGUAGE plpgsql; + serverName text, + port integer, + userSearchPath text, + roleSearchPath text, + groupSearchPath text, + groupWritePath text, + tenantLevel integer, + searchUser text, + searchUserPwd text, + writeUser text, + writeUserPwd text, + ldapType integer, + activeFlag boolean DEFAULT true -- ← include active explicitly +) RETURNS void +LANGUAGE sql +VOLATILE +SECURITY DEFINER +SET search_path = public +AS $$ +WITH k AS (SELECT getMainKey() AS mk) +INSERT INTO ldap_connection ( + ldap_server, + ldap_port, + ldap_searchpath_for_users, + ldap_searchpath_for_roles, + ldap_searchpath_for_groups, + ldap_writepath_for_groups, + ldap_tenant_level, + ldap_search_user, + ldap_search_user_pwd, + ldap_write_user, + ldap_write_user_pwd, + ldap_type, + active +) +SELECT + serverName, + port, + userSearchPath, + roleSearchPath, + groupSearchPath, + groupWritePath, + tenantLevel, + searchUser, + encryptText(searchUserPwd, k.mk), + writeUser, + encryptText(writeUserPwd, k.mk), + ldapType, + activeFlag +FROM k +ON CONFLICT (ldap_server, ldap_port, active) DO NOTHING; +$$; + -- 8.7.2 @@ -463,8 +492,6 @@ Create table IF NOT EXISTS "rulebase" "removed" BIGINT ); --- ALTER TABLE "rulebase" ADD COLUMN IF NOT EXISTS "uid" Varchar NOT NULL; - ALTER TABLE "rulebase" DROP CONSTRAINT IF EXISTS "fk_rulebase_mgm_id" CASCADE; Alter table "rulebase" add CONSTRAINT fk_rulebase_mgm_id foreign key ("mgm_id") references "management" ("mgm_id") on update restrict on delete cascade; @@ -1034,31 +1061,46 @@ Alter Table "rule" ADD Constraint "rule_unique_mgm_id_rule_uid_rule_create_xlate -- rewrite get_rulebase_for_owner to work with rulebase instead of device CREATE OR REPLACE FUNCTION public.get_rulebase_for_owner(rulebase_row rulebase, ownerid integer) - RETURNS SETOF rule - LANGUAGE plpgsql - STABLE -AS -$function$ - BEGIN - RETURN QUERY - SELECT r.* FROM rule r - LEFT JOIN rule_from rf ON (r.rule_id=rf.rule_id) - LEFT JOIN objgrp_flat rf_of ON (rf.obj_id=rf_of.objgrp_flat_id) - LEFT JOIN object rf_o ON (rf_of.objgrp_flat_member_id=rf_o.obj_id) - LEFT JOIN owner_network ON - (ip_ranges_overlap(rf_o.obj_ip, rf_o.obj_ip_end, ip, ip_end, rf.negated != r.rule_src_neg)) - WHERE r.rulebase_id = rulebase_row.id AND owner_id = ownerid AND rule_head_text IS NULL - UNION - SELECT r.* FROM rule r - LEFT JOIN rule_to rt ON (r.rule_id=rt.rule_id) - LEFT JOIN objgrp_flat rt_of ON (rt.obj_id=rt_of.objgrp_flat_id) - LEFT JOIN object rt_o ON (rt_of.objgrp_flat_member_id=rt_o.obj_id) - LEFT JOIN owner_network ON - (ip_ranges_overlap(rt_o.obj_ip, rt_o.obj_ip_end, ip, ip_end, rt.negated != r.rule_dst_neg)) - WHERE r.rulebase_id = rulebase_row.id AND owner_id = ownerid AND rule_head_text IS NULL - ORDER BY rule_name; - END; -$function$; +RETURNS SETOF rule +LANGUAGE sql +STABLE +AS $$ + SELECT r.* + FROM rule r + WHERE r.rulebase_id = rulebase_row.id + AND r.rule_head_text IS NULL + AND ( + r.rule_id IN ( + SELECT rf.rule_id + FROM rule_from rf + JOIN objgrp_flat of1 ON of1.objgrp_flat_id = rf.obj_id + JOIN object o1 ON o1.obj_id = of1.objgrp_flat_member_id + JOIN owner_network onet1 ON onet1.owner_id = ownerid + WHERE rf.rule_id = r.rule_id + AND ip_ranges_overlap( + o1.obj_ip, o1.obj_ip_end, + onet1.ip, onet1.ip_end, + rf.negated <> r.rule_src_neg + ) + ) + OR + r.rule_id IN ( + SELECT rt.rule_id + FROM rule_to rt + JOIN objgrp_flat of2 ON of2.objgrp_flat_id = rt.obj_id + JOIN object o2 ON o2.obj_id = of2.objgrp_flat_member_id + JOIN owner_network onet2 ON onet2.owner_id = ownerid + WHERE rt.rule_id = r.rule_id + AND ip_ranges_overlap( + o2.obj_ip, o2.obj_ip_end, + onet2.ip, onet2.ip_end, + rt.negated <> r.rule_dst_neg + ) + ) + ) + ORDER BY r.rule_name ASC; +$$; + -- drop only after migration diff --git a/roles/lib/files/FWO.Api.Client/Queries/Queries.cs b/roles/lib/files/FWO.Api.Client/Queries/Queries.cs index a9b6ebac6..20e307aef 100644 --- a/roles/lib/files/FWO.Api.Client/Queries/Queries.cs +++ b/roles/lib/files/FWO.Api.Client/Queries/Queries.cs @@ -6,7 +6,6 @@ namespace FWO.Api.Client.Queries { public class Queries { - // protected static readonly string QueryPath = AppDomain.CurrentDomain.BaseDirectory + "../../../../../../common/files/fwo-api-calls/"; protected static readonly string QueryPath = GlobalConst.kFwoBaseDir + "/fwo-api-calls/"; protected static string GetQueryText(string relativeQueryFileName) diff --git a/roles/lib/files/FWO.Compliance/ComplianceCheck.cs b/roles/lib/files/FWO.Compliance/ComplianceCheck.cs index f88696df7..8d8ab6fc6 100644 --- a/roles/lib/files/FWO.Compliance/ComplianceCheck.cs +++ b/roles/lib/files/FWO.Compliance/ComplianceCheck.cs @@ -95,7 +95,7 @@ public async Task CheckAll() Log.TryWriteLog(LogType.Info, "Compliance Check", $"Policy without criteria. Compliance check not possible.", LocalSettings.ComplianceCheckVerbose); return; } - + foreach (var criterion in Policy.Criteria) { Log.TryWriteLog(LogType.Info, "Compliance Check", $"Criterion: {criterion.Content.Name} ({criterion.Content.CriterionType})", LocalSettings.ComplianceCheckVerbose); @@ -124,13 +124,13 @@ public async Task CheckAll() else { Log.WriteError("Compliance Check", "Could not generate compliance report."); - } + } } catch (System.Exception e) { Log.WriteError("Compliance Check", "Error while checking for compliance violations.", e); } - + } /// @@ -311,7 +311,7 @@ private Task> GetViolationsForRemove(List existin return Task.FromResult(violationsForUpdate); } - + private async Task CheckRuleComplianceForAllRules() { int nonCompliantRules = 0; @@ -351,7 +351,7 @@ public async Task CheckRuleCompliance(Rule rule) .GetResolvedNetworkLocations(rule.Tos) .Select(to => to.Object) .ToList(); - + foreach (var criterion in (Policy?.Criteria ?? []).Select(c => c.Content)) { switch (criterion.CriterionType) @@ -397,8 +397,16 @@ private bool CheckMatrixCompliance(Rule rule, List<(NetworkObject networkObject, { foreach (ComplianceNetworkZone sourceNetworkZone in sourceZone.networkZones ?? []) { - foreach ((NetworkObject networkObject, List? networkZones) destinationZone in destinationZones) + if (destinationZones.Count() == 0) { +<<<<<<< HEAD + continue; + } + + if (!CheckZonePairCompliance(rule, matrixCriterion, sourceZone, destinationZone)) + { + ruleIsCompliant = false; +======= foreach (ComplianceNetworkZone destinationNetworkZone in destinationZone.networkZones ?? []) { if (!sourceNetworkZone.CommunicationAllowedTo(destinationNetworkZone)) @@ -416,6 +424,7 @@ private bool CheckMatrixCompliance(Rule rule, List<(NetworkObject networkObject, ruleIsCompliant = false; } } +>>>>>>> 1b3ee8da1a918500b019ad9add252d9dfd540d52 } } } @@ -423,7 +432,91 @@ private bool CheckMatrixCompliance(Rule rule, List<(NetworkObject networkObject, return ruleIsCompliant; } +<<<<<<< HEAD + private bool HandleUnresolvedZones(Rule rule, IEnumerable<(NetworkObject networkObject, List? networkZones)> zones, bool isSource) + { + bool zonesResolvable = true; + + foreach ((NetworkObject networkObject, List? networkZones) zone in zones) + { + if (zone.networkZones != null) + { + continue; + } + + CreateUnresolvableZoneViolation(rule, zone.networkObject, isSource); + zonesResolvable = false; + } + + return zonesResolvable; + } + + private bool CheckZonePairCompliance(Rule rule, ComplianceCriterion? matrixCriterion, (NetworkObject networkObject, List? networkZones) sourceZone, (NetworkObject networkObject, List? networkZones) destinationZone) + { + bool zonesCompliant = true; + + foreach (ComplianceNetworkZone sourceNetworkZone in sourceZone.networkZones!) + { + foreach (ComplianceNetworkZone destinationNetworkZone in destinationZone.networkZones!) + { + if (sourceNetworkZone.CommunicationAllowedTo(destinationNetworkZone)) + { + continue; + } + + AddMatrixViolation(rule, matrixCriterion, sourceZone.networkObject, sourceNetworkZone, destinationZone.networkObject, destinationNetworkZone); + zonesCompliant = false; + } + } + + return zonesCompliant; + } + + private void AddMatrixViolation(Rule rule, ComplianceCriterion? matrixCriterion, NetworkObject source, ComplianceNetworkZone sourceZone, NetworkObject destination, ComplianceNetworkZone destinationZone) + { + ComplianceCheckResult complianceCheckResult = new(rule, ComplianceViolationType.MatrixViolation) + { + Criterion = matrixCriterion, + Source = source, + SourceZone = sourceZone, + Destination = destination, + DestinationZone = destinationZone + }; + + ComplianceViolation? violation = TryCreateViolation(ComplianceViolationType.MatrixViolation, rule, complianceCheckResult); + + + + ComplianceReport!.Violations.Add(violation!); + } + + private void CreateUnresolvableZoneViolation(Rule rule, NetworkObject networkObject, bool isSource) + { + ComplianceCheckResult complianceCheckResult = new(rule, ComplianceViolationType.MatrixViolation) + { + Criterion = _policy?.Criteria.FirstOrDefault(c => c.Content.CriterionType == CriterionType.Matrix.ToString())?.Content, + }; + + if (isSource) + { + complianceCheckResult.Source = networkObject; + } + else + { + complianceCheckResult.Destination = networkObject; + } + + ComplianceViolation? violation = TryCreateViolation(ComplianceViolationType.MatrixViolation, rule, complianceCheckResult); + + + + ComplianceReport!.Violations.Add(violation!); + } + + private ComplianceViolation? TryCreateViolation(ComplianceViolationType violationType, Rule rule, ComplianceCheckResult complianceCheckResult) +======= private void CreateViolation(ComplianceViolationType violationType, Rule rule, ComplianceCheckResult complianceCheckResult) +>>>>>>> 1b3ee8da1a918500b019ad9add252d9dfd540d52 { ComplianceViolation violation = new() { @@ -438,11 +531,39 @@ private void CreateViolation(ComplianceViolationType violationType, Rule rule, C { case ComplianceViolationType.MatrixViolation: +<<<<<<< HEAD + // Workaround!! TODO: Check compliance per criterion and transfer criterion id through the methods + + violation.CriterionId = _policy?.Criteria + .FirstOrDefault(criterionWrapper => criterionWrapper.Content.CriterionType == "Matrix")? + .Content.Id ?? 0; + +======= +>>>>>>> 1b3ee8da1a918500b019ad9add252d9dfd540d52 if (complianceCheckResult.Source is NetworkObject s && complianceCheckResult.Destination is NetworkObject d) { string sourceString = GetNwObjectString(s); string destinationString = GetNwObjectString(d); +<<<<<<< HEAD + violation.Details = $"Matrix violation: {sourceString} (Zone: {complianceCheckResult.SourceZone?.Name ?? ""}) -> {destinationString} (Zone: {complianceCheckResult.DestinationZone?.Name ?? ""})"; + } + else + { + if (complianceCheckResult.Source != null && complianceCheckResult.SourceZone == null) + { + violation.Details = $"Matrix violations: Failed to resolve zone for source {GetNwObjectString(complianceCheckResult.Source)}."; + } + else if (complianceCheckResult.Destination != null && complianceCheckResult.DestinationZone == null) + { + violation.Details = $"Matrix violations: Failed to resolve zone for destination {GetNwObjectString(complianceCheckResult.Destination)}."; + } + else + { + violation.Details = $"Matrix violation: Failed to resolve network objects."; + } +======= violation.Details = $"{_userConfig.GetText("H5839")}: {sourceString} (Zone: {complianceCheckResult.SourceZone?.Name ?? ""}) -> {destinationString} (Zone: {complianceCheckResult.DestinationZone?.Name ?? ""})"; +>>>>>>> 1b3ee8da1a918500b019ad9add252d9dfd540d52 } break; @@ -500,27 +621,6 @@ private string GetNwObjectString(NetworkObject networkObject) return networkObjectString; } - private bool CheckMatrixCompliance(List source, List destination, out List<(ComplianceNetworkZone, ComplianceNetworkZone)> forbiddenCommunication) - { - // Determine all matching source zones - List sourceZones = DetermineZones(source); - - // Determine all macthing destination zones - List destinationZones = DetermineZones(destination); - - forbiddenCommunication = []; - - foreach (ComplianceNetworkZone sourceZone in sourceZones) - { - foreach (ComplianceNetworkZone destinationZone in destinationZones.Where(d => !sourceZone.CommunicationAllowedTo(d))) - { - forbiddenCommunication.Add((sourceZone, destinationZone)); - } - } - - return forbiddenCommunication.Count == 0; - } - private async Task SetUpReportFilters() { Log.TryWriteLog(LogType.Info, "Compliance Check", "Setting up report filters for compliance check", LocalSettings.ComplianceCheckVerbose); @@ -559,14 +659,24 @@ private bool CheckForForbiddenService(Rule rule, ComplianceCriterion criterion) Service = service.Content }; +<<<<<<< HEAD + ComplianceViolation? violation = TryCreateViolation(ComplianceViolationType.ServiceViolation, rule, complianceCheckResult); + + if (violation != null) + { + ComplianceReport!.Violations.Add(violation); + } + +======= CreateViolation(ComplianceViolationType.ServiceViolation, rule, complianceCheckResult); +>>>>>>> 1b3ee8da1a918500b019ad9add252d9dfd540d52 ruleIsCompliant = false; } } return ruleIsCompliant; } - + private static Task ipRanges)>> GetNetworkObjectsWithIpRanges(List networkObjects) { List<(NetworkObject networkObject, List ipRanges)> networkObjectsWithIpRange = []; @@ -625,7 +735,7 @@ private static List ParseIpRange(NetworkObject networkObject) { networkZones = DetermineZones(dataItem.ipRanges); } - + map.Add((dataItem.networkObject, networkZones)); } @@ -762,6 +872,6 @@ private Expression> CreateAssessabilityExpression(NetworkObject netwo || (networkObject.IP == "255.255.255.255/32" && networkObject.IpEnd == "255.255.255.255/32") || (networkObject.IP == "0.0.0.0/32" && networkObject.IpEnd == "0.0.0.0/32"); } - + } } diff --git a/roles/lib/files/FWO.Data/ApiCrudHelper.cs b/roles/lib/files/FWO.Data/ApiCrudHelper.cs index de64bc61a..a6e5f3c47 100644 --- a/roles/lib/files/FWO.Data/ApiCrudHelper.cs +++ b/roles/lib/files/FWO.Data/ApiCrudHelper.cs @@ -47,11 +47,6 @@ public class ReturnIdWrapper } public class AggregateCountLastHit - // { - // [JsonProperty("device"), JsonPropertyName("device")] - // public List Devices {get; set;} = []; - // } - // public class DeviceLastHit { [JsonProperty("rulebase_link"), JsonPropertyName("rulebase_link")] public List RulebasesOnGateway { get; set; } = []; @@ -62,7 +57,7 @@ public class RulebaseOnGatewaysLastHit [JsonProperty("rulebase"), JsonPropertyName("rulebase")] public RulebaseLastHit Rulebase { get; set; } = new RulebaseLastHit(); } - + public class RulebaseLastHit { [JsonProperty("rulesWithHits"), JsonPropertyName("rulesWithHits")] diff --git a/roles/lib/files/FWO.Data/DisplayBase.cs b/roles/lib/files/FWO.Data/DisplayBase.cs index 2d95c6466..815ebf91a 100644 --- a/roles/lib/files/FWO.Data/DisplayBase.cs +++ b/roles/lib/files/FWO.Data/DisplayBase.cs @@ -10,14 +10,13 @@ public static class DisplayBase { public static StringBuilder DisplayGateway(Device gateway, bool isTechReport, string? gatewayName = null) { - StringBuilder result = new (); - // result.Append($"

{gateway.Name}

"); + StringBuilder result = new(); result.Append($" {gateway.Name}"); return result; - } + } public static StringBuilder DisplayService(NetworkService service, bool isTechReport, string? serviceName = null) { - StringBuilder result = new (); + StringBuilder result = new(); string ports = service.DestinationPortEnd == null || service.DestinationPortEnd == 0 || service.DestinationPort == service.DestinationPortEnd ? $"{service.DestinationPort}" : $"{service.DestinationPort}-{service.DestinationPortEnd}"; bool displayPorts = service.Protocol != null && service.Protocol.HasPorts() && service.DestinationPort != null; @@ -63,24 +62,24 @@ public static List CustomSortProtocols(List ListIn) { List ListOut = []; IpProtocol? tcp = ListIn.Find(x => x.Name.ToLower() == "tcp"); - if(tcp != null) + if (tcp != null) { ListOut.Add(tcp); ListIn.Remove(tcp); } IpProtocol? udp = ListIn.Find(x => x.Name.ToLower() == "udp"); - if(udp != null) + if (udp != null) { ListOut.Add(udp); ListIn.Remove(udp); } IpProtocol? icmp = ListIn.Find(x => x.Name.ToLower() == "icmp"); - if(icmp != null) + if (icmp != null) { ListOut.Add(icmp); ListIn.Remove(icmp); } - foreach(var proto in ListIn.Where(p => p.Name.ToLower() != "unassigned").OrderBy(x => x.Name).ToList()) + foreach (var proto in ListIn.Where(p => p.Name.ToLower() != "unassigned").OrderBy(x => x.Name).ToList()) { ListOut.Add(proto); } @@ -89,7 +88,7 @@ public static List CustomSortProtocols(List ListIn) public static string DisplayIpWithName(NetworkObject elem) { - if(elem.Name != null && elem.Name != "") + if (elem.Name != null && elem.Name != "") { return elem.Name + DisplayIp(elem.IP, elem.IpEnd, true); } @@ -111,7 +110,7 @@ public static string DisplayIp(string ip1, string ip2, bool inBrackets = false) string nwObjType = IpOperations.GetObjectType(ip1, ip2); return DisplayIp(ip1, ip2, nwObjType, inBrackets); } - catch(Exception exc) + catch (Exception exc) { Log.WriteError("Ip displaying", $"Exception thrown: {exc.Message}"); return ""; @@ -132,7 +131,8 @@ public static string DisplayIp(string ip1, string ip2, string nwObjType, bool in if (string.IsNullOrEmpty(ip1)) { Log.WriteDebug("Ip displaying", $"Nessessary parameter {nameof(ip1)} is empty."); - }else if (!ip1.IsV4Address() && !ip1.IsV6Address()) + } + else if (!ip1.IsV4Address() && !ip1.IsV6Address()) { Log.WriteError("Ip displaying", $"Found undefined IP family: {ip1} - {ip2}"); } @@ -141,7 +141,7 @@ public static string DisplayIp(string ip1, string ip2, string nwObjType, bool in Log.WriteError("Ip displaying", $"Found mixed IP family: {ip1} - {ip2}"); } else - { + { string IpStart = ip1.StripOffUnnecessaryNetmask(); string IpEnd = ip2.StripOffUnnecessaryNetmask(); diff --git a/roles/lib/files/FWO.Data/Modelling/ModellingVarianceResult.cs b/roles/lib/files/FWO.Data/Modelling/ModellingVarianceResult.cs index da8c872f6..75d7910d5 100644 --- a/roles/lib/files/FWO.Data/Modelling/ModellingVarianceResult.cs +++ b/roles/lib/files/FWO.Data/Modelling/ModellingVarianceResult.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.Linq; using FWO.Data.Report; namespace FWO.Data.Modelling @@ -37,7 +40,7 @@ public void AddDifference(ModellingConnection conn, Rule rule) ModProdDifference? diff = RuleDifferences.FirstOrDefault(d => d.ModelledConnection.Id == conn.Id); if (diff == null) { - RuleDifferences.Add(new(){ModelledConnection = conn, ImplementedRules = [rule]}); + RuleDifferences.Add(new() { ModelledConnection = conn, ImplementedRules = [rule] }); } else { @@ -50,7 +53,7 @@ public void AddOkRule(ModellingConnection conn, Rule rule) ModProdDifference? diff = OkRules.FirstOrDefault(d => d.ModelledConnection.Id == conn.Id); if (diff == null) { - OkRules.Add(new(){ModelledConnection = conn, ImplementedRules = [rule]}); + OkRules.Add(new() { ModelledConnection = conn, ImplementedRules = [rule] }); } else { @@ -81,29 +84,251 @@ public List DeletedConnRuleDataToReport() private List MgtDataToReport(Dictionary> rulesToReport) { List managementReports = []; - foreach (var mgtId in rulesToReport.Keys.Where(m => rulesToReport[m].Count > 0)) + + foreach ((int managementId, List rulesPerManagement) in rulesToReport.Where(entry => entry.Value.Count > 0)) { - Management? mgt = Managements.FirstOrDefault(m => m.Id == mgtId); - ManagementReport managementReport = new() { Id = mgtId, Name = mgt?.Name ?? "" }; - List deviceReports = []; - foreach (var rule in rulesToReport[mgtId]) + Management? management = Managements.FirstOrDefault(m => m.Id == managementId); + Dictionary deviceMap = management?.Devices?.ToDictionary(d => d.Id) ?? []; + Dictionary deviceNameMap = management?.Devices? + .Where(d => !string.IsNullOrEmpty(d.Name)) + .GroupBy(d => d.Name!, StringComparer.OrdinalIgnoreCase) + .ToDictionary(group => group.Key, group => group.First(), StringComparer.OrdinalIgnoreCase) ?? []; + + ManagementReport managementReport = new() + { + Id = managementId, + Name = management?.Name ?? "", + Uid = management?.Uid ?? "" + }; + + Dictionary rulebaseReports = []; + Dictionary> rulebaseRules = []; + + Dictionary deviceAggregations = []; + Dictionary pseudoRulebaseIds = []; + int nextPseudoRulebaseId = -1; + + foreach (Rule rule in rulesPerManagement) + { + int deviceId = ResolveDeviceId(rule, management, deviceNameMap); + int rulebaseId = ResolveRulebaseId(rule, pseudoRulebaseIds, ref nextPseudoRulebaseId); + + RulebaseReport rulebaseReport = GetOrCreateRulebaseReport(rulebaseReports, rulebaseId, rule); + if (!rulebaseRules.TryGetValue(rulebaseId, out List? rulesForRulebase)) + { + rulesForRulebase = []; + rulebaseRules.Add(rulebaseId, rulesForRulebase); + } + + rulesForRulebase.Add(rule); + + DeviceAggregation deviceAggregation = GetOrCreateDeviceAggregation(deviceAggregations, deviceId, rule, management, deviceMap, deviceNameMap); + + EnsureRulebaseLink(deviceAggregation, deviceId, rulebaseReport.Id); + + deviceAggregation.RuleCount++; + } + + foreach ((int rulebaseId, List ruleList) in rulebaseRules) { - // TODO: Migrate - // DeviceReport? existingDev = deviceReports.FirstOrDefault(d => d.Id == rule.DeviceId); - // if (existingDev != null) - // { - // existingDev.Rules = existingDev.Rules?.Append(rule).ToArray(); - // } - // else - // { - // string devName = mgt == null ? "" : mgt.Devices.FirstOrDefault(d => d.Id == rule.DeviceId)?.Name ?? ""; - // deviceReports.Add(new() { Id = rule.DeviceId, Name = devName, Rules = [rule] }); - // } + RulebaseReport report = rulebaseReports[rulebaseId]; + report.Rules = ruleList.ToArray(); + report.RuleStatistics.ObjectAggregate.ObjectCount = report.Rules.Length; } - managementReport.Devices = [.. deviceReports]; + + managementReport.Rulebases = rulebaseReports.Values.OrderBy(rb => rb.Id).ToArray(); + + managementReport.Devices = deviceAggregations + .OrderBy(pair => pair.Key) + .Select(pair => CreateDeviceReport(pair.Key, pair.Value)) + .ToArray(); + managementReports.Add(managementReport); } + return managementReports; } + + private static int ResolveRulebaseId(Rule rule, Dictionary pseudoRulebaseIds, ref int nextPseudoRulebaseId) + { + if (rule.RulebaseId != 0) + { + return rule.RulebaseId; + } + + if (rule.Rulebase?.Id > 0) + { + return Convert.ToInt32(rule.Rulebase.Id); + } + + long fallbackKey = rule.Id; + if (!pseudoRulebaseIds.TryGetValue(fallbackKey, out int pseudoId)) + { + pseudoId = nextPseudoRulebaseId--; + pseudoRulebaseIds.Add(fallbackKey, pseudoId); + } + + return pseudoId; + } + + private static RulebaseReport GetOrCreateRulebaseReport(Dictionary rulebaseReports, int rulebaseId, Rule rule) + { + if (rulebaseReports.TryGetValue(rulebaseId, out RulebaseReport? existing)) + { + return existing; + } + + string? rulebaseName = rule.Rulebase?.Name; + if (string.IsNullOrEmpty(rulebaseName)) + { + rulebaseName = rule.RulebaseName; + } + + RulebaseReport newReport = new() + { + Id = rulebaseId, + Name = rulebaseName + }; + + rulebaseReports.Add(rulebaseId, newReport); + + return newReport; + } + + private static int ResolveDeviceId(Rule rule, Management? management, Dictionary deviceNameMap) + { + if (rule.Metadata?.DeviceId > 0) + { + return rule.Metadata.DeviceId; + } + + if (rule.EnforcingGateways?.Length > 0) + { + Device? enforcingDevice = rule.EnforcingGateways + .Select(wrapper => wrapper.Content) + .FirstOrDefault(device => device?.Id > 0); + if (enforcingDevice?.Id > 0) + { + return enforcingDevice.Id; + } + } + + if (!string.IsNullOrWhiteSpace(rule.DeviceName) && deviceNameMap.TryGetValue(rule.DeviceName, out Device? deviceByName) && deviceByName.Id > 0) + { + return deviceByName.Id; + } + + if (management?.Devices?.Length == 1) + { + return management.Devices[0].Id; + } + + return 0; + } + + private static DeviceAggregation GetOrCreateDeviceAggregation(Dictionary deviceAggregations, int deviceId, Rule rule, Management? management, Dictionary deviceMap, Dictionary deviceNameMap) + { + if (!deviceAggregations.TryGetValue(deviceId, out DeviceAggregation? aggregation)) + { + (string deviceName, string deviceUid) = ResolveDeviceMetadata(deviceId, rule, deviceMap, deviceNameMap); + + aggregation = new DeviceAggregation + { + Name = deviceName, + Uid = deviceUid + }; + + deviceAggregations.Add(deviceId, aggregation); + } + else + { + RefreshDeviceAggregationMetadata(aggregation, deviceId, rule, deviceMap, deviceNameMap); + } + + return aggregation; + } + + private static (string deviceName, string deviceUid) ResolveDeviceMetadata(int deviceId, Rule rule, Dictionary deviceMap, Dictionary deviceNameMap) + { + if (deviceId > 0 && deviceMap.TryGetValue(deviceId, out Device? deviceFromManagement)) + { + return (deviceFromManagement.Name ?? "", deviceFromManagement.Uid ?? ""); + } + + if (!string.IsNullOrWhiteSpace(rule.DeviceName) && deviceNameMap.TryGetValue(rule.DeviceName, out Device? deviceByName)) + { + return (deviceByName.Name ?? "", deviceByName.Uid ?? ""); + } + + if (!string.IsNullOrWhiteSpace(rule.DeviceName)) + { + return (rule.DeviceName, ""); + } + + return (deviceId > 0 ? $"Device {deviceId}" : "Unknown Device", ""); + } + + private static void RefreshDeviceAggregationMetadata(DeviceAggregation aggregation, int deviceId, Rule rule, Dictionary deviceMap, Dictionary deviceNameMap) + { + (string candidateName, string candidateUid) = ResolveDeviceMetadata(deviceId, rule, deviceMap, deviceNameMap); + + bool hasMeaningfulName = !string.IsNullOrWhiteSpace(candidateName); + bool shouldReplaceName = string.IsNullOrWhiteSpace(aggregation.Name) + || aggregation.Name.StartsWith("Unknown", StringComparison.OrdinalIgnoreCase); + + if (hasMeaningfulName && shouldReplaceName) + { + aggregation.Name = candidateName; + } + + if (!string.IsNullOrEmpty(candidateUid)) + { + aggregation.Uid = candidateUid; + } + } + + private static void EnsureRulebaseLink(DeviceAggregation deviceAggregation, int deviceId, int rulebaseId) + { + if (deviceAggregation.RulebaseLinks.Any(link => link.NextRulebaseId == rulebaseId)) + { + return; + } + + int? initialRulebaseId = deviceAggregation.RulebaseLinks.FirstOrDefault()?.NextRulebaseId; + + RulebaseLink newLink = new() + { + GatewayId = deviceId, + NextRulebaseId = rulebaseId, + IsInitial = deviceAggregation.RulebaseLinks.Count == 0, + FromRulebaseId = deviceAggregation.RulebaseLinks.Count == 0 ? null : initialRulebaseId, + Removed = null + }; + + deviceAggregation.RulebaseLinks.Add(newLink); + } + + private static DeviceReport CreateDeviceReport(int deviceId, DeviceAggregation aggregation) + { + DeviceReport deviceReport = new() + { + Id = deviceId, + Name = aggregation.Name, + Uid = aggregation.Uid, + RulebaseLinks = aggregation.RulebaseLinks.ToArray() + }; + + deviceReport.RuleStatistics.ObjectAggregate.ObjectCount = aggregation.RuleCount; + + return deviceReport; + } + + private sealed class DeviceAggregation + { + public string Name { get; set; } = ""; + public string Uid { get; set; } = ""; + public List RulebaseLinks { get; } = []; + public int RuleCount { get; set; } + } } } diff --git a/roles/lib/files/FWO.Report/Display/RuleDisplayHtml.cs b/roles/lib/files/FWO.Report/Display/RuleDisplayHtml.cs index 7506eba3d..b223be7db 100644 --- a/roles/lib/files/FWO.Report/Display/RuleDisplayHtml.cs +++ b/roles/lib/files/FWO.Report/Display/RuleDisplayHtml.cs @@ -126,7 +126,7 @@ protected static string EnforcingGatewayToHtml(Device gateway, int mgmtId, int c string gwLink = ReportDevicesBase.GetReportDevicesLinkAddress(location, mgmtId, ObjCatString.NwObj, chapterNumber, gateway.Id, reportType); return DisplayGateway(gateway, reportType, reportType.IsResolvedReport() ? null : - ReportBase.ConstructLink(ReportBase.GetIconClass(ObjCategory.nsrv, "Gateway"), gateway.Name, style, gwLink)).ToString(); + ReportBase.ConstructLink(ReportBase.GetIconClass(ObjCategory.nsrv, "Gateway"), gateway.Name ?? "", style, gwLink)).ToString(); } private string DisplaySourceOrDestination(Rule rule, int chapterNumber, OutputLocation location, ReportType reportType, string style, bool isSource, bool overwriteIsResolvedReport = false) diff --git a/roles/lib/files/FWO.Report/ReportDevicesBase.cs b/roles/lib/files/FWO.Report/ReportDevicesBase.cs index a5d138089..af9d9db39 100644 --- a/roles/lib/files/FWO.Report/ReportDevicesBase.cs +++ b/roles/lib/files/FWO.Report/ReportDevicesBase.cs @@ -116,9 +116,9 @@ private static async Task UsageDataAvailable(ApiConnection apiConnection, try { // TODO: the following only deals with first rulebase of a gateway: - // return (await apiConnection.SendQueryAsync>(ReportQueries.getUsageDataCount, new { devId }) - // )[0].RulebasesOnGateway[0].Rulebase.RulesWithHits.Aggregate.Count > 0; - return false; // TODO: implement + var result = await apiConnection.SendQueryAsync>(ReportQueries.getUsageDataCount, new { devId }); + return result?.FirstOrDefault()?.RulebasesOnGateway?.FirstOrDefault()?.ToRulebase?.Rules?.Length > 0; + } catch (Exception) {