safing-portmaster/spn/access/zones.go

257 lines
7.2 KiB
Go

package access
import (
"errors"
"fmt"
"os"
"strings"
"github.com/tevino/abool"
"github.com/safing/jess/lhash"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/spn/access/token"
"github.com/safing/portmaster/spn/conf"
"github.com/safing/portmaster/spn/terminal"
)
var (
// ExpandAndConnectZones are the zones that grant access to the expand and
// connect operations.
ExpandAndConnectZones = []string{"pblind1", "alpha2", "fallback1"}
zonePermissions = map[string]terminal.Permission{
"pblind1": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
"alpha2": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
"fallback1": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
}
persistentZones = ExpandAndConnectZones
enableTestMode = abool.New()
)
// EnableTestMode enables the test mode, leading the access module to only
// register a test zone.
// This should not be used to test the access module itself.
func EnableTestMode() {
enableTestMode.Set()
}
// InitializeZones initialized the permission zones.
// It initializes the test zones, if EnableTestMode was called before.
// Must only be called once.
func InitializeZones() error {
// Check if we are testing.
if enableTestMode.IsSet() {
return initializeTestZone()
}
// Special client zone config.
var requestSignalHandler func(token.Handler)
if conf.Client() {
requestSignalHandler = shouldRequestTokensHandler
}
// Register pblind1 as the first primary zone.
ph, err := token.NewPBlindHandler(token.PBlindOptions{
Zone: "pblind1",
CurveName: "P-256",
PublicKey: "eXoJXzXbM66UEsM2eVi9HwyBPLMfVnNrC7gNrsfMUJDs",
UseSerials: true,
BatchSize: 1000,
RandomizeOrder: true,
SignalShouldRequest: requestSignalHandler,
})
if err != nil {
return fmt.Errorf("failed to create pblind1 token handler: %w", err)
}
err = token.RegisterPBlindHandler(ph)
if err != nil {
return fmt.Errorf("failed to register pblind1 token handler: %w", err)
}
// Register fallback1 zone as fallback when the issuer is not available.
sh, err := token.NewScrambleHandler(token.ScrambleOptions{
Zone: "fallback1",
Algorithm: lhash.BLAKE2b_256,
InitialVerifiers: []string{"ZwkQoaAttVBMURzeLzNXokFBMAMUUwECfM1iHojcVKBmjk"},
Fallback: true,
})
if err != nil {
return fmt.Errorf("failed to create fallback1 token handler: %w", err)
}
err = token.RegisterScrambleHandler(sh)
if err != nil {
return fmt.Errorf("failed to register fallback1 token handler: %w", err)
}
// Register alpha2 zone for transition phase.
sh, err = token.NewScrambleHandler(token.ScrambleOptions{
Zone: "alpha2",
Algorithm: lhash.BLAKE2b_256,
InitialVerifiers: []string{"ZwojEvXZmAv7SZdNe7m94Xzu7F9J8vULqKf7QYtoTpN2tH"},
})
if err != nil {
return fmt.Errorf("failed to create alpha2 token handler: %w", err)
}
err = token.RegisterScrambleHandler(sh)
if err != nil {
return fmt.Errorf("failed to register alpha2 token handler: %w", err)
}
return nil
}
func initializeTestZone() error {
// Safeguard checks if we should really enable the test zone.
if !strings.HasSuffix(os.Args[0], ".test") {
return errors.New("tried to enable test mode, but no test binary was detected")
}
if token.RegistrySize() > 0 {
return fmt.Errorf("tried to enable test zone, but %d handlers are already registered", token.RegistrySize())
}
// Reset zones.
token.ResetRegistry()
// Set eligible zones.
ExpandAndConnectZones = []string{"unittest"}
zonePermissions = map[string]terminal.Permission{
"unittest": terminal.AddPermissions(terminal.MayExpand, terminal.MayConnect),
}
// Register unittest zone as for testing.
sh, err := token.NewScrambleHandler(token.ScrambleOptions{
Zone: "unittest",
Algorithm: lhash.BLAKE2b_256,
InitialTokens: []string{"6jFqLA93uSLL52utGKrvctG3ZfopSQ8WFqjsRK1c2Svt"},
InitialVerifiers: []string{"ZwoEoL59sr81s7WnF2vydGzjeejE3u8CqVafig1NTQzUr7"},
})
if err != nil {
return fmt.Errorf("failed to create unittest token handler: %w", err)
}
err = token.RegisterScrambleHandler(sh)
if err != nil {
return fmt.Errorf("failed to register unittest token handler: %w", err)
}
return nil
}
func shouldRequestTokensHandler(_ token.Handler) {
// accountUpdateTask is always set in client mode and when the module is online.
// Check if it's set in case this gets executed in other circumstances.
if accountUpdateTask == nil {
log.Warningf("spn/access: trying to trigger account update, but the task is not available")
return
}
accountUpdateTask.StartASAP()
}
// GetTokenAmount returns the amount of tokens for the given zones.
func GetTokenAmount(zones []string) (regular, fallback int) {
handlerLoop:
for _, zone := range zones {
// Get handler and check if it should be used.
handler, ok := token.GetHandler(zone)
if !ok {
log.Warningf("spn/access: use of non-registered zone %q", zone)
continue handlerLoop
}
if handler.IsFallback() {
fallback += handler.Amount()
} else {
regular += handler.Amount()
}
}
return
}
// ShouldRequest returns whether tokens should be requested for the given zones.
func ShouldRequest(zones []string) (shouldRequest bool) {
handlerLoop:
for _, zone := range zones {
// Get handler and check if it should be used.
handler, ok := token.GetHandler(zone)
if !ok {
log.Warningf("spn/access: use of non-registered zone %q", zone)
continue handlerLoop
}
// Go through all handlers every time as this will be the case anyway most
// of the time and will help us better catch zone misconfiguration.
if handler.ShouldRequest() {
shouldRequest = true
}
}
return shouldRequest
}
// GetToken returns a token of one of the given zones.
func GetToken(zones []string) (t *token.Token, err error) {
handlerSelection:
for _, zone := range zones {
// Get handler and check if it should be used.
handler, ok := token.GetHandler(zone)
switch {
case !ok:
log.Warningf("spn/access: use of non-registered zone %q", zone)
continue handlerSelection
case handler.IsFallback() && !TokenIssuerIsFailing():
// Skip fallback zone if everything works.
continue handlerSelection
}
// Get token from handler.
t, err = token.GetToken(zone)
if err == nil {
return t, nil
}
}
// Return existing error, if exists.
if err != nil {
return nil, err
}
return nil, token.ErrEmpty
}
// VerifyRawToken verifies a raw token.
func VerifyRawToken(data []byte) (granted terminal.Permission, err error) {
t, err := token.ParseRawToken(data)
if err != nil {
return 0, fmt.Errorf("failed to parse token: %w", err)
}
return VerifyToken(t)
}
// VerifyToken verifies a token.
func VerifyToken(t *token.Token) (granted terminal.Permission, err error) {
handler, ok := token.GetHandler(t.Zone)
if !ok {
return terminal.NoPermission, token.ErrZoneUnknown
}
// Check if the token is a fallback token.
if handler.IsFallback() && !healthCheck() {
return terminal.NoPermission, ErrFallbackNotAvailable
}
// Verify token.
err = handler.Verify(t)
if err != nil {
return 0, fmt.Errorf("failed to verify token: %w", err)
}
// Return permission of zone.
granted, ok = zonePermissions[t.Zone]
if !ok {
return terminal.NoPermission, nil
}
return granted, nil
}