diff --git a/firewall/interception.go b/firewall/interception.go index 858ae8e9..25e0354b 100644 --- a/firewall/interception.go +++ b/firewall/interception.go @@ -25,9 +25,6 @@ import ( "github.com/safing/portmaster/network" "github.com/safing/portmaster/network/netutils" "github.com/safing/portmaster/network/packet" - "github.com/safing/spn/captain" - "github.com/safing/spn/crew" - "github.com/safing/spn/sluice" ) var ( @@ -335,9 +332,6 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true - // Set tunnel options. - setCustomTunnelOptionsForPortmaster(conn) - // Redirect outbound DNS packests, case pkt.IsOutbound() && pkt.Info().DstPort == 53 && @@ -368,41 +362,6 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { conn.Accept("privacy filter disabled", noReasonOptionKey) } - // Tunnel, if enabled. - if pkt.IsOutbound() && conn.Entity.IPScope.IsGlobal() && - tunnelEnabled() && conn.Verdict == network.VerdictAccept && - conn.Process().Profile() != nil && - conn.Process().Profile().UseSPN() { - - switch { - case captain.ClientBootstrapping() && - conn.Process().Pid == ownPID: - // Exclude the Portmaster during SPN bootstrapping. - - case captain.IsExcepted(conn.Entity.IP) && - conn.Process().Pid == ownPID: - // Exclude requests of the SPN itself. - - case captain.ClientReady(): - // Queue request in sluice. - err := sluice.AwaitRequest(conn, crew.HandleSluiceRequest) - if err != nil { - log.Tracer(pkt.Ctx()).Warningf("failed to rqeuest tunneling: %s", err) - conn.Failed("failed to request tunneling", "") - } else { - log.Tracer(pkt.Ctx()).Trace("filter: tunneling requested") - conn.Verdict = network.VerdictRerouteToTunnel - conn.Tunneled = true - } - - default: - // Block connection as SPN is not ready yet. - log.Tracer(pkt.Ctx()).Trace("SPN not ready for tunneling") - conn.Failed("SPN not ready for tunneling", "") - - } - } - // TODO: Enable inspection framework again. conn.Inspecting = false @@ -419,6 +378,9 @@ func initialHandler(conn *network.Connection, pkt packet.Packet) { conn.Encrypted = true } + // Check if connection should be tunneled. + checkTunneling(pkt.Ctx(), conn, pkt) + switch { case conn.Inspecting: log.Tracer(pkt.Ctx()).Trace("filter: start inspecting") diff --git a/firewall/master.go b/firewall/master.go index fd02f68e..16dc2b5f 100644 --- a/firewall/master.go +++ b/firewall/master.go @@ -140,10 +140,6 @@ func checkPortmasterConnection(ctx context.Context, conn *network.Connection, _ log.Tracer(ctx).Infof("filter: granting own connection %s", conn) conn.Accept("connection by Portmaster", noReasonOptionKey) conn.Internal = true - - // Set tunnel options. - setCustomTunnelOptionsForPortmaster(conn) - return true } diff --git a/firewall/tunnel.go b/firewall/tunnel.go index 27435466..b0add3a8 100644 --- a/firewall/tunnel.go +++ b/firewall/tunnel.go @@ -1,23 +1,140 @@ package firewall import ( + "context" + + "github.com/safing/portbase/log" "github.com/safing/portmaster/network" + "github.com/safing/portmaster/network/packet" + "github.com/safing/portmaster/profile" + "github.com/safing/portmaster/profile/endpoints" "github.com/safing/portmaster/resolver" + "github.com/safing/spn/captain" + "github.com/safing/spn/crew" "github.com/safing/spn/navigator" + "github.com/safing/spn/sluice" ) -func setCustomTunnelOptionsForPortmaster(conn *network.Connection) { +func checkTunneling(ctx context.Context, conn *network.Connection, pkt packet.Packet) { + // Check if the connection should be tunneled at all. switch { case !tunnelEnabled(): - // Ignore when tunneling is not enabled. + // Tunneling is disabled. return case !conn.Entity.IPScope.IsGlobal(): - // Ignore if destination is not in global address space. + // Can't tunnel Local/LAN connections. return - case resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port): - // Set custom tunnel options for DNS servers. - conn.TunnelOpts = &navigator.Options{ - RoutingProfile: navigator.RoutingProfileHomeName, + case conn.Inbound: + // Can't tunnel incoming connections. + return + case conn.Verdict != network.VerdictAccept: + // Connection will be blocked. + return + case conn.Process().Pid == ownPID: + // Bypass tunneling for certain own connections. + switch { + case captain.ClientBootstrapping(): + return + case captain.IsExcepted(conn.Entity.IP): + return } } + + // Get profile. + layeredProfile := conn.Process().Profile() + if layeredProfile == nil { + conn.Failed("no profile set", "") + return + } + + // Update profile. + if layeredProfile.NeedsUpdate() { + // Update revision counter in connection. + conn.ProfileRevisionCounter = layeredProfile.Update() + conn.SaveWhenFinished() + } else { + // Check if the revision counter of the connection needs updating. + revCnt := layeredProfile.RevisionCnt() + if conn.ProfileRevisionCounter != revCnt { + conn.ProfileRevisionCounter = revCnt + conn.SaveWhenFinished() + } + } + + // Check if tunneling is enabeld for this app at all. + if !layeredProfile.UseSPN() { + return + } + + // Check if tunneling is enabled for entity. + conn.Entity.FetchData(ctx) + result, _ := layeredProfile.MatchSPNUsagePolicy(ctx, conn.Entity) + switch result { + case endpoints.MatchError: + conn.Failed("failed to check SPN rules", profile.CfgOptionSPNUsagePolicyKey) + return + case endpoints.Denied: + return + case endpoints.Permitted, endpoints.NoMatch: + // Continue + } + + // Tunnel all the things! + + // Check if ready. + if !captain.ClientReady() { + // Block connection as SPN is not ready yet. + log.Tracer(pkt.Ctx()).Trace("SPN not ready for tunneling") + conn.Failed("SPN not ready for tunneling", "") + return + } + + // Set options. + conn.TunnelOpts = &navigator.Options{ + HubPolicies: layeredProfile.StackedExitHubPolicies(), + CheckHubExitPolicyWith: conn.Entity, + RequireTrustedDestinationHubs: !conn.Encrypted, + RoutingProfile: layeredProfile.SPNRoutingAlgorithm(), + } + + // If we have any exit hub policies, we need to raise the routing algorithm at least to single-hop. + if conn.TunnelOpts.RoutingProfile == navigator.RoutingProfileHomeID && + conn.TunnelOpts.HubPoliciesAreSet() { + conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID + } + + // Special handling for the internal DNS resolver. + if conn.Process().Pid == ownPID && resolver.IsResolverAddress(conn.Entity.IP, conn.Entity.Port) { + dnsExitHubPolicy, err := captain.GetDNSExitHubPolicy() + if err != nil { + log.Errorf("firewall: failed to get dns exit hub policy: %s", err) + } + + if err == nil && dnsExitHubPolicy.IsSet() { + // Apply the dns exit hub policy, if set. + conn.TunnelOpts.HubPolicies = []endpoints.Endpoints{dnsExitHubPolicy} + // Use the routing algorithm from the profile, as the home profile won't work with the policy. + conn.TunnelOpts.RoutingProfile = layeredProfile.SPNRoutingAlgorithm() + // Raise the routing algorithm at least to single-hop. + if conn.TunnelOpts.RoutingProfile == navigator.RoutingProfileHomeID { + conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileSingleHopID + } + } else { + // Disable any policies for the internal DNS resolver. + conn.TunnelOpts.HubPolicies = nil + // Always use the home routing profile for the internal DNS resolver. + conn.TunnelOpts.RoutingProfile = navigator.RoutingProfileHomeID + } + } + + // Queue request in sluice. + err := sluice.AwaitRequest(conn, crew.HandleSluiceRequest) + if err != nil { + log.Tracer(pkt.Ctx()).Warningf("failed to request tunneling: %s", err) + conn.Failed("failed to request tunneling", "") + } else { + log.Tracer(pkt.Ctx()).Trace("filter: tunneling requested") + conn.Verdict = network.VerdictRerouteToTunnel + conn.Tunneled = true + } } diff --git a/go.mod b/go.mod index 8317643a..f6ecb616 100644 --- a/go.mod +++ b/go.mod @@ -7,24 +7,22 @@ require ( github.com/cookieo9/resources-go v0.0.0-20150225115733-d27c04069d0d github.com/coreos/go-iptables v0.6.0 github.com/florianl/go-nfqueue v1.3.0 - github.com/godbus/dbus/v5 v5.0.6 + github.com/godbus/dbus/v5 v5.1.0 github.com/google/gopacket v1.1.19 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-version v1.4.0 - github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect - github.com/mdlayher/netlink v1.6.0 // indirect - github.com/mdlayher/socket v0.2.0 // indirect + github.com/mdlayher/socket v0.2.2 // indirect github.com/miekg/dns v1.1.46 github.com/oschwald/maxminddb-golang v1.8.0 github.com/safing/portbase v0.14.0 - github.com/safing/spn v0.4.2 + github.com/safing/spn v0.4.3 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cobra v1.3.0 github.com/stretchr/testify v1.7.0 github.com/tannerryan/ring v1.1.2 github.com/tevino/abool v1.2.0 github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 - golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd + golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 ) diff --git a/go.sum b/go.sum index 4bbdeeab..247f730e 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,6 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.0.7/go.mod h1:mlizQTaPjnR4jcpwRSaSlkbsRfYFEyKgLQvYTzxxiHA= -github.com/AlecAivazis/survey/v2 v2.3.2 h1:TqTB+aDDCLYhf9/bD2TwSO8u8jDSmMUd2SUVO4gCnU8= -github.com/AlecAivazis/survey/v2 v2.3.2/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M= github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0= @@ -360,8 +358,9 @@ github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e h1:BWhy2j3IXJhjCbC68Fp github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.5/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= @@ -474,8 +473,9 @@ github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosimple/slug v1.10.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw= github.com/gosimple/unidecode v1.0.0/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= @@ -670,13 +670,12 @@ github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZY github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g= github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= -github.com/mdlayher/socket v0.2.0 h1:EY4YQd6hTAg2tcXF84p5DTHazShE50u5HeBzBaNgjkA= github.com/mdlayher/socket v0.2.0/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= +github.com/mdlayher/socket v0.2.2 h1:UOh5gQk70kRl1YMLCTRwRF4MvsAQsudjkEA+ZDXS4jo= +github.com/mdlayher/socket v0.2.2/go.mod h1:IcNFWYJJuSGgnfKie27UfpEDWytPDqy+TrDd9I5hUKQ= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -806,9 +805,7 @@ github.com/safing/portbase v0.13.1/go.mod h1:5vj5IK5WJoSGareDe6yCMZfnF7txVRx7jZy github.com/safing/portbase v0.13.2/go.mod h1:5vj5IK5WJoSGareDe6yCMZfnF7txVRx7jZyTZInISP0= github.com/safing/portbase v0.13.3/go.mod h1:5vj5IK5WJoSGareDe6yCMZfnF7txVRx7jZyTZInISP0= github.com/safing/portbase v0.13.4/go.mod h1:5vj5IK5WJoSGareDe6yCMZfnF7txVRx7jZyTZInISP0= -github.com/safing/portbase v0.13.5 h1:WtDvnTh8reBMnhPiyAr62qkBeBUri0EaNlb0u2msNNM= github.com/safing/portbase v0.13.5/go.mod h1:5vj5IK5WJoSGareDe6yCMZfnF7txVRx7jZyTZInISP0= -github.com/safing/portbase v0.13.6 h1:0tAa4fyCdlZy4J+Ne9G5JshV+Cmu+Ol0m5AftPHnjvE= github.com/safing/portbase v0.13.6/go.mod h1:G0maDSQxYDuluNhMzA1zVd/nfXawfECv5H7+fnTfVhM= github.com/safing/portbase v0.14.0 h1:6+sdUs1tdRCKnyuzy/zHrvUsdO1GdI0l4gZaoYJmJ5Q= github.com/safing/portbase v0.14.0/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI= @@ -824,6 +821,7 @@ github.com/safing/portmaster v0.7.17/go.mod h1:TCWK75oZtkWv9bPyYIAILOma5G6VSK5FD github.com/safing/portmaster v0.7.18/go.mod h1:NXd1l1z0oKy0WfXMhv/gpAXz+pUHnlNuYPBSaMZe7uw= github.com/safing/portmaster v0.7.21/go.mod h1:Jy0G6x6m5dE36Mv9grXHI77cxysQ0fIQV1EYQ00WEiQ= github.com/safing/portmaster v0.8.0/go.mod h1:lY2/WvOlH8kl1AwkixdWCjlo+PZQv+oEOQhIaSS/+wA= +github.com/safing/portmaster v0.8.5-interdep/go.mod h1:A+zAVEKjr057ktgiMSJRdUmOF+FPW8XY/5LqGnbsKbU= github.com/safing/spn v0.3.4/go.mod h1:TfzNsZCbnlWv0UFDILFOUSudVKJZlnBVoR1fDXrjOK0= github.com/safing/spn v0.3.5/go.mod h1:jHkFF2Yu1fnjFu4KXjVA+iagMr/z4eB4p3jiwikvKj8= github.com/safing/spn v0.3.6/go.mod h1:RSeFb/h5Wt3yDVezXj3lhXJ/Iwd7FbtsGf5E+p5J2YQ= @@ -835,12 +833,10 @@ github.com/safing/spn v0.3.15/go.mod h1:SXlGMeYLgfMcpT1aXI1Iaw07iy6Dy6+9KaD2p+HU github.com/safing/spn v0.3.16/go.mod h1:NMaHlnIHcTHMxxz6PuaBg0rJdYc8LhyF5N6v+Iie+k0= github.com/safing/spn v0.3.17/go.mod h1:Fq/70Hl0OUxtYuY5NATv5q468hvfDDEFwN3mivEecic= github.com/safing/spn v0.3.19/go.mod h1:phCnWjWOgdVMXaMsmDr6izR/ROVElSZGdIm7j7PIit4= -github.com/safing/spn v0.4.0 h1:7bUdeOAt65s7RCuwiuPrJU/E0kUIhS5I5dcXTZ3xtEc= github.com/safing/spn v0.4.0/go.mod h1:0jBetnYCfxqO5PJskhPOxJ/v6VRfE+bQU98XW240BNw= -github.com/safing/spn v0.4.1 h1:ikotIeXN5dX6O29mmXVbL8sYA21FL774ITq9zmzmPbQ= -github.com/safing/spn v0.4.1/go.mod h1:yZPezHDEYyhei8n13tTxjQCGq6LRr5svz9WFAAeDPec= -github.com/safing/spn v0.4.2 h1:7ETU2XsU0gT94VTrKbIEC9q8ws7XlzkCfuZR8wtVp48= github.com/safing/spn v0.4.2/go.mod h1:yZPezHDEYyhei8n13tTxjQCGq6LRr5svz9WFAAeDPec= +github.com/safing/spn v0.4.3 h1:iEFmpzyrThJ8QF9Qpbxk/m4w2+ZvbVPyuqJ4EwnpfDg= +github.com/safing/spn v0.4.3/go.mod h1:YHtg3FkZviN8T7db4BdRffbYO1pO7w9SydQatLmvW2M= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= @@ -945,8 +941,9 @@ github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp1 github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= +github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= @@ -1061,7 +1058,6 @@ golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506 h1:EuGTJDfeg/PGZJp3gq1K+14eSLFTsrj1eg8KQuiUyKg= golang.org/x/crypto v0.0.0-20220213190939-1e6e3497d506/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1179,8 +1175,9 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220121210141-e204ce36a2ba/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1330,16 +1327,16 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/profile/config-update.go b/profile/config-update.go index 2e6a1af6..f8979d3d 100644 --- a/profile/config-update.go +++ b/profile/config-update.go @@ -18,6 +18,8 @@ var ( cfgDefaultAction uint8 cfgEndpoints endpoints.Endpoints cfgServiceEndpoints endpoints.Endpoints + cfgSPNUsagePolicy endpoints.Endpoints + cfgSPNExitHubPolicy endpoints.Endpoints cfgFilterLists []string ) @@ -75,6 +77,20 @@ func updateGlobalConfigProfile(ctx context.Context, task *modules.Task) error { lastErr = err } + list = cfgOptionSPNUsagePolicy() + cfgSPNUsagePolicy, err = endpoints.ParseEndpoints(list) + if err != nil { + // TODO: module error? + lastErr = err + } + + list = cfgOptionExitHubPolicy() + cfgSPNExitHubPolicy, err = endpoints.ParseEndpoints(list) + if err != nil { + // TODO: module error? + lastErr = err + } + // build global profile for reference profile := New(SourceSpecial, "global-config", "", nil) profile.Name = "Global Configuration" diff --git a/profile/config.go b/profile/config.go index 8324ef13..e9d9f5ef 100644 --- a/profile/config.go +++ b/profile/config.go @@ -98,9 +98,27 @@ var ( // Setting "Permanent Verdicts" at order 96. + // Setting "Enable SPN" at order 128. + CfgOptionUseSPNKey = "spn/use" cfgOptionUseSPN config.BoolOption cfgOptionUseSPNOrder = 129 + + CfgOptionSPNUsagePolicyKey = "spn/usagePolicy" + cfgOptionSPNUsagePolicy config.StringArrayOption + cfgOptionSPNUsagePolicyOrder = 130 + + CfgOptionRoutingAlgorithmKey = "spn/routingAlgorithm" + cfgOptionRoutingAlgorithm config.StringOption + cfgOptionRoutingAlgorithmOrder = 144 + + // Setting "Home Node Rules" at order 145. + + CfgOptionExitHubPolicyKey = "spn/exitHubPolicy" + cfgOptionExitHubPolicy config.StringArrayOption + cfgOptionExitHubPolicyOrder = 146 + + // Setting "DNS Exit Node Rules" at order 147. ) // A list of all security level settings. @@ -119,6 +137,39 @@ var securityLevelSettings = []string{ CfgOptionDisableAutoPermitKey, } +var ( + // SPNRulesQuickSettings is a list of countries the SPN currently is present in + // as quick settings in order to help users with SPN related policy settings. + // This is a quick win to make the MVP easier to use, but will be replaced by + // a better solution in the future. + SPNRulesQuickSettings = []config.QuickSetting{ + {Name: "Exclude Canada (CA)", Action: config.QuickMergeTop, Value: []string{"- CA"}}, + {Name: "Exclude Finland (FI)", Action: config.QuickMergeTop, Value: []string{"- FI"}}, + {Name: "Exclude France (FR)", Action: config.QuickMergeTop, Value: []string{"- FR"}}, + {Name: "Exclude Germany (DE)", Action: config.QuickMergeTop, Value: []string{"- DE"}}, + {Name: "Exclude Israel (IL)", Action: config.QuickMergeTop, Value: []string{"- IL"}}, + {Name: "Exclude Poland (PL)", Action: config.QuickMergeTop, Value: []string{"- PL"}}, + {Name: "Exclude United Kingdom (GB)", Action: config.QuickMergeTop, Value: []string{"- GB"}}, + {Name: "Exclude United States of America (US)", Action: config.QuickMergeTop, Value: []string{"- US"}}, + } + + // SPNRulesVerdictNames defines the verdicts names to be used for SPN Rules. + SPNRulesVerdictNames = map[string]string{ + "-": "Exclude", // Default. + "+": "Allow", + } + + // SPNRulesHelp defines the help text for SPN related Hub selection rules. + SPNRulesHelp = strings.ReplaceAll(`Rules are checked from top to bottom, stopping after the first match. They can match the following attributes of SPN Nodes: + +- Country (based on IPs): "US" +- AS number: "AS123456" +- Address: "192.168.0.1" +- Network: "192.168.0.1/24" +- Anything: "*" +`, `"`, "`") +) + func registerConfiguration() error { //nolint:maintidx // Default Filter Action // permit - blocklist mode: everything is allowed unless blocked @@ -207,15 +258,6 @@ Examples: "192.168.0.1 TCP/HTTP", "LAN UDP/50000-55000", "example.com */HTTPS", Important: DNS Requests are only matched against domain and filter list rules, all others require an IP address and are checked only with the following IP connection. `, `"`, "`") - rulesValidationRegex := strings.Join([]string{ - `^(\+|\-) `, // Rule verdict. - `[A-z0-9\.:\-*/]+`, // Entity matching. - `( `, // Start of optional matching. - `[A-z0-9*]+`, // Protocol matching. - `(/[A-z0-9]+(\-[A-z0-9]+)?)?`, // Port and port range matching. - `)?$`, // End of optional matching. - }, "") - // Endpoint Filter List err = config.Register(&config.Option{ Name: "Outgoing Rules", @@ -230,7 +272,8 @@ Important: DNS Requests are only matched against domain and filter list rules, a config.DisplayOrderAnnotation: cfgOptionEndpointsOrder, config.CategoryAnnotation: "Rules", }, - ValidationRegex: rulesValidationRegex, + ValidationRegex: endpoints.ListEntryValidationRegex, + ValidationFunc: endpoints.ValidateEndpointListConfigOption, }) if err != nil { return err @@ -270,7 +313,8 @@ Important: DNS Requests are only matched against domain and filter list rules, a }, }, }, - ValidationRegex: rulesValidationRegex, + ValidationRegex: endpoints.ListEntryValidationRegex, + ValidationFunc: endpoints.ValidateEndpointListConfigOption, }) if err != nil { return err @@ -570,5 +614,98 @@ Please note that if you are using the system resolver, bypass attempts might be cfgOptionUseSPN = config.Concurrent.GetAsBool(CfgOptionUseSPNKey, true) cfgBoolOptions[CfgOptionUseSPNKey] = cfgOptionUseSPN + // SPN Rules + err = config.Register(&config.Option{ + Name: "SPN Rules", + Key: CfgOptionSPNUsagePolicyKey, + Description: `Customize which websites should or should not be routed through the SPN. Only active if "Use SPN" is enabled.`, + Help: rulesHelp, + OptType: config.OptTypeStringArray, + DefaultValue: []string{}, + Annotations: config.Annotations{ + config.StackableAnnotation: true, + config.CategoryAnnotation: "General", + config.DisplayOrderAnnotation: cfgOptionSPNUsagePolicyOrder, + config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList, + endpoints.EndpointListVerdictNamesAnnotation: SPNRulesVerdictNames, + }, + ValidationRegex: endpoints.ListEntryValidationRegex, + ValidationFunc: endpoints.ValidateEndpointListConfigOption, + }) + if err != nil { + return err + } + cfgOptionSPNUsagePolicy = config.Concurrent.GetAsStringArray(CfgOptionSPNUsagePolicyKey, []string{}) + cfgStringArrayOptions[CfgOptionSPNUsagePolicyKey] = cfgOptionSPNUsagePolicy + + // Exit Node Rules + err = config.Register(&config.Option{ + Name: "Exit Node Rules", + Key: CfgOptionExitHubPolicyKey, + Description: `Customize which countries should or should not be used for your Exit Nodes. Exit Nodes are used to exit the SPN and establish a connection to your destination. + +By default, the Portmaster tries to choose the node closest to the destination as the Exit Node. This reduces your exposure to the open Internet. Exit Nodes are chosen for every destination separately.`, + Help: SPNRulesHelp, + OptType: config.OptTypeStringArray, + DefaultValue: []string{}, + Annotations: config.Annotations{ + config.StackableAnnotation: true, + config.CategoryAnnotation: "Routing", + config.DisplayOrderAnnotation: cfgOptionExitHubPolicyOrder, + config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList, + config.QuickSettingsAnnotation: SPNRulesQuickSettings, + endpoints.EndpointListVerdictNamesAnnotation: SPNRulesVerdictNames, + }, + ValidationRegex: endpoints.ListEntryValidationRegex, + ValidationFunc: endpoints.ValidateEndpointListConfigOption, + }) + if err != nil { + return err + } + cfgOptionExitHubPolicy = config.Concurrent.GetAsStringArray(CfgOptionExitHubPolicyKey, []string{}) + cfgStringArrayOptions[CfgOptionExitHubPolicyKey] = cfgOptionExitHubPolicy + + // Select SPN Routing Algorithm + defaultRoutingAlg := "double-hop" + err = config.Register(&config.Option{ + Name: "Select SPN Routing Algorithm", + Key: CfgOptionRoutingAlgorithmKey, + Description: "Select the routing algorithm for your connections through the SPN. Configure your preferred balance between speed and privacy.", + OptType: config.OptTypeString, + DefaultValue: defaultRoutingAlg, + Annotations: config.Annotations{ + config.DisplayHintAnnotation: config.DisplayHintOneOf, + config.DisplayOrderAnnotation: cfgOptionRoutingAlgorithmOrder, + config.CategoryAnnotation: "Routing", + }, + PossibleValues: []config.PossibleValue{ + { + Name: "Plain VPN Mode", + Value: "home", + Description: "Always connect to the destination directly from the Home Hub. Only provides very basic privacy, as the Home Hub both knows where you are coming from and where you are connecting to.", + }, + { + Name: "Speed Focused", + Value: "single-hop", + Description: "Optimize routes with a minimum of one hop. Provides good speeds. This will often use the Home Hub to connect to destinations near you, but will use more hops to far away destinations for better privacy over long distances.", + }, + { + Name: "Balanced", + Value: "double-hop", + Description: "Optimize routes with a minimum of two hops. Provides good privacy as well as good speeds. No single node knows where you are coming from *and* where you are connecting to.", + }, + { + Name: "Privacy Focused", + Value: "triple-hop", + Description: "Optimize routes with a minimum of three hops. Provides very good privacy. No single node knows where you are coming from *and* where you are connecting to - with an additional hop just to be sure.", + }, + }, + }) + if err != nil { + return err + } + cfgOptionRoutingAlgorithm = config.Concurrent.GetAsString(CfgOptionRoutingAlgorithmKey, defaultRoutingAlg) + cfgStringOptions[CfgOptionRoutingAlgorithmKey] = cfgOptionRoutingAlgorithm + return nil } diff --git a/profile/endpoints/annotations.go b/profile/endpoints/annotations.go index bb37d048..8eea6f8b 100644 --- a/profile/endpoints/annotations.go +++ b/profile/endpoints/annotations.go @@ -4,21 +4,10 @@ package endpoints // list option. It's meant to be used with DisplayHintAnnotation. const DisplayHintEndpointList = "endpoint list" -// EndpointListAnnotation is the annotation identifier used in configuration -// options to hint the UI on available endpoint list types. If configured, only -// the specified set of entities is allowed to be used. The value is expected -// to be a single string or []string. If this annotation is missing, all -// values are expected to be allowed. -const EndpointListAnnotation = "safing/portmaster:ui:endpoint-list" - -// Allowed values for the EndpointListAnnotation. -const ( - EndpointListIP = "ip" - EndpointListAsn = "asn" - EndpointListCountry = "country" - EndpointListDomain = "domain" - EndpointListIPRange = "iprange" - EndpointListLists = "lists" - EndpointListScopes = "scopes" - EndpointListProtocolAndPorts = "protocol-port" -) +// EndpointListVerdictNamesAnnotation is the annotation identifier used in +// configuration options to hint the UI on names to be used for endpoint list +// verdicts. +// If configured, it must be of type map[string]string, mapping the verdict +// symbol to a name to be displayed in the UI. +// May only used when config.DisplayHintAnnotation is set to DisplayHintEndpointList. +const EndpointListVerdictNamesAnnotation = "safing/portmaster:ui:endpoint-list:verdict-names" diff --git a/profile/endpoints/endpoints.go b/profile/endpoints/endpoints.go index 1f158017..7b16fab5 100644 --- a/profile/endpoints/endpoints.go +++ b/profile/endpoints/endpoints.go @@ -2,6 +2,7 @@ package endpoints import ( "context" + "errors" "fmt" "strings" @@ -58,6 +59,27 @@ entriesLoop: return endpoints, nil } +// ListEntryValidationRegex is a regex to bullshit check endpoint list entries. +var ListEntryValidationRegex = strings.Join([]string{ + `^(\+|\-) `, // Rule verdict. + `[A-z0-9\.:\-*/]+`, // Entity matching. + `( `, // Start of optional matching. + `[A-z0-9*]+`, // Protocol matching. + `(/[A-z0-9]+(\-[A-z0-9]+)?)?`, // Port and port range matching. + `)?$`, // End of optional matching. +}, "") + +// ValidateEndpointListConfigOption validates the given value. +func ValidateEndpointListConfigOption(value interface{}) error { + list, ok := value.([]string) + if !ok { + return errors.New("invalid type") + } + + _, err := ParseEndpoints(list) + return err +} + // IsSet returns whether the Endpoints object is "set". func (e Endpoints) IsSet() bool { return len(e) > 0 diff --git a/profile/profile-layered.go b/profile/profile-layered.go index bf965944..ce96c01f 100644 --- a/profile/profile-layered.go +++ b/profile/profile-layered.go @@ -35,19 +35,20 @@ type LayeredProfile struct { // via the API. If we ever switch away from JSON to something else supported // by DSD this WILL BREAK! - DisableAutoPermit config.BoolOption `json:"-"` - BlockScopeLocal config.BoolOption `json:"-"` - BlockScopeLAN config.BoolOption `json:"-"` - BlockScopeInternet config.BoolOption `json:"-"` - BlockP2P config.BoolOption `json:"-"` - BlockInbound config.BoolOption `json:"-"` - RemoveOutOfScopeDNS config.BoolOption `json:"-"` - RemoveBlockedDNS config.BoolOption `json:"-"` - FilterSubDomains config.BoolOption `json:"-"` - FilterCNAMEs config.BoolOption `json:"-"` - PreventBypassing config.BoolOption `json:"-"` - DomainHeuristics config.BoolOption `json:"-"` - UseSPN config.BoolOption `json:"-"` + DisableAutoPermit config.BoolOption `json:"-"` + BlockScopeLocal config.BoolOption `json:"-"` + BlockScopeLAN config.BoolOption `json:"-"` + BlockScopeInternet config.BoolOption `json:"-"` + BlockP2P config.BoolOption `json:"-"` + BlockInbound config.BoolOption `json:"-"` + RemoveOutOfScopeDNS config.BoolOption `json:"-"` + RemoveBlockedDNS config.BoolOption `json:"-"` + FilterSubDomains config.BoolOption `json:"-"` + FilterCNAMEs config.BoolOption `json:"-"` + PreventBypassing config.BoolOption `json:"-"` + DomainHeuristics config.BoolOption `json:"-"` + UseSPN config.BoolOption `json:"-"` + SPNRoutingAlgorithm config.StringOption `json:"-"` } // NewLayeredProfile returns a new layered profile based on the given local profile. @@ -115,6 +116,10 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile { CfgOptionUseSPNKey, cfgOptionUseSPN, ) + lp.SPNRoutingAlgorithm = lp.wrapStringOption( + CfgOptionRoutingAlgorithmKey, + cfgOptionRoutingAlgorithm, + ) lp.LayerIDs = append(lp.LayerIDs, localProfile.ScopedID()) lp.layers = append(lp.layers, localProfile) @@ -334,6 +339,39 @@ func (lp *LayeredProfile) MatchServiceEndpoint(ctx context.Context, entity *inte return cfgServiceEndpoints.Match(ctx, entity) } +// MatchSPNUsagePolicy checks if the given endpoint matches an entry in any of the profiles. This functions requires the layered profile to be read locked. +func (lp *LayeredProfile) MatchSPNUsagePolicy(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) { + for _, layer := range lp.layers { + if layer.spnUsagePolicy.IsSet() { + result, reason := layer.spnUsagePolicy.Match(ctx, entity) + if endpoints.IsDecision(result) { + return result, reason + } + } + } + + cfgLock.RLock() + defer cfgLock.RUnlock() + return cfgSPNUsagePolicy.Match(ctx, entity) +} + +// StackedExitHubPolicies returns all exit hub policies of the layered profile, including the global one. +func (lp *LayeredProfile) StackedExitHubPolicies() []endpoints.Endpoints { + policies := make([]endpoints.Endpoints, 0, len(lp.layers)+3) // +1 for global policy, +2 for intel policies + + for _, layer := range lp.layers { + if layer.spnExitHubPolicy.IsSet() { + policies = append(policies, layer.spnExitHubPolicy) + } + } + + cfgLock.RLock() + defer cfgLock.RUnlock() + policies = append(policies, cfgSPNExitHubPolicy) + + return policies +} + // MatchFilterLists matches the entity against the set of filter // lists. This functions requires the layered profile to be read locked. func (lp *LayeredProfile) MatchFilterLists(ctx context.Context, entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) { @@ -451,9 +489,6 @@ func (lp *LayeredProfile) GetProfileSource(configKey string) string { return "" } -/* -For later: - func (lp *LayeredProfile) wrapStringOption(configKey string, globalConfig config.StringOption) config.StringOption { var revCnt uint64 = 0 var value string @@ -485,7 +520,6 @@ func (lp *LayeredProfile) wrapStringOption(configKey string, globalConfig config return value } } -*/ func max(a, b uint8) uint8 { if a > b { diff --git a/profile/profile.go b/profile/profile.go index 1e126714..bf0d3017 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -128,6 +128,8 @@ type Profile struct { //nolint:maligned // not worth the effort serviceEndpoints endpoints.Endpoints filterListsSet bool filterListIDs []string + spnUsagePolicy endpoints.Endpoints + spnExitHubPolicy endpoints.Endpoints // Lifecycle Management outdated *abool.AtomicBool @@ -202,6 +204,24 @@ func (profile *Profile) parseConfig() error { } } + list, ok = profile.configPerspective.GetAsStringArray(CfgOptionSPNUsagePolicyKey) + profile.spnUsagePolicy = nil + if ok { + profile.spnUsagePolicy, err = endpoints.ParseEndpoints(list) + if err != nil { + lastErr = err + } + } + + list, ok = profile.configPerspective.GetAsStringArray(CfgOptionExitHubPolicyKey) + profile.spnExitHubPolicy = nil + if ok { + profile.spnExitHubPolicy, err = endpoints.ParseEndpoints(list) + if err != nil { + lastErr = err + } + } + return lastErr }