Skip to content
This repository has been archived by the owner on Mar 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #56 from safing/feature/spn-settings
Browse files Browse the repository at this point in the history
Add SPN Settings
  • Loading branch information
dhaavi authored Mar 2, 2022
2 parents 999ff06 + e8d72d4 commit 0b03b06
Show file tree
Hide file tree
Showing 19 changed files with 444 additions and 155 deletions.
10 changes: 4 additions & 6 deletions cabin/config-public.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,8 @@ func prepPublicHubConfig() error {
ExpertiseLevel: config.ExpertiseLevelExpert,
DefaultValue: publicCfgOptionEntryDefault,
Annotations: config.Annotations{
config.DisplayOrderAnnotation: publicCfgOptionEntryOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
endpoints.EndpointListAnnotation: []string{endpoints.EndpointListIP},
config.DisplayOrderAnnotation: publicCfgOptionEntryOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
},
})
if err != nil {
Expand All @@ -258,9 +257,8 @@ func prepPublicHubConfig() error {
ExpertiseLevel: config.ExpertiseLevelExpert,
DefaultValue: publicCfgOptionExitDefault,
Annotations: config.Annotations{
config.DisplayOrderAnnotation: publicCfgOptionExitOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
endpoints.EndpointListAnnotation: []string{endpoints.EndpointListIP},
config.DisplayOrderAnnotation: publicCfgOptionExitOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
},
})
if err != nil {
Expand Down
137 changes: 134 additions & 3 deletions captain/config.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,96 @@
package captain

import "github.com/safing/portbase/config"
import (
"sync"

"github.com/safing/portbase/config"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/endpoints"
)

var (
// CfgOptionEnableSPNKey is the configuration key for the SPN module.
CfgOptionEnableSPNKey = "spn/enable"
cfgOptionEnableSPNOrder = 128

// CfgOptionHomeHubPolicyKey is the configuration key for the SPN home policy.
CfgOptionHomeHubPolicyKey = "spn/homePolicy"
cfgOptionHomeHubPolicy config.StringArrayOption
cfgOptionHomeHubPolicyOrder = 145

// CfgOptionDNSExitHubPolicyKey is the configuration key for the SPN DNS exit policy.
CfgOptionDNSExitHubPolicyKey = "spn/dnsExitPolicy"
cfgOptionDNSExitHubPolicy config.StringArrayOption
cfgOptionDNSExitHubPolicyOrder = 147

// Special Access Code.
cfgOptionSpecialAccessCodeKey = "spn/specialAccessCode"
cfgOptionSpecialAccessCodeDefault = "none"
cfgOptionSpecialAccessCode config.StringOption //nolint:unused // Linter, you drunk?
cfgOptionSpecialAccessCodeOrder = 144
cfgOptionSpecialAccessCodeOrder = 160
)

func prepConfig() error {
// Home Node Rules
err := config.Register(&config.Option{
Name: "Home Node Rules",
Key: CfgOptionHomeHubPolicyKey,
Description: `Customize which countries should or should not be used for your Home Node. The Home Node is your entry into the SPN. You connect directly to it and all your connections are routed through it.
By default, the Portmaster tries to choose the nearest node as your Home Node in order to reduce your exposure to the open Internet.
Reconnect to the SPN in order to apply new rules.`,
Help: profile.SPNRulesHelp,
OptType: config.OptTypeStringArray,
ExpertiseLevel: config.ExpertiseLevelExpert,
DefaultValue: []string{},
Annotations: config.Annotations{
config.StackableAnnotation: true,
config.CategoryAnnotation: "Routing",
config.DisplayOrderAnnotation: cfgOptionHomeHubPolicyOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
config.QuickSettingsAnnotation: profile.SPNRulesQuickSettings,
endpoints.EndpointListVerdictNamesAnnotation: profile.SPNRulesVerdictNames,
},
ValidationRegex: endpoints.ListEntryValidationRegex,
ValidationFunc: endpoints.ValidateEndpointListConfigOption,
})
if err != nil {
return err
}
cfgOptionHomeHubPolicy = config.Concurrent.GetAsStringArray(CfgOptionHomeHubPolicyKey, []string{})

// DNS Exit Node Rules
err = config.Register(&config.Option{
Name: "DNS Exit Node Rules",
Key: CfgOptionDNSExitHubPolicyKey,
Description: `Customize which countries should or should not be used as DNS Exit Nodes.
By default, the Portmaster will exit DNS requests directly at your Home Node in order to keep them fast and close to your location. This is important, as DNS resolution often takes your approximate location into account when deciding which optimized DNS records are returned to you. As the Portmaster encrypts your DNS requests by default, you effectively gain a two-hop security level for your DNS requests in order to protect your privacy.
This setting mainly exists for when you need to simulate your presence in another location on a lower level too. This might be necessary to defeat more intelligent geo-blocking systems.`,
Help: profile.SPNRulesHelp,
OptType: config.OptTypeStringArray,
RequiresRestart: true,
ExpertiseLevel: config.ExpertiseLevelExpert,
DefaultValue: []string{},
Annotations: config.Annotations{
config.StackableAnnotation: true,
config.CategoryAnnotation: "Routing",
config.DisplayOrderAnnotation: cfgOptionDNSExitHubPolicyOrder,
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
config.QuickSettingsAnnotation: profile.SPNRulesQuickSettings,
endpoints.EndpointListVerdictNamesAnnotation: profile.SPNRulesVerdictNames,
},
ValidationRegex: endpoints.ListEntryValidationRegex,
ValidationFunc: endpoints.ValidateEndpointListConfigOption,
})
if err != nil {
return err
}
cfgOptionDNSExitHubPolicy = config.Concurrent.GetAsStringArray(CfgOptionDNSExitHubPolicyKey, []string{})

err = config.Register(&config.Option{
Name: "Special Access Code",
Key: cfgOptionSpecialAccessCodeKey,
Description: "Special Access Codes grant access to the SPN for testing or evaluation purposes.",
Expand All @@ -29,8 +104,64 @@ func prepConfig() error {
if err != nil {
return err
}

cfgOptionSpecialAccessCode = config.Concurrent.GetAsString(cfgOptionSpecialAccessCodeKey, "")

return nil
}

var (
homeHubPolicy endpoints.Endpoints
homeHubPolicyLock sync.Mutex
homeHubPolicyConfigFlag = config.NewValidityFlag()
)

func getHomeHubPolicy() (endpoints.Endpoints, error) {
homeHubPolicyLock.Lock()
defer homeHubPolicyLock.Unlock()

// Return cached value if config is still valid.
if homeHubPolicyConfigFlag.IsValid() {
return homeHubPolicy, nil
}
homeHubPolicyConfigFlag.Refresh()

// Parse new policy.
policy, err := endpoints.ParseEndpoints(cfgOptionHomeHubPolicy())
if err != nil {
homeHubPolicy = nil
return nil, err
}

// Save and return the new policy.
homeHubPolicy = policy
return homeHubPolicy, nil
}

var (
dnsExitHubPolicy endpoints.Endpoints
dnsExitHubPolicyLock sync.Mutex
dnsExitHubPolicyConfigFlag = config.NewValidityFlag()
)

// GetDNSExitHubPolicy return the current DNS exit policy.
func GetDNSExitHubPolicy() (endpoints.Endpoints, error) {
dnsExitHubPolicyLock.Lock()
defer dnsExitHubPolicyLock.Unlock()

// Return cached value if config is still valid.
if dnsExitHubPolicyConfigFlag.IsValid() {
return dnsExitHubPolicy, nil
}
dnsExitHubPolicyConfigFlag.Refresh()

// Parse new policy.
policy, err := endpoints.ParseEndpoints(cfgOptionDNSExitHubPolicy())
if err != nil {
dnsExitHubPolicy = nil
return nil, err
}

// Save and return the new policy.
dnsExitHubPolicy = policy
return dnsExitHubPolicy, nil
}
66 changes: 59 additions & 7 deletions captain/navigation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portbase/notifications"
"github.com/safing/portmaster/intel"
"github.com/safing/portmaster/netenv"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/profile/endpoints"
"github.com/safing/spn/access"
"github.com/safing/spn/docks"
"github.com/safing/spn/hub"
Expand All @@ -20,6 +22,9 @@ import (

const stopCraneAfterBeingUnsuggestedFor = 6 * time.Hour

// ErrAllHomeHubsExcluded is returned when all available home hubs were excluded.
var ErrAllHomeHubsExcluded = errors.New("all home hubs are excluded")

func homeHubManager(ctx context.Context) (err error) {
defer ready.UnSet()
defer netenv.ConnectedToSPN.UnSet()
Expand Down Expand Up @@ -49,12 +54,28 @@ managing:
err = establishHomeHub(ctx)
if err != nil {
log.Warningf("failed to establish connection to home hub: %s", err)
notifications.NotifyWarn(
"spn:home-hub-failure",
"SPN Failed to Connect",
fmt.Sprintf("Failed to connect to a home hub: %s. The Portmaster will retry to connect automatically.", err),
spnSettingsButton,
).AttachToModule(module)
switch {
case errors.Is(err, ErrAllHomeHubsExcluded):
notifications.NotifyError(
"spn:all-home-hubs-excluded",
"All Home Nodes Excluded",
"Your current Home Node Rules exclude all available SPN Nodes. Please change your rules to allow for at least one available Home Node.",
notifications.Action{
Text: "Configure",
Type: notifications.ActionTypeOpenSetting,
Payload: &notifications.ActionTypeOpenSettingPayload{
Key: CfgOptionHomeHubPolicyKey,
},
},
).AttachToModule(module)
default:
notifications.NotifyWarn(
"spn:home-hub-failure",
"SPN Failed to Connect",
fmt.Sprintf("Failed to connect to a home hub: %s. The Portmaster will retry to connect automatically.", err),
spnSettingsButton,
).AttachToModule(module)
}
resetSPNStatus(StatusFailed)
select {
case <-ctx.Done():
Expand Down Expand Up @@ -125,12 +146,38 @@ func establishHomeHub(ctx context.Context) error {
locations.BestV6(),
)

// Get own entity.
// Checking the entity against the entry policies is somewhat hit and miss
// anyway, as the device location is an approximation.
var myEntity *intel.Entity
if dl := locations.BestV4(); dl != nil && dl.IP != nil {
myEntity = &intel.Entity{}
myEntity.SetIP(dl.IP)
myEntity.FetchData(ctx)
} else if dl := locations.BestV6(); dl != nil && dl.IP != nil {
myEntity = &intel.Entity{}
myEntity.SetIP(dl.IP)
myEntity.FetchData(ctx)
}

// Get home hub policy for selecting the home hub.
homePolicy, err := getHomeHubPolicy()
if err != nil {
return err
}

// Build navigation options for searching for a home hub.
opts := &navigator.Options{
HubPolicies: []endpoints.Endpoints{homePolicy},
CheckHubEntryPolicyWith: myEntity,
}

// Find nearby hubs.
findCandidates:
candidates, err := navigator.Main.FindNearestHubs(
locations.BestV4().LocationOrNil(),
locations.BestV6().LocationOrNil(),
nil, navigator.HomeHub, 10,
opts, navigator.HomeHub, 10,
)
if err != nil {
if errors.Is(err, navigator.ErrEmptyMap) {
Expand All @@ -145,6 +192,11 @@ findCandidates:
return fmt.Errorf("failed to find nearby hubs: %w", err)
}

// Check if any candidates were returned.
if len(candidates) == 0 && len(homePolicy) > 0 {
return ErrAllHomeHubsExcluded
}

// Try connecting to a hub.
var tries int
var candidate *hub.Hub
Expand Down
33 changes: 27 additions & 6 deletions captain/piers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ package captain

import (
"context"
"fmt"
"sync"

"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
"github.com/safing/portmaster/intel"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/profile/endpoints"
"github.com/safing/spn/docks"
"github.com/safing/spn/hub"
"github.com/safing/spn/ships"
Expand Down Expand Up @@ -82,10 +86,10 @@ func dockingRequestHandler(ctx context.Context) error {
// TODO: Do actual pier management.
log.Errorf("spn/captain: pier %s failed: %s", r.Pier.Transport(), r.Err)
case r.Ship != nil:
if checkDockingPermission(r.Ship) {
handleDockingRequest(r.Ship)
if err := checkDockingPermission(ctx, r.Ship); err != nil {
log.Warningf("spn/captain: denied ship from %s to dock at pier %s: %s", r.Ship.RemoteAddr(), r.Pier.Transport(), err)
} else {
log.Warningf("spn/captain: denied ship from %s to dock at pier %s", r.Ship.RemoteAddr(), r.Pier.Transport())
handleDockingRequest(r.Ship)
}
default:
log.Warningf("spn/captain: received invalid docking request without ship for pier %s", r.Pier.Transport())
Expand All @@ -94,9 +98,26 @@ func dockingRequestHandler(ctx context.Context) error {
}
}

func checkDockingPermission(ship ships.Ship) (ok bool) {
// TODO: check docking policies (hub entry policy)
return true
func checkDockingPermission(ctx context.Context, ship ships.Ship) error {
remoteIP, err := netutils.IPFromAddr(ship.RemoteAddr())
if err != nil {
return fmt.Errorf("failed to parse remote IP: %w", err)
}

// Create entity.
entity := &intel.Entity{}
entity.SetIP(remoteIP)
entity.FetchData(ctx)

// TODO: Do we want to handle protocol and port too?

// Check against policy.
result, reason := publicIdentity.Hub.GetInfo().EntryPolicy().Match(ctx, entity)
if result == endpoints.Denied {
return fmt.Errorf("entry policy violated: %s", reason)
}

return nil
}

func handleDockingRequest(ship ships.Ship) {
Expand Down
Loading

0 comments on commit 0b03b06

Please sign in to comment.