// deleteRule will drop the rule and matching set before creating the rule and set, use this is you don't care to update the rule and set in place protected bool UpdateRuleDelta(string ruleName, string action, IEnumerable <IPBanFirewallIPAddressDelta> deltas, string hashType, int maxCount, bool deleteRule, IEnumerable <PortRange> allowPorts, CancellationToken cancelToken) { #if ENABLE_FIREWALL_PROFILING Stopwatch timer = Stopwatch.StartNew(); #endif string ipFileTemp = OSUtility.Instance.GetTempFileName(); try { // add and remove the appropriate ip addresses from the set using (StreamWriter writer = File.CreateText(ipFileTemp)) { if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } writer.WriteLine($"create {ruleName} hash:{hashType} family {INetFamily} hashsize {hashSize} maxelem {maxCount} -exist"); foreach (IPBanFirewallIPAddressDelta delta in deltas) { if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } if (IPAddressRange.TryParse(delta.IPAddress, out IPAddressRange range) && range.Begin.AddressFamily == addressFamily && range.End.AddressFamily == addressFamily) { try { if (delta.Added) { if (range.Begin.Equals(range.End)) { writer.WriteLine($"add {ruleName} {range.Begin} -exist"); } else { writer.WriteLine($"add {ruleName} {range.ToCidrString()} -exist"); } } else { if (range.Begin.Equals(range.End)) { writer.WriteLine($"del {ruleName} {range.Begin} -exist"); } else { writer.WriteLine($"del {ruleName} {range.ToCidrString()} -exist"); } } } catch { // ignore invalid cidr ranges } } } } if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } else { // restore the deltas into the existing set bool result = (RunProcess("ipset", true, $"restore < \"{ipFileTemp}\"") == 0); CreateOrUpdateRule(ruleName, action, hashType, maxCount, allowPorts, cancelToken); return(result); } } finally { ExtensionMethods.FileDeleteWithRetry(ipFileTemp); #if ENABLE_FIREWALL_PROFILING timer.Stop(); Logger.Warn("BlockIPAddressesDelta rule '{0}' took {1:0.00}ms with {2} ips", ruleName, timer.Elapsed.TotalMilliseconds, deltas.Count()); #endif } }
private async Task ProcessPendingFailedLogins(IReadOnlyList <IPAddressLogEvent> ipAddresses) { List <IPAddressLogEvent> bannedIpAddresses = new List <IPAddressLogEvent>(); object transaction = BeginTransaction(); try { foreach (IPAddressLogEvent failedLogin in ipAddresses) { try { string ipAddress = failedLogin.IPAddress; string userName = failedLogin.UserName; string source = failedLogin.Source; if (IsWhitelisted(ipAddress)) { Logger.Warn("Login failure, ignoring whitelisted ip address {0}, {1}, {2}", ipAddress, userName, source); } else { int maxFailedLoginAttempts; if (Config.IsWhitelisted(userName)) { maxFailedLoginAttempts = Config.FailedLoginAttemptsBeforeBanUserNameWhitelist; } else { maxFailedLoginAttempts = Config.FailedLoginAttemptsBeforeBan; } DateTime now = failedLogin.Timestamp; // check for the target user name for additional blacklisting checks bool ipBlacklisted = Config.IsBlackListed(ipAddress); bool userBlacklisted = (ipBlacklisted ? false : Config.IsBlackListed(userName)); bool userFailsWhitelistRegex = (userBlacklisted ? false : Config.UserNameFailsUserNameWhitelistRegex(userName)); bool editDistanceBlacklisted = (ipBlacklisted || userBlacklisted || userFailsWhitelistRegex ? false : !Config.IsUserNameWithinMaximumEditDistanceOfUserNameWhitelist(userName)); bool configBlacklisted = ipBlacklisted || userBlacklisted || userFailsWhitelistRegex || editDistanceBlacklisted; // if the event came in with a count of 0 that means it is an automatic ban int incrementCount = (failedLogin.Count < 1 ? maxFailedLoginAttempts : failedLogin.Count); int newCount = ipDB.IncrementFailedLoginCount(ipAddress, userName, source, UtcNow, incrementCount, transaction); Logger.Warn(now, "Login failure: {0}, {1}, {2}, {3}", ipAddress, userName, source, newCount); // if the ip address is black listed or the ip address has reached the maximum failed login attempts before ban, ban the ip address if (configBlacklisted || newCount >= maxFailedLoginAttempts) { Logger.Info("IP blacklisted: {0}, user name blacklisted: {1}, fails user name white list regex: {2}, user name edit distance blacklisted: {3}", ipBlacklisted, userBlacklisted, userFailsWhitelistRegex, editDistanceBlacklisted); if (ipDB.TryGetIPAddressState(ipAddress, out IPBanDB.IPAddressState state, transaction) && (state == IPBanDB.IPAddressState.Active || state == IPBanDB.IPAddressState.AddPending)) { Logger.Warn(now, "IP {0}, {1}, {2} ban pending.", ipAddress, userName, source); } else { Logger.Debug("Failed login count {0} >= ban count {1}{2}", newCount, maxFailedLoginAttempts, (configBlacklisted ? " config blacklisted" : string.Empty)); // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && failedLogin.Count > 0) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } AddBannedIPAddress(ipAddress, source, userName, bannedIpAddresses, now, configBlacklisted, newCount, string.Empty, transaction); } } else { Logger.Debug("Failed login count {0} <= ban count {1}", newCount, maxFailedLoginAttempts); if (OSUtility.UserIsActive(userName)) { Logger.Warn("Login failed for known active user {0}", userName); } // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && failedLogin.Count > 0) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } } } } catch (Exception ex) { Logger.Error(ex); } }
public override Task <bool> BlockIPAddressesDelta(string ruleNamePrefix, IEnumerable <IPBanFirewallIPAddressDelta> ipAddresses, IEnumerable <PortRange> allowedPorts = null, CancellationToken cancelToken = default) { #if ENABLE_FIREWALL_PROFILING Stopwatch timer = Stopwatch.StartNew(); #endif string prefix = (string.IsNullOrWhiteSpace(ruleNamePrefix) ? BlockRulePrefix : RulePrefix + ruleNamePrefix).TrimEnd('_') + "_"; int ruleIndex; INetFwRule[] rules = EnumerateRulesMatchingPrefix(prefix).ToArray(); List <HashSet <string> > remoteIPAddresses = new List <HashSet <string> >(); List <bool> ruleChanges = new List <bool>(); for (int i = 0; i < rules.Length; i++) { string[] ipList = rules[i].RemoteAddresses.Split(','); HashSet <string> ipSet = new HashSet <string>(); foreach (string ip in ipList) { // trim out submask int pos = ip.IndexOf('/'); if (pos >= 0) { ipSet.Add(ip.Substring(0, pos)); } else { ipSet.Add(ip); } } remoteIPAddresses.Add(ipSet); ruleChanges.Add(false); } List <IPBanFirewallIPAddressDelta> deltas = ipAddresses.ToList(); int deltasCount = deltas.Count; for (int deltaIndex = deltas.Count - 1; deltaIndex >= 0; deltaIndex--) { IPBanFirewallIPAddressDelta delta = deltas[deltaIndex]; if (delta.Added) { if (remoteIPAddresses.Any(set => set.Contains(delta.IPAddress))) { // no change, a set already has the ip deltas.RemoveAt(deltaIndex); continue; } else { // try to find a set with an availble slot for (int setIndex = 0; setIndex < remoteIPAddresses.Count; setIndex++) { if (remoteIPAddresses[setIndex].Count < MaxIpAddressesPerRule) { remoteIPAddresses[setIndex].Add(delta.IPAddress); deltas.RemoveAt(deltaIndex); ruleChanges[setIndex] = true; break; } } } } else { for (int setIndex = 0; setIndex < remoteIPAddresses.Count; setIndex++) { if (remoteIPAddresses[setIndex].Remove(delta.IPAddress)) { ruleChanges[setIndex] = true; break; } } deltas.RemoveAt(deltaIndex); } } // any remaining deltas for adding need to go in new rules if they did not fit in the existing rules // remaining deltas are guaranteed to be adds string[] remainingIPAddresses = deltas.Select(d => d.IPAddress).Where(ip => IPAddress.TryParse(ip, out _)).ToArray(); for (int i = 0; i < remainingIPAddresses.Length; i += MaxIpAddressesPerRule) { remoteIPAddresses.Add(new HashSet <string>(remainingIPAddresses.Skip(i).Take(MaxIpAddressesPerRule))); ruleChanges.Add(true); } // update the rules ruleIndex = 0; for (int i = 0; i < remoteIPAddresses.Count; i++) { if (ruleChanges[i]) { string name = (i < rules.Length ? rules[i].Name : prefix + ruleIndex.ToStringInvariant()); GetOrCreateRule(name, string.Join(',', remoteIPAddresses[i]), NetFwAction.Block, allowedPorts); } ruleIndex += MaxIpAddressesPerRule; } #if ENABLE_FIREWALL_PROFILING timer.Stop(); Logger.Warn("BlockIPAddressesDelta rule '{0}' took {1:0.00}ms with {2} ips", prefix, timer.Elapsed.TotalMilliseconds, deltasCount); #endif return(Task.FromResult(true)); }
private Task <bool> BlockOrAllowIPAddresses(string ruleNamePrefix, bool block, IEnumerable <string> ipAddresses, IEnumerable <PortRange> allowedPorts = null, CancellationToken cancelToken = default) { #if ENABLE_FIREWALL_PROFILING Stopwatch timer = Stopwatch.StartNew(); #endif int i = 0; string prefix = ruleNamePrefix.TrimEnd('_') + "_"; try { List <string> ipAddressesList = new List <string>(); foreach (string ipAddress in ipAddresses) { if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } ipAddressesList.Add(ipAddress); if (ipAddressesList.Count == MaxIpAddressesPerRule) { if (block) { CreateBlockRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } else { CreateAllowRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } i += MaxIpAddressesPerRule; ipAddressesList.Clear(); } } if (cancelToken.IsCancellationRequested) { throw new OperationCanceledException(cancelToken); } if (ipAddressesList.Count != 0) { if (block) { CreateBlockRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } else { CreateAllowRule(ipAddressesList, 0, MaxIpAddressesPerRule, prefix + i.ToStringInvariant(), allowedPorts); } i += MaxIpAddressesPerRule; } DeleteRules(prefix, i); return(Task.FromResult(true)); } catch (Exception ex) { if (!(ex is OperationCanceledException)) { Logger.Error(ex); } return(Task.FromResult(false)); } finally { #if ENABLE_FIREWALL_PROFILING timer.Stop(); Logger.Warn("Block ip addresses rule '{0}' took {1:0.00}ms with {2} ips", prefix, timer.Elapsed.TotalMilliseconds, i); #endif } }
private void LoadFirewall(IPBanConfig oldConfig) { IIPBanFirewall existing = Firewall; Firewall = FirewallCreator.CreateFirewall(Config, Firewall); if (existing != Firewall) { AddUpdater(Firewall); Logger.Warn("Loaded firewall type {0}", Firewall.GetType()); if (existing != null) { RemoveUpdater(existing); // transfer banned ip to new firewall Firewall.BlockIPAddresses(null, ipDB.EnumerateBannedIPAddresses()).Sync(); } } if (oldConfig is null) { // clear out all previous custom rules foreach (string rule in Firewall.GetRuleNames(Firewall.RulePrefix + "EXTRA_").ToArray()) { Firewall.DeleteRule(rule); } } else { // check for updated / new / removed block rules List <string> deleteList = new List <string>(oldConfig.ExtraRules.Select(r => r.Name)); // cleanup rules that are no longer in the config foreach (string newRule in Config.ExtraRules.Select(r => r.Name)) { deleteList.Remove(newRule); } foreach (string rule in deleteList) { foreach (string ruleName in Firewall.GetRuleNames(rule).ToArray()) { Firewall.DeleteRule(ruleName); } } } // ensure firewall is cleared out if needed - will only execute once UpdateBannedIPAddressesOnStart(); // ensure windows event viewer is setup if needed - will only execute once SetupWindowsEventViewer(); // add/update global rules Firewall.AllowIPAddresses("GlobalWhitelist", Config.Whitelist); Firewall.BlockIPAddresses("GlobalBlacklist", Config.BlackList); // add/update user specified rules foreach (IPBanFirewallRule rule in Config.ExtraRules) { if (rule.Block) { Firewall.BlockIPAddresses(rule.Name, rule.IPAddressRanges, rule.AllowPortRanges); } else { Firewall.AllowIPAddresses(rule.Name, rule.IPAddressRanges, rule.AllowPortRanges); } } }
private void AddBannedIPAddress(string ipAddress, string source, string userName, List <IPAddressLogEvent> bannedIpAddresses, DateTime startBanDate, bool configBlacklisted, int counter, string extraInfo, object transaction, bool external) { // never ban whitelisted ip addresses if (IsWhitelisted(ipAddress)) { Logger.Info("Ignoring ban request for whitelisted ip address {0}", ipAddress); return; } TimeSpan[] banTimes = Config.BanTimes; TimeSpan banTime = banTimes.First(); DateTime banEndDate = startBanDate + banTime; // if we have an ip in the database, use the ban time to move to the next ban slot in the list of ban times // if ban times only has one entry, do not do this if (banTimes.Length > 1 && ipDB.TryGetIPAddress(ipAddress, out IPBanDB.IPAddressEntry ipEntry, transaction) && ipEntry.BanStartDate != null && ipEntry.BanEndDate != null) { // find the next ban time in the array banTime = ipEntry.BanEndDate.Value - ipEntry.BanStartDate.Value; for (int i = 0; i < banTimes.Length; i++) { if (banTime < banTimes[i]) { // ban for next timespan banTime = banTimes[i]; banEndDate = startBanDate + banTime; Logger.Info("Moving to next ban duration {0} at index {1} for ip {1}", banTimes[i], i, ipAddress); break; } } } int adjustedCount = (counter <= 0 ? Config.FailedLoginAttemptsBeforeBan : counter); bannedIpAddresses?.Add(new IPAddressLogEvent(ipAddress, userName, source, adjustedCount, IPAddressEventType.Blocked)); if (ipDB.SetBanDates(ipAddress, startBanDate, banEndDate, UtcNow, transaction)) { firewallNeedsBlockedIPAddressesUpdate = true; } Logger.Warn(startBanDate, "Banning ip address: {0}, user name: {1}, config black listed: {2}, count: {3}, extra info: {4}, duration: {5}", ipAddress, userName, configBlacklisted, counter, extraInfo, banTime); // if this is a delegate callback (counter of 0), exit out - we don't want to run handlers or processes for shared banned ip addresses if (counter <= 0) { return; } else if (BannedIPAddressHandler != null && System.Net.IPAddress.TryParse(ipAddress, out System.Net.IPAddress ipAddressObj) && !ipAddressObj.IsInternal()) { try { ExecuteTask(BannedIPAddressHandler.HandleBannedIPAddress(ipAddress, source, userName, OSName, OSVersion, AssemblyVersion, RequestMaker)); } catch { // eat exception, delicious } } if (IPBanDelegate != null && !external) { try { ExecuteTask(IPBanDelegate.IPAddressBanned(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow, true)); } catch (Exception ex) { Logger.Info("Error calling ipban delegate with banned ip address: " + ex.ToString()); } } }
/// <inheritdoc /> public override async Task StartAsync(CancellationToken cancellationToken) { Logger.Warn("Starting service"); await base.StartAsync(cancellationToken); }