From 05eb55ea31f428c753c67abdd7a6b94cf0699ee9 Mon Sep 17 00:00:00 2001 From: chris_bednarski Date: Sun, 13 Aug 2023 11:34:57 +1000 Subject: [PATCH] add integration tests for the firewall extenstion --- .../Directory.Packages.props.pp | 1 + .../burn/WixTestTools/Firewall/RuleDetails.cs | 258 +++++++++++++++ .../burn/WixTestTools/Firewall/UniqueCheck.cs | 72 +++++ .../burn/WixTestTools/Firewall/Verifier.cs | 306 ++++++++++++++++++ .../burn/WixTestTools/WixTestTools.csproj | 11 + .../FirewallRules/FirewallRules.wixproj | 13 + .../FirewallRules/product.wxs | 23 ++ .../FirewallExtensionTests.cs | 205 ++++++++++++ .../WixToolsetTest.MsiE2E.csproj | 11 + 9 files changed, 900 insertions(+) create mode 100644 src/test/burn/WixTestTools/Firewall/RuleDetails.cs create mode 100644 src/test/burn/WixTestTools/Firewall/UniqueCheck.cs create mode 100644 src/test/burn/WixTestTools/Firewall/Verifier.cs create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRules/FirewallRules.wixproj create mode 100644 src/test/msi/TestData/FirewallExtensionTests/FirewallRules/product.wxs create mode 100644 src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs diff --git a/src/internal/SetBuildNumber/Directory.Packages.props.pp b/src/internal/SetBuildNumber/Directory.Packages.props.pp index cea1def80..b39eeb787 100644 --- a/src/internal/SetBuildNumber/Directory.Packages.props.pp +++ b/src/internal/SetBuildNumber/Directory.Packages.props.pp @@ -37,6 +37,7 @@ + diff --git a/src/test/burn/WixTestTools/Firewall/RuleDetails.cs b/src/test/burn/WixTestTools/Firewall/RuleDetails.cs new file mode 100644 index 000000000..b4de5ca96 --- /dev/null +++ b/src/test/burn/WixTestTools/Firewall/RuleDetails.cs @@ -0,0 +1,258 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixTestTools.Firewall +{ + using System.Net; + using System; + using NetFwTypeLib; + + /// + /// The RuleDetails class provides access to the properties of a firewall rule.
+ /// Details are retrieved via the NetFwTypeLib.INetFwRule3 interface and originally stored at
+ /// HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules. + ///
+ public class RuleDetails + { + public RuleDetails(string name) + { + this.Name = name; + } + + public RuleDetails(INetFwRule3 rule3) + { + this.Name = rule3.Name; + this.Description = rule3.Description; + this.ApplicationName = rule3.ApplicationName; + this.ServiceName = rule3.serviceName; + this.Protocol = rule3.Protocol; + this.LocalPorts = rule3.LocalPorts; + this.RemotePorts = rule3.RemotePorts; + this.LocalAddresses = rule3.LocalAddresses; + this.RemoteAddresses = rule3.RemoteAddresses; + this.IcmpTypesAndCodes = rule3.IcmpTypesAndCodes; + this.Direction = rule3.Direction; + this.Interfaces = rule3.Interfaces as object[]; + this.InterfaceTypes = rule3.InterfaceTypes; + this.Enabled = rule3.Enabled; + this.Grouping = rule3.Grouping; + this.Profiles = rule3.Profiles; + this.EdgeTraversal = rule3.EdgeTraversal; + this.Action = rule3.Action; + this.EdgeTraversalOptions = rule3.EdgeTraversalOptions; + this.LocalAppPackageId = rule3.LocalAppPackageId; + this.LocalUserOwner = rule3.LocalUserOwner; + this.LocalUserAuthorizedList = rule3.LocalUserAuthorizedList; + this.RemoteUserAuthorizedList = rule3.RemoteUserAuthorizedList; + this.RemoteMachineAuthorizedList = rule3.RemoteMachineAuthorizedList; + this.SecureFlags = rule3.SecureFlags; + } + + /// + /// Specifies a friendly name of this rule. Rule name should be unique, must not contain a "|" (pipe) character and cannot be "all".
+ /// Use netsh advfirewall firewall add rule help for more information. + ///
+ public string Name { get; set; } + + /// + /// This property is optional. It specifies the description of this rule.
+ /// The string must not contain the "|" (pipe) character. + ///
+ public string Description { get; set; } + + /// + /// This property is optional. It specifies the path and name (an image path) of the application to which this rule applies.
+ /// Environment variables can be used in the path.
+ /// Example: "%ProgramFiles%\Windows Defender\MsMpEng.exe"
+ /// Example: "%SystemRoot%\system32\svchost.exe" + ///
+ public string ApplicationName { get; set; } + + /// + /// This property is optional. A service name value of "*" indicates that a service, not an application, must be sending or receiving traffic.
+ /// Example: "Spooler"
+ /// Example: "FTPSVC" + ///
+ public string ServiceName { get; set; } + + /// + /// This property is optional. The Protocol property must be set before the LocalPorts or RemotePorts properties, or an error will be returned.
+ /// A list of protocol numbers is available at the IANA website. + /// The default value is 256, which means any protocol.
+ /// Example: 6 , which specifies the TCP protocol
+ /// Example: 17 , which specifies the UDP protocol + ///
+ public int? Protocol { get; set; } + + /// + /// This property is optional. The Protocol property must be set before the LocalPorts property or an error will be returned.
+ /// A clear text string containing a single port, a comma separated list of port numbers, or a port range can be provided. "RPC" is an acceptable value.
+ /// Example: "23456"
+ /// Example: "10234-10300"
+ /// Example: "5026,7239" + ///
+ public string LocalPorts { get; set; } + + /// + /// This property is optional. The Protocol property must be set before the RemotePorts property or an error will be returned.
+ /// A clear text string containing a single port, a comma separated list of port numbers, or a port range can be provided.
+ /// Example: "23456"
+ /// Example: "10234-10300"
+ /// Example: "5026,7239" + ///
+ public string RemotePorts { get; set; } + + /// + /// This property is optional. It consists of one or more comma-delimited tokens specifying the local addresses from which the application can listen for traffic.
+ /// "*" indicates any local address and is the default value. If present, this must be the only token included.
+ /// Other tokens:
+ /// o "Defaultgateway"
+ /// o "DHCP"
+ /// o "WINS"
+ /// o "LocalSubnet" indicates any local address on the local subnet. This token is not case-sensitive.
+ /// o A subnet can be specified using either the subnet mask or network prefix notation. If neither a subnet mask not a network prefix is specified, the subnet mask defaults to 255.255.255.255.
+ /// o A valid IPv6 address.
+ /// o An IPv4 address range in the format of "start address - end address" with no spaces included.
+ /// o An IPv6 address range in the format of "start address - end address" with no spaces included.
+ /// Example: "LocalSubnet" + ///
+ public string LocalAddresses { get; set; } + + /// + /// This property is optional. It consists of one or more comma-delimited tokens specifying the local addresses from which the application can listen for traffic.
+ /// "*" indicates any remote address and is the default value. If present, this must be the only token included.
+ /// Other tokens:
+ /// o "Defaultgateway"
+ /// o "DHCP"
+ /// o "WINS"
+ /// o "LocalSubnet" indicates any local address on the local subnet. This token is not case-sensitive.
+ /// o A subnet can be specified using either the subnet mask or network prefix notation. If neither a subnet mask not a network prefix is specified, the subnet mask defaults to 255.255.255.255.
+ /// o A valid IPv6 address.
+ /// o An IPv4 address range in the format of "start address - end address" with no spaces included.
+ /// o An IPv6 address range in the format of "start address - end address" with no spaces included.
+ /// Example: "LocalSubnet" + ///
+ public string RemoteAddresses { get; set; } + + /// + /// This property is optional. A list of ICMP types and codes separated by semicolon. "*" indicates all ICMP types and codes.
+ /// Example: "4:*,9:*,12:*" + ///
+ public string IcmpTypesAndCodes { get; set; } + + /// + /// This property is optional. If this property is not specified, the default value is NET_FW_RULE_DIR_IN. + /// + public NET_FW_RULE_DIRECTION_? Direction { get; set; } + + /// + /// This parameter allows the specification of an array of interface LUIDs (locally unique identifiers) supplied as strings.
+ /// This is commonly used by USB RNDIS (Remote Network Driver Interface Specification) devices to restrict traffic to a specific non-routable interface.
+ /// Use netsh trace show interfaces to show a list of local interfaces and their LUIDs.
+ /// Example: new object[] { "Wi-Fi", "Local Area Connection* 14" } + ///
+ public object[] Interfaces { get; set; } + + /// + /// This property is optional. It specifies the list of interface types for which the rule applies.
+ /// Acceptable values for this property are "RemoteAccess", "Wireless", "Lan", and "All".
+ /// If more than one interface type is specified, the strings must be separated by a comma.
+ /// Example: "Lan,Wireless" + ///
+ public string InterfaceTypes { get; set; } + + /// + /// This property is optional. It enables or disables a rule. A new rule is disabled by default. + /// + public bool? Enabled { get; set; } + + /// + /// This property is optional. It specifies the group to which an individual rule belongs and groups multiple rules into a single line in the Windows Firewall control panel
+ /// This allows the users to enable or disable multiple rules with a single click.
+ /// The Grouping property can also be specified using indirect strings.
+ /// Example: "Simple Group Name"
+ /// Example: "@yourresources.dll,-1005" + ///
+ public string Grouping { get; set; } + + /// + /// This property is optional. The NET_FW_PROFILE_TYPE2 enumerated type specifies the type of profile.
+ /// NET_FW_PROFILE2_ALL is the default value. Profiles can be combined from the following values:
+ /// o NET_FW_PROFILE2_DOMAIN = 0x1
+ /// o NET_FW_PROFILE2_PRIVATE = 0x2
+ /// o NET_FW_PROFILE2_PUBLIC = 0x4
+ /// o NET_FW_PROFILE2_ALL = 0x7fffffff
+ /// Example: 0x5 + ///
+ public int? Profiles { get; set; } + + /// + /// New rules have the EdgeTraversal property disabled by default.
+ /// The EdgeTraversal property indicates that specific inbound traffic is allowed to tunnel through NATs and other edge devices using the Teredo tunneling technology.
+ /// The application or service with the inbound firewall rule needs to support IPv6. The primary application of this setting allows listeners on the host to be globally addressable through a Teredo IPv6 address.
+ /// See EdgeTraversalOptions property for additional information. + ///
+ public bool? EdgeTraversal { get; set; } + + /// + /// This property is optional. The NET_FW_ACTION enumerated type specifies the action for this rule.
+ /// NET_FW_ACTION_ALLOW is the default value. Profiles can be combined from the following values:
+ /// o NET_FW_ACTION_BLOCK = 0x0
+ /// o NET_FW_ACTION_ALLOW = 0x1
+ ///
+ public NET_FW_ACTION_? Action { get; set; } + + /// + /// This property is optional and can be used to access the edge traversal properties of a firewall rule defined by NET_FW_EDGE_TRAVERSAL_TYPE enumerated type.
+ /// NET_FW_EDGE_TRAVERSAL_TYPE_DENY is the default value. Enumerated types cannot be combined and must be selected from the following list of values:
+ /// o NET_FW_EDGE_TRAVERSAL_TYPE_DENY = 0x0 - Edge traversal traffic is always blocked.
+ /// o NET_FW_EDGE_TRAVERSAL_TYPE_ALLOW = 0x1 - Edge traversal traffic is always allowed.
+ /// These two options above are equivalent to setting the EdgeTraversal property to true or false.
+ /// o NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_APP = 0x2 - Edge traversal traffic is allowed when the application sets the IPV6_PROTECTION_LEVEL socket option to PROTECTION_LEVEL_UNRESTRICTED. Otherwise, it is blocked.
+ /// o NET_FW_EDGE_TRAVERSAL_TYPE_DEFER_TO_USER = 0x3 - The user is prompted whether to allow edge traversal traffic when the application sets the IPV6_PROTECTION_LEVEL socket option to PROTECTION_LEVEL_UNRESTRICTED.
+ /// If the user chooses to allow edge traversal traffic, the rule is modified to defer to the application's settings. If the application has not set the IPV6_PROTECTION_LEVEL socket option to PROTECTION_LEVEL_UNRESTRICTED,
+ /// edge traversal traffic is blocked. In order to use this option, the firewall rule must have both the application path and protocol scopes specified. This option cannot be used if port(s) are defined. + ///
+ public int? EdgeTraversalOptions { get; set; } + + /// + /// This property is optional. It specifies the package identifier or the app container identifier of a process, whether from a Windows Store app or a desktop app.
+ /// For more details and examples look at HKLM\SYSTEM\ControlSet001\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\AppIso\FirewallRules.
+ /// Example: "S-1-15-2-1239072475-3687740317-1842961305-3395936705-4023953123-1525404051-2779347315" Microsoft.WindowsMaps + ///
+ public string LocalAppPackageId { get; set; } + + /// + /// This property is optional. It specifies the user security identifier (SID) of the user who is the owner of the rule.
+ /// If this rule does not specify LocalUserAuthorizedList, all the traffic that this rule matches must be destined to or originated from this user.
+ /// Example: "S-1-5-21-1898747406-2352535518-1247798438-1914" + ///
+ public string LocalUserOwner { get; set; } + + /// + /// This property is optional. It specifies a list of authorized local users for an app container.
+ /// Example: "O:LSD:(A;;CC;;;S-1-5-84-0-0-0-0-0)" + ///
+ public string LocalUserAuthorizedList { get; set; } + + /// + /// This property is optional. It specifies a list of remote users who are authorized to access an app container.
+ ///
+ public string RemoteUserAuthorizedList { get; set; } + + /// + /// This property is optional. It specifies a list of remote computers which are authorized to access an app container.
+ ///
+ public string RemoteMachineAuthorizedList { get; set; } + + /// + /// This property is optional. It specifies which firewall verifications of security levels provided by IPsec must be guaranteed to allow the connection.
+ /// The allowed values must correspond to one of those of the NET_FW_AUTHENTICATE_TYPE enumeration:
+ /// o NET_FW_AUTHENTICATE_NONE - 0x0 - No security check is performed.
+ /// o NET_FW_AUTHENTICATE_NO_ENCAPSULATION - 0x1 - The traffic is allowed if it is IPsec-protected with authentication and no encapsulation protection. This means that the peer is authenticated, but there is no integrity protection on the data.
+ /// o NET_FW_AUTHENTICATE_WITH_INTEGRITY - 0x2 - The traffic is allowed if it is IPsec-protected with authentication and integrity protection.
+ /// o NET_FW_AUTHENTICATE_AND_NEGOTIATE_ENCRYPTION - 0x3 - The traffic is allowed if its is IPsec-protected with authentication and integrity protection. In addition, negotiation of encryption protections on subsequent packets is requested.
+ /// o NET_FW_AUTHENTICATE_AND_ENCRYPT - 0x4 - The traffic is allowed if it is IPsec-protected with authentication, integrity and encryption protection since the very first packet. + ///
+ public int? SecureFlags { get; set; } + } +} diff --git a/src/test/burn/WixTestTools/Firewall/UniqueCheck.cs b/src/test/burn/WixTestTools/Firewall/UniqueCheck.cs new file mode 100644 index 000000000..a35882fb7 --- /dev/null +++ b/src/test/burn/WixTestTools/Firewall/UniqueCheck.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixTestTools.Firewall +{ + using NetFwTypeLib; + + public class UniqueCheck + { + public UniqueCheck() + { + } + + public UniqueCheck(RuleDetails details) + { + this.Name = details.Name; + this.Direction = details.Direction; + this.Profile = details.Profiles; + this.Protocol = details.Protocol; + this.ApplicationName = details.ApplicationName; + this.LocalUserOwner = details.LocalUserOwner; + } + + + public string Name { get; set; } + + public NET_FW_RULE_DIRECTION_? Direction { get; set; } + + public int? Profile { get; set; } + + public int? Protocol { get; set; } + + public string ApplicationName { get; set; } + + public string LocalUserOwner { get; set; } + + + public bool FirewallRuleIsUqniue(INetFwRule3 rule) + { + if (this.Name != null && rule.Name != this.Name) + { + return false; + } + + if (this.Direction.HasValue && rule.Direction != this.Direction.Value) + { + return false; + } + + if (this.Profile.HasValue && rule.Profiles != this.Profile.Value) + { + return false; + } + + if (this.Protocol.HasValue && rule.Protocol != this.Protocol.Value) + { + return false; + } + + if (this.ApplicationName != null && rule.ApplicationName != this.ApplicationName) + { + return false; + } + + if (this.LocalUserOwner != null && rule.LocalUserOwner != this.LocalUserOwner) + { + return false; + } + + return true; + } + } +} diff --git a/src/test/burn/WixTestTools/Firewall/Verifier.cs b/src/test/burn/WixTestTools/Firewall/Verifier.cs new file mode 100644 index 000000000..37cddb161 --- /dev/null +++ b/src/test/burn/WixTestTools/Firewall/Verifier.cs @@ -0,0 +1,306 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixTestTools.Firewall +{ + using System; + using System.Collections.Generic; + using System.Xml; + using NetFwTypeLib; + using Xunit; + + public static class Verifier + { + static INetFwRules GetINetFwRules() + { + var policyType = Type.GetTypeFromProgID("HNetCfg.FwPolicy2", true); + var policyInstance = Activator.CreateInstance(policyType); + var policy2 = policyInstance as INetFwPolicy2; + return policy2.Rules; + } + + static INetFwRule3 GetINetFwRule3(string name, UniqueCheck unique) + { + var rules = GetINetFwRules(); + INetFwRule3 rule3; + + // A lot of firewall rules don't follow the Microsoft recommendation of using unique exception rule names. + // Disambiguate rules by other means. + if (unique != null) + { + var enumerator = rules.GetEnumerator(); + while (enumerator.MoveNext()) + { + rule3 = enumerator.Current as INetFwRule3; + if (!unique.FirewallRuleIsUqniue(rule3)) + { + continue; + } + + return rule3; + } + } + + var rule1 = rules.Item(name); + rule3 = rule1 as INetFwRule3; + return rule3; + } + + public static RuleDetails GetFirewallRule(string name, UniqueCheck unique) + { + var rule = GetINetFwRule3(name, unique); + var details = new RuleDetails(rule); + return details; + } + + public static bool FirewallRuleExists(string name, UniqueCheck unique = null) + { + try + { + GetINetFwRule3(name, unique); + return true; + } + catch (System.IO.FileNotFoundException) + { + return false; + } + } + + public static IEnumerable GetFirewallRules() + { + var rules = GetINetFwRules(); + var enumerator = rules.GetEnumerator(); + while (enumerator.MoveNext()) + { + var rule3 = enumerator.Current as INetFwRule3; + yield return new RuleDetails(rule3); + } + } + + public static void AddFirewallRule(RuleDetails information) + { + var rules = GetINetFwRules(); + var rule1 = Activator.CreateInstance(Type.GetTypeFromProgID("HNetCfg.FWRule")); + var rule3 = rule1 as INetFwRule3; + + rule3.Name = information.Name; + + if (!String.IsNullOrEmpty(information.Description)) + { + rule3.Description = information.Description; + } + + if (!String.IsNullOrEmpty(information.ApplicationName)) + { + rule3.ApplicationName = information.ApplicationName; + } + + if (!String.IsNullOrEmpty(information.ServiceName)) + { + rule3.serviceName = information.ServiceName; + } + + if (information.Protocol.HasValue) + { + rule3.Protocol = information.Protocol.Value; + } + + if (!String.IsNullOrEmpty(information.LocalPorts)) + { + rule3.LocalPorts = information.LocalPorts; + } + + if (!String.IsNullOrEmpty(information.RemotePorts)) + { + rule3.RemotePorts = information.RemotePorts; + } + + if (!String.IsNullOrEmpty(information.LocalAddresses)) + { + rule3.LocalAddresses = information.LocalAddresses; + } + + if (!String.IsNullOrEmpty(information.RemoteAddresses)) + { + rule3.RemoteAddresses = information.RemoteAddresses; + } + + if (!String.IsNullOrEmpty(information.IcmpTypesAndCodes)) + { + rule3.IcmpTypesAndCodes = information.IcmpTypesAndCodes; + } + + if (information.Direction.HasValue) + { + rule3.Direction = information.Direction.Value; + } + + if (information.Interfaces != null) + { + rule3.Interfaces = information.Interfaces; + } + + if (!String.IsNullOrEmpty(information.InterfaceTypes)) + { + rule3.InterfaceTypes = information.InterfaceTypes; + } + + if (information.Enabled.HasValue) + { + rule3.Enabled = information.Enabled.Value; + } + + if (!String.IsNullOrEmpty(information.Grouping)) + { + rule3.Grouping = information.Grouping; + } + + if (information.Profiles.HasValue) + { + rule3.Profiles = information.Profiles.Value; + } + + if (information.EdgeTraversal.HasValue) + { + rule3.EdgeTraversal = information.EdgeTraversal.Value; + } + + if (information.Action.HasValue) + { + rule3.Action = information.Action.Value; + } + + if (information.EdgeTraversalOptions.HasValue) + { + rule3.EdgeTraversalOptions = information.EdgeTraversalOptions.Value; + } + + if (!String.IsNullOrEmpty(information.LocalAppPackageId)) + { + rule3.LocalAppPackageId = information.LocalAppPackageId; + } + + if (!String.IsNullOrEmpty(information.LocalUserOwner)) + { + rule3.LocalUserOwner = information.LocalUserOwner; + } + + if (!String.IsNullOrEmpty(information.LocalUserAuthorizedList)) + { + rule3.LocalUserAuthorizedList = information.LocalUserAuthorizedList; + } + + if (!String.IsNullOrEmpty(information.RemoteUserAuthorizedList)) + { + rule3.RemoteUserAuthorizedList = information.RemoteUserAuthorizedList; + } + + if (!String.IsNullOrEmpty(information.RemoteMachineAuthorizedList)) + { + rule3.RemoteMachineAuthorizedList = information.RemoteMachineAuthorizedList; + } + + if (information.SecureFlags.HasValue) + { + rule3.SecureFlags = information.SecureFlags.Value; + } + + rules.Add(rule3); + } + + public static void UpdateFirewallRule(string name, RuleDetails information, UniqueCheck unique = null) + { + var rule = GetINetFwRule3(name, unique); + + // remove ports so the Protocol can be changed, if required + if (information.Protocol.HasValue && rule.Protocol != information.Protocol.Value) + { + rule.LocalPorts = null; + rule.RemotePorts = null; + } + + rule.Name = information.Name; + rule.Description = information.Description; + rule.Direction = information.Direction ?? NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN; + rule.ApplicationName = information.ApplicationName; + rule.serviceName = information.ServiceName; + rule.Protocol = information.Protocol ?? 256; + rule.LocalPorts = information.LocalPorts; + rule.RemotePorts = information.RemotePorts; + rule.LocalAddresses = information.LocalAddresses; + rule.RemoteAddresses = information.RemoteAddresses; + rule.IcmpTypesAndCodes = information.IcmpTypesAndCodes; + rule.Interfaces = information.Interfaces; + rule.InterfaceTypes = information.InterfaceTypes; + rule.Enabled = information.Enabled ?? false; + rule.Grouping = information.Grouping; + rule.Profiles = information.Profiles ?? 0x7fffffff; + rule.EdgeTraversal = information.EdgeTraversal ?? false; + rule.Action = information.Action ?? NET_FW_ACTION_.NET_FW_ACTION_ALLOW; + rule.EdgeTraversalOptions = information.EdgeTraversalOptions ?? 0x0; + rule.LocalAppPackageId = information.LocalAppPackageId; + rule.LocalUserOwner = information.LocalUserOwner; + rule.LocalUserAuthorizedList = information.LocalUserAuthorizedList; + rule.RemoteUserAuthorizedList = information.RemoteUserAuthorizedList; + rule.RemoteMachineAuthorizedList = information.RemoteMachineAuthorizedList; + rule.SecureFlags = information.SecureFlags ?? 0; + } + + public static void EnableFirewallRule(string name, UniqueCheck unique = null) + { + var rule = GetINetFwRule3(name, unique); + rule.Enabled = true; + } + + public static void DisableFirewallRule(string name, UniqueCheck unique = null) + { + var rule = GetINetFwRule3(name, unique); + rule.Enabled = false; + } + + public static void RemoveFirewallRulesByName(string name) + { + var rules = GetINetFwRules(); + rules.Remove(name); + } + + static string FormatErrorMessage(string name, string property, object expected, object actual, UniqueCheck unique) + { + return $"Assert Failure: {property} differ on rule: {name}" + + "\nExpected: " + expected + + "\nActual: " + actual + + "\n\nDirection: " + unique?.Direction + + "\nProfile: " + unique?.Profile + + "\nProtocol: " + unique?.Protocol + + "\nApplicationName: " + unique?.ApplicationName + + "\nLocalUserOwner: " + unique?.LocalUserOwner; + } + + public static void VerifyFirewallRule(string name, RuleDetails expected, UniqueCheck unique = null) + { + var actual = GetFirewallRule(name, unique); + Assert.True(expected.Name == actual.Name, String.Format("Assert Failure: Names differ on rule: \nExpected: {0}\nActual: {1}", expected.Name, actual.Name)); + Assert.True(expected.Description == actual.Description, FormatErrorMessage(name, "Descriptions", expected.Description, actual.Description, unique)); + Assert.True(expected.ApplicationName == actual.ApplicationName, FormatErrorMessage(name, "ApplicationNames", expected.ApplicationName, actual.ApplicationName, unique)); + Assert.True(expected.ServiceName == actual.ServiceName, FormatErrorMessage(name, "ServiceNames", expected.ServiceName, actual.ServiceName, unique)); + Assert.True(expected.Protocol == actual.Protocol, FormatErrorMessage(name, "Protocols", expected.Protocol, actual.Protocol, unique)); + Assert.True(expected.LocalPorts == actual.LocalPorts, FormatErrorMessage(name, "LocalPorts", expected.LocalPorts, actual.LocalPorts, unique)); + Assert.True(expected.RemotePorts == actual.RemotePorts, FormatErrorMessage(name, "RemotePorts", expected.RemotePorts, actual.RemotePorts, unique)); + Assert.True(expected.IcmpTypesAndCodes == actual.IcmpTypesAndCodes, FormatErrorMessage(name, "IcmpTypesAndCodes", expected.IcmpTypesAndCodes, actual.Description, unique)); + Assert.True(expected.Direction == actual.Direction, FormatErrorMessage(name, "Directions", expected.Direction, actual.Direction, unique)); + Assert.Equal(expected.Interfaces, actual.Interfaces); + Assert.True(expected.InterfaceTypes == actual.InterfaceTypes, FormatErrorMessage(name, "InterfaceTypes", expected.InterfaceTypes, actual.InterfaceTypes, unique)); + Assert.True(expected.Enabled == actual.Enabled, FormatErrorMessage(name, "Enabled flags", expected.Enabled, actual.Enabled, unique)); + Assert.True(expected.Grouping == actual.Grouping, FormatErrorMessage(name, "Groupings", expected.Grouping, actual.Grouping, unique)); + Assert.True(expected.Profiles == actual.Profiles, FormatErrorMessage(name, "Profiles", expected.Profiles, actual.Profiles, unique)); + Assert.True(expected.EdgeTraversal == actual.EdgeTraversal, FormatErrorMessage(name, "EdgeTraversals", expected.EdgeTraversal, actual.EdgeTraversal, unique)); + Assert.True(expected.Action == actual.Action, FormatErrorMessage(name, "Actions", expected.Action, actual.Action, unique)); + Assert.True(expected.EdgeTraversalOptions == actual.EdgeTraversalOptions, FormatErrorMessage(name, "EdgeTraversalOptions", expected.EdgeTraversalOptions, actual.EdgeTraversalOptions, unique)); + Assert.True(expected.LocalAppPackageId == actual.LocalAppPackageId, FormatErrorMessage(name, "LocalAppPackageIds", expected.LocalAppPackageId, actual.LocalAppPackageId, unique)); + Assert.True(expected.LocalUserOwner == actual.LocalUserOwner, FormatErrorMessage(name, "LocalUserOwners", expected.LocalUserOwner, actual.LocalUserOwner, unique)); + Assert.True(expected.LocalUserAuthorizedList == actual.LocalUserAuthorizedList, FormatErrorMessage(name, "LocalUserAuthorizedLists", expected.LocalUserAuthorizedList, actual.LocalUserAuthorizedList, unique)); + Assert.True(expected.RemoteUserAuthorizedList == actual.RemoteUserAuthorizedList, FormatErrorMessage(name, "RemoteUserAuthorizedLists", expected.RemoteUserAuthorizedList, actual.RemoteUserAuthorizedList, unique)); + Assert.True(expected.RemoteMachineAuthorizedList == actual.RemoteMachineAuthorizedList, FormatErrorMessage(name, "RemoteMachineAuthorizedLists", expected.RemoteMachineAuthorizedList, actual.RemoteMachineAuthorizedList, unique)); + Assert.True(expected.SecureFlags == actual.SecureFlags, FormatErrorMessage(name, "SecureFlags", expected.SecureFlags, actual.SecureFlags, unique)); + } + } +} diff --git a/src/test/burn/WixTestTools/WixTestTools.csproj b/src/test/burn/WixTestTools/WixTestTools.csproj index 65db5a600..8d8f11cce 100644 --- a/src/test/burn/WixTestTools/WixTestTools.csproj +++ b/src/test/burn/WixTestTools/WixTestTools.csproj @@ -7,6 +7,17 @@ x64 true + + + 0 + 1 + 58fbcf7c-e7a9-467c-80b3-fc65e8fcca08 + 0 + tlbimp + false + False + + diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/FirewallRules.wixproj b/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/FirewallRules.wixproj new file mode 100644 index 000000000..b1770b0f4 --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/FirewallRules.wixproj @@ -0,0 +1,13 @@ + + + + {4D188568-1CCF-4EEE-BC27-17C3DCC83E58} + true + + + + + + + + \ No newline at end of file diff --git a/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/product.wxs b/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/product.wxs new file mode 100644 index 000000000..e8ce54bfb --- /dev/null +++ b/src/test/msi/TestData/FirewallExtensionTests/FirewallRules/product.wxs @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs new file mode 100644 index 000000000..df6dae913 --- /dev/null +++ b/src/test/msi/WixToolsetTest.MsiE2E/FirewallExtensionTests.cs @@ -0,0 +1,205 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolsetTest.MsiE2E +{ + using System; + using System.IO; + using NetFwTypeLib; + using WixTestTools; + using WixTestTools.Firewall; + using Xunit; + using Xunit.Abstractions; + + public class FirewallExtensionTests : MsiE2ETests + { + public FirewallExtensionTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + [RuntimeFact] + public void VerifierSelfTest() + { + // A lot of firewall rules don't follow the Microsoft recommendation of using unique exception rule names. + // Disambiguate rules by a unique check on Name, Direction, Profiles, Protocol, ApplicationName and the LocalUserOwner. + foreach (var expected in Verifier.GetFirewallRules()) + { + Verifier.VerifyFirewallRule(expected.Name, expected, new UniqueCheck(expected)); + } + } + + [RuntimeFact] + public void CanInstallAndUninstallFirewallRulesWithMinimalProperties() + { + var product = this.CreatePackageInstaller("FirewallRules"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // Validate new firewall exception details. + var expected1 = new RuleDetails("WiXToolset401 Test - 0001") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), + Description = "WiX Toolset firewall exception rule integration test - minimal app properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = true, + EdgeTraversalOptions = 1, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + Profiles = Int32.MaxValue, + Protocol = 256, + RemoteAddresses = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0001", expected1); + + var expected2 = new RuleDetails("WiXToolset401 Test - 0002") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - minimal port properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "23456", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0002", expected2); + + product.UninstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + // verify the firewall exceptions have been removed. + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0001")); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0002")); + } + + [RuntimeFact] + public void DisabledPortFirewallRuleIsEnabledAfterRepair() + { + var product = this.CreatePackageInstaller("FirewallRules"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + Verifier.DisableFirewallRule("WiXToolset401 Test - 0002"); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected = new RuleDetails("WiXToolset401 Test - 0002") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - minimal port properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "23456", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0002", expected); + } + + [RuntimeFact] + public void DisabledApplicationFirewallRuleIsEnabledAfterRepair() + { + var product = this.CreatePackageInstaller("FirewallRules"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + Verifier.DisableFirewallRule("WiXToolset401 Test - 0001"); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected = new RuleDetails("WiXToolset401 Test - 0001") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), + Description = "WiX Toolset firewall exception rule integration test - minimal app properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = true, + EdgeTraversalOptions = 1, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + Profiles = Int32.MaxValue, + Protocol = 256, + RemoteAddresses = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0001", expected); + } + + [RuntimeFact] + public void MissingPortFirewallRuleIsAddedAfterRepair() + { + var product = this.CreatePackageInstaller("FirewallRules"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + Verifier.RemoveFirewallRulesByName("WiXToolset401 Test - 0002"); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0002")); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected = new RuleDetails("WiXToolset401 Test - 0002") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + Description = "WiX Toolset firewall exception rule integration test - minimal port properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = false, + EdgeTraversalOptions = 0, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + LocalPorts = "23456", + Profiles = Int32.MaxValue, + Protocol = 6, + RemoteAddresses = "*", + RemotePorts = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0002", expected); + } + + [RuntimeFact] + public void MissingApplicationFirewallRuleIsAddedAfterRepair() + { + var product = this.CreatePackageInstaller("FirewallRules"); + product.InstallProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + Verifier.RemoveFirewallRulesByName("WiXToolset401 Test - 0001"); + Assert.False(Verifier.FirewallRuleExists("WiXToolset401 Test - 0001")); + + product.RepairProduct(MSIExec.MSIExecReturnCode.SUCCESS); + + var expected = new RuleDetails("WiXToolset401 Test - 0001") + { + Action = NET_FW_ACTION_.NET_FW_ACTION_ALLOW, + ApplicationName = this.TestContext.GetTestInstallFolder(false, Path.Combine("FirewallRules", "product.wxs")), + Description = "WiX Toolset firewall exception rule integration test - minimal app properties", + Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN, + EdgeTraversal = true, + EdgeTraversalOptions = 1, + Enabled = true, + InterfaceTypes = "All", + LocalAddresses = "*", + Profiles = Int32.MaxValue, + Protocol = 256, + RemoteAddresses = "*", + SecureFlags = 0, + }; + + Verifier.VerifyFirewallRule("WiXToolset401 Test - 0001", expected); + } + } +} diff --git a/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj index d64c942ae..a5536de43 100644 --- a/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj +++ b/src/test/msi/WixToolsetTest.MsiE2E/WixToolsetTest.MsiE2E.csproj @@ -7,6 +7,17 @@ x64 true + + + 0 + 1 + 58fbcf7c-e7a9-467c-80b3-fc65e8fcca08 + 0 + tlbimp + false + False + +