wip: migrate to mono-repo. SPN has already been moved to spn/
This commit is contained in:
330
spn/navigator/options.go
Normal file
330
spn/navigator/options.go
Normal file
@@ -0,0 +1,330 @@
|
||||
package navigator
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/safing/portbase/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
|
||||
}
|
||||
Reference in New Issue
Block a user