safing-portmaster/spn/navigator/options.go
Daniel Hååvi 80664d1a27
Restructure modules ()
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

330 lines
9.5 KiB
Go

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
}