package navigator import ( "context" "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/service/intel" "github.com/safing/portmaster/service/profile" "github.com/safing/portmaster/service/profile/endpoints" "github.com/safing/portmaster/spn/hub" ) // HubType is the usage type of a Hub in routing. type HubType uint8 // Hub Types. const ( HomeHub HubType = iota TransitHub DestinationHub ) // DeriveTunnelOptions derives and returns the tunnel options from the connection and profile. // This function lives in firewall/tunnel.go and is set here to avoid import loops. var DeriveTunnelOptions func(lp *profile.LayeredProfile, destination *intel.Entity, connEncrypted bool) *Options // Options holds configuration options for operations with the Map. type Options struct { //nolint:maligned // Home holds the options for Home Hubs. Home *HomeHubOptions // Transit holds the options for Transit Hubs. Transit *TransitHubOptions // Destination holds the options for Destination Hubs. Destination *DestinationHubOptions // RoutingProfile defines the algorithm to use to find a route. RoutingProfile string } // HomeHubOptions holds configuration options for Home Hub operations with the Map. type HomeHubOptions HubOptions // TransitHubOptions holds configuration options for Transit Hub operations with the Map. type TransitHubOptions HubOptions // DestinationHubOptions holds configuration options for Destination Hub operations with the Map. type DestinationHubOptions HubOptions // HubOptions holds configuration options for a specific hub type for operations with the Map. type HubOptions struct { // Regard holds required States. Only Hubs where all of these are present // will taken into account for the operation. If NoDefaults is not set, a // basic set of desirable states is added automatically. Regard PinState // Disregard holds disqualifying States. Only Hubs where none of these are // present will be taken into account for the operation. If NoDefaults is not // set, a basic set of undesirable states is added automatically. Disregard PinState // NoDefaults declares whether default and recommended Regard and Disregard states should not be used. NoDefaults bool // HubPolicies is a collection of endpoint lists that Hubs must pass in order // to be taken into account for the operation. HubPolicies []endpoints.Endpoints // RequireVerifiedOwners specifies which verified owners are allowed to be used. // If the list is empty, all owners are allowed. RequireVerifiedOwners []string // CheckHubPolicyWith provides an entity that must match the Hubs entry or exit // policy (depending on type) in order to be taken into account for the operation. CheckHubPolicyWith *intel.Entity } // Copy returns a shallow copy of the Options. func (o *Options) Copy() *Options { copied := &Options{ RoutingProfile: o.RoutingProfile, } if o.Home != nil { c := HomeHubOptions(HubOptions(*o.Home).Copy()) copied.Home = &c } if o.Transit != nil { c := TransitHubOptions(HubOptions(*o.Transit).Copy()) copied.Transit = &c } if o.Destination != nil { c := DestinationHubOptions(HubOptions(*o.Destination).Copy()) copied.Destination = &c } return copied } // Copy returns a shallow copy of the Options. func (o HubOptions) Copy() HubOptions { return HubOptions{ Regard: o.Regard, Disregard: o.Disregard, NoDefaults: o.NoDefaults, HubPolicies: o.HubPolicies, RequireVerifiedOwners: o.RequireVerifiedOwners, CheckHubPolicyWith: o.CheckHubPolicyWith, } } // PinMatcher is a stateful matching function generated by Options. type PinMatcher func(pin *Pin) bool // DefaultOptions returns the default options for this Map. func (m *Map) DefaultOptions() *Options { m.Lock() defer m.Unlock() return m.defaultOptions() } func (m *Map) defaultOptions() *Options { opts := &Options{ RoutingProfile: DefaultRoutingProfileID, } return opts } // HubPoliciesAreSet returns whether any of the given hub policies are set and non-empty. func HubPoliciesAreSet(policies []endpoints.Endpoints) bool { for _, policy := range policies { if policy.IsSet() { return true } } return false } var emptyHubOptions = &HubOptions{} // Matcher generates a PinMatcher based on the Options. func (o *HomeHubOptions) Matcher(hubIntel *hub.Intel) PinMatcher { if o == nil { return emptyHubOptions.Matcher(HomeHub, hubIntel) } // Convert and call base func. ho := HubOptions(*o) return ho.Matcher(HomeHub, hubIntel) } // Matcher generates a PinMatcher based on the Options. func (o *TransitHubOptions) Matcher(hubIntel *hub.Intel) PinMatcher { if o == nil { return emptyHubOptions.Matcher(TransitHub, hubIntel) } // Convert and call base func. ho := HubOptions(*o) return ho.Matcher(TransitHub, hubIntel) } // Matcher generates a PinMatcher based on the Options. func (o *DestinationHubOptions) Matcher(hubIntel *hub.Intel) PinMatcher { if o == nil { return emptyHubOptions.Matcher(DestinationHub, hubIntel) } // Convert and call base func. ho := HubOptions(*o) return ho.Matcher(DestinationHub, hubIntel) } // Matcher generates a PinMatcher based on the Options. // Always use the Matcher on option structs if you can. func (o *Options) Matcher(hubType HubType, hubIntel *hub.Intel) PinMatcher { switch hubType { case HomeHub: return o.Home.Matcher(hubIntel) case TransitHub: return o.Transit.Matcher(hubIntel) case DestinationHub: return o.Destination.Matcher(hubIntel) default: return nil // This will panic, but should never be used. } } // Matcher generates a PinMatcher based on the Options. func (o *HubOptions) Matcher(hubType HubType, hubIntel *hub.Intel) PinMatcher { // Fallback to empty hub options. if o == nil { o = emptyHubOptions } // Compile states to regard and disregard. regard := o.Regard disregard := o.Disregard // Add default states. if !o.NoDefaults { // Add default States. regard = regard.Add(StateSummaryRegard) disregard = disregard.Add(StateSummaryDisregard) // Add type based Advisories. switch hubType { case HomeHub: // Home Hubs don't need to be reachable and don't need keys ready to be used. regard = regard.Remove(StateReachable) regard = regard.Remove(StateActive) // Follow advisory. disregard = disregard.Add(StateUsageAsHomeDiscouraged) // Home Hub may be the current Home Hub. disregard = disregard.Remove(StateIsHomeHub) case TransitHub: // Transit Hubs get no additional states. case DestinationHub: // Follow advisory. disregard = disregard.Add(StateUsageAsDestinationDiscouraged) // Do not use if Hub reports network issues. disregard = disregard.Add(StateConnectivityIssues) } } // Add intel policies. hubPolicies := o.HubPolicies if hubIntel != nil && hubIntel.Parsed() != nil { switch hubType { case HomeHub: hubPolicies = append(hubPolicies, hubIntel.Parsed().HubAdvisory, hubIntel.Parsed().HomeHubAdvisory) case TransitHub: hubPolicies = append(hubPolicies, hubIntel.Parsed().HubAdvisory) case DestinationHub: hubPolicies = append(hubPolicies, hubIntel.Parsed().HubAdvisory, hubIntel.Parsed().DestinationHubAdvisory) } } // Add entry/exit policiy checks. checkHubPolicyWith := o.CheckHubPolicyWith return func(pin *Pin) bool { // Check required Pin States. if !pin.State.Has(regard) || pin.State.HasAnyOf(disregard) { return false } // Check verified owners. if len(o.RequireVerifiedOwners) > 0 { // Check if Pin has a verified owner at all. if pin.VerifiedOwner == "" { return false } // Check if verified owner is in the list. inList := false for _, allowed := range o.RequireVerifiedOwners { if pin.VerifiedOwner == allowed { inList = true break } } // Pin does not have a verified owner from the allowed list. if !inList { return false } } // Check policies. policyCheck: for _, policy := range hubPolicies { // Check if policy is set. if !policy.IsSet() { continue } // Check if policy matches. result, reason := policy.MatchMulti(context.TODO(), pin.EntityV4, pin.EntityV6) switch result { case endpoints.NoMatch: // Continue with check. case endpoints.MatchError: log.Warningf("spn/navigator: failed to match policy: %s", reason) // Continue with check for now. // TODO: Rethink how to do this. If eg. the geoip database has a // problem, then no Hub will match. For now, just continue to the // next rule set. Not optimal, but fail safe. case endpoints.Denied: // Explicitly denied, abort immediately. return false case endpoints.Permitted: // Explicitly allowed, abort check and continue. break policyCheck } } // Check entry/exit policies. if checkHubPolicyWith != nil { switch hubType { case HomeHub: if endpointListMatch(pin.Hub.Info.EntryPolicy(), checkHubPolicyWith) == endpoints.Denied { // Hub does not allow entry from the given entity. return false } case TransitHub: // Transit Hubs do not have a hub policy. case DestinationHub: if endpointListMatch(pin.Hub.Info.ExitPolicy(), checkHubPolicyWith) == endpoints.Denied { // Hub does not allow exit to the given entity. return false } } } return true // All checks have passed. } } func endpointListMatch(list endpoints.Endpoints, entity *intel.Entity) endpoints.EPResult { // Check if endpoint list and entity are available. if !list.IsSet() || entity == nil { return endpoints.NoMatch } // Match and return result only. result, _ := list.Match(context.TODO(), entity) return result }