/******************************************************************** Copyright (c) 2017, Check Point Software Technologies Ltd. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ********************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; using CommonUtils; namespace CheckPointObjects { /// /// Optimizes the security policy rulebase by merging several rules from the same sub-policy into a single rule. /// Two rules can be merged into one rule if: /// 1. both rules have the same action, and /// 2. both rules are enabled or disabled, and /// 3. both rules have source and destination columns negated or not, and /// 4. both rules have the same time objects, and /// 5. either one of the following is true: /// 5.1. both the source and destination columns match /// 5.2. both the source and service columns match /// 5.3. both the destination and service columns match /// for CiscoASA and FirePower vendors there is an option to optimize by comments - /// two rules can be merged if they have the same comments and in addition they up to the above criteria. /// public static class RuleBaseOptimizer { public static bool IsOptimizeByComments = false; public static CheckPoint_Layer Optimize(CheckPoint_Layer originalLayer, string newName) { CheckPoint_Layer curLayer = originalLayer; CheckPoint_Layer newLayer; while (true) { var nextLayer = new CheckPoint_Layer { Name = newName }; foreach (CheckPoint_Rule rule in curLayer.Rules) { AddRule(nextLayer, rule); } if (nextLayer.Rules.Count == curLayer.Rules.Count) { newLayer = nextLayer; break; } curLayer = nextLayer; } for (int i = 0; i < newLayer.Rules.Count; ++i) { newLayer.Rules[i].ConversionComments = OptimizeConverstionComments(newLayer.Rules[i].ConversionComments); } return newLayer; } private static void AddRule(CheckPoint_Layer layer, CheckPoint_Rule newRule) { bool match = false; int pos = GetFirstRuleWithSameAction(layer, newRule.Action); if (pos >= 0) { for (int i = pos; i < layer.Rules.Count(); i++) { if (IsRuleSimilarToRule(layer.Rules[i], newRule)) { layer.Rules[i] = MergeRules(layer.Rules[i], newRule); match = true; break; } } } if (!match) { CheckPoint_Rule rule = newRule.Clone(); rule.Layer = layer.Name; rule.Comments = IsOptimizeByComments ? rule.Comments : ""; rule.ConversionComments = newRule.ConversionComments; layer.Rules.Add(rule); } } private static CheckPoint_Rule MergeRules(CheckPoint_Rule rule1, CheckPoint_Rule rule2) { var mergedRule = new CheckPoint_Rule(); var sources = new List(); var destinations = new List(); var services = new List(); var times = new List(); sources.AddRange(rule1.Source); sources.AddRange(rule2.Source); mergedRule.Source = sources.Distinct().ToList(); OmitAnyFromList(mergedRule.Source); destinations.AddRange(rule1.Destination); destinations.AddRange(rule2.Destination); mergedRule.Destination = destinations.Distinct().ToList(); OmitAnyFromList(mergedRule.Destination); services.AddRange(rule1.Service); services.AddRange(rule2.Service); mergedRule.Service = services.Distinct().ToList(); OmitAnyFromList(mergedRule.Service); times.AddRange(rule1.Time); times.AddRange(rule2.Time); mergedRule.Time = times.Distinct().ToList(); OmitAnyFromList(mergedRule.Time); mergedRule.Enabled = (rule1.Enabled && rule2.Enabled); mergedRule.Layer = rule1.Layer; mergedRule.Action = rule1.Action; mergedRule.Track = rule1.Track; mergedRule.SourceNegated = rule1.SourceNegated; mergedRule.DestinationNegated = rule1.DestinationNegated; mergedRule.Comments = IsOptimizeByComments ? rule1.Comments : ""; // adding or not adding comments by the user request mergedRule.ConversionComments = rule1.ConversionComments + " | " + rule2.ConversionComments; mergedRule.ConvertedCommandId = rule1.ConvertedCommandId; mergedRule.ConversionIncidentType = ConversionIncidentType.None; if (rule1.ConversionIncidentType != ConversionIncidentType.None || rule2.ConversionIncidentType != ConversionIncidentType.None) { if (rule1.ConversionIncidentType > rule2.ConversionIncidentType) { mergedRule.ConvertedCommandId = rule1.ConvertedCommandId; mergedRule.ConversionIncidentType = rule1.ConversionIncidentType; } else { mergedRule.ConvertedCommandId = rule2.ConvertedCommandId; mergedRule.ConversionIncidentType = rule2.ConversionIncidentType; } } return mergedRule; } private static void OmitAnyFromList(List list) { if (list.Count > 1) { foreach (var item in list.Where(item => item.Name == CheckPointObject.Any)) { list.Remove(item); break; } } } private static int GetFirstRuleWithSameAction(CheckPoint_Layer layer, CheckPoint_Rule.ActionType action) { int matchedRules = 0; int pos = layer.Rules.Count - 1; while (pos >= 0 && layer.Rules[pos].Action == action) { matchedRules++; pos--; } return (matchedRules == 0) ? -1 : (pos + 1); } private static bool IsRuleSimilarToRule(CheckPoint_Rule rule1, CheckPoint_Rule rule2) { // Optimizing by comments - checks if comments of the two rules are matched and not empty if (IsOptimizeByComments && rule1.Comments != rule2.Comments || IsOptimizeByComments && string.IsNullOrEmpty(rule1.Comments)) { return false; } if (rule1.Action != rule2.Action) { return false; } if (rule1.Enabled != rule2.Enabled) { return false; } if (rule1.SourceNegated != rule2.SourceNegated || rule1.DestinationNegated != rule2.DestinationNegated) { return false; } if ((rule1.Time.Count != rule2.Time.Count) || (rule1.Time.Count > 0 && rule2.Time.Count > 0 && rule1.Time[0].Name != rule2.Time[0].Name)) { return false; } bool sourceMatch = CompareLists(rule1.Source, rule2.Source); bool destMatch = CompareLists(rule1.Destination, rule2.Destination); bool serviceMatch = CompareLists(rule1.Service, rule2.Service); return (sourceMatch && destMatch || destMatch && serviceMatch || sourceMatch && serviceMatch); } private static bool CompareLists(List items, List searchedItems) { var list1 = (from o in items select o.Name).ToList(); var list2 = (from o in searchedItems select o.Name).ToList(); var firstNotSecond = list1.Except(list2).ToList(); var secondNotFirst = list2.Except(list1).ToList(); return (!firstNotSecond.Any() && !secondNotFirst.Any()); } /// /// Method for creation comment by the template 'optimized of access-list #x #y' /// /// comment to process /// optimized comment at the right format private static string OptimizeConverstionComments(string commentToProcess) { string commentBuilder = "optimized of access-list"; List rules = new List(); List comments_parts = commentToProcess.Trim().Split(' ').ToList(); Regex regex = new Regex(@"[0-9]+[)]"); //remove empty strings and '_' chars comments_parts = comments_parts.Where(s => !string.IsNullOrWhiteSpace(s) && !s.Equals("_")).Distinct().ToList(); //if there is nothing to merge return empty comment if (comments_parts.Count == 0) return ""; if (comments_parts.Count > 0) { if (regex.IsMatch(comments_parts[0])) foreach (string part in comments_parts) { if (regex.IsMatch(part)) commentBuilder += " " + part.Remove(part.Length - 1); } //FG and Juniper else if (comments_parts[0].Equals("Matched") && (comments_parts[1].Equals("rule") || comments_parts[1].Equals("rule:"))) { commentBuilder = "Matched rule(s)"; Regex regexNumbers = new Regex(@"[0-9]"); foreach (string word in comments_parts) { if (regexNumbers.IsMatch(word)) { if (commentBuilder.Equals("Matched rule(s)")) commentBuilder += " " + word; else commentBuilder += ", " + word; } } } //Panorama else if (comments_parts[0].Equals("Matched") && comments_parts[1].Equals("rule:")) { commentBuilder = "Matched rule(s)"; for (int i = 2; i < comments_parts.Count; ++i) { if (commentBuilder.Equals("Matched rule(s)")) commentBuilder += " " + comments_parts[i]; else commentBuilder += ", " + comments_parts[i]; } } else return commentToProcess.Trim(); } // slicing the conversion comment if there are more than 250 characters string comment = commentBuilder == "Matched rule(s)" ? "" : commentBuilder; if (comment.Length > 0) return comment.Substring(0, Math.Min(comment.Length, 250)); else return comment; } } }