Merge pull request #1326 from safing/feature/import-export-apis
Add first set of import/export APIs
This commit is contained in:
@@ -26,7 +26,7 @@ func scan(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Export latest versions.
|
||||
data, err := json.MarshalIndent(exportSelected(false), "", " ")
|
||||
data, err := json.MarshalIndent(exportSelected(true), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
13
core/core.go
13
core/core.go
@@ -13,6 +13,7 @@ import (
|
||||
_ "github.com/safing/portmaster/netenv"
|
||||
_ "github.com/safing/portmaster/netquery"
|
||||
_ "github.com/safing/portmaster/status"
|
||||
_ "github.com/safing/portmaster/sync"
|
||||
_ "github.com/safing/portmaster/ui"
|
||||
"github.com/safing/portmaster/updates"
|
||||
)
|
||||
@@ -29,7 +30,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("core", prep, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui", "netenv", "network", "netquery", "interception", "compat", "broadcasts")
|
||||
module = modules.Register("core", prep, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui", "netenv", "network", "netquery", "interception", "compat", "broadcasts", "sync")
|
||||
subsystems.Register(
|
||||
"core",
|
||||
"Core",
|
||||
@@ -62,11 +63,6 @@ func prep() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Enable persistent metrics.
|
||||
if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil {
|
||||
log.Warningf("core: failed to enable persisted metrics: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -75,6 +71,11 @@ func start() error {
|
||||
return fmt.Errorf("failed to start plattform-specific components: %w", err)
|
||||
}
|
||||
|
||||
// Enable persistent metrics.
|
||||
if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil {
|
||||
log.Warningf("core: failed to enable persisted metrics: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,18 @@ package firewall
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
_ "github.com/safing/portmaster/core"
|
||||
"github.com/safing/portmaster/network"
|
||||
"github.com/safing/portmaster/profile"
|
||||
"github.com/safing/spn/access"
|
||||
"github.com/safing/spn/captain"
|
||||
)
|
||||
|
||||
var module *modules.Module
|
||||
@@ -25,12 +30,6 @@ func init() {
|
||||
)
|
||||
}
|
||||
|
||||
const (
|
||||
configChangeEvent = "config change"
|
||||
profileConfigChangeEvent = "profile config change"
|
||||
onSPNConnectEvent = "spn connect"
|
||||
)
|
||||
|
||||
func prep() error {
|
||||
network.SetDefaultFirewallHandler(verdictHandler)
|
||||
|
||||
@@ -38,8 +37,8 @@ func prep() error {
|
||||
// this will be triggered on spn enable/disable
|
||||
err := module.RegisterEventHook(
|
||||
"config",
|
||||
configChangeEvent,
|
||||
"reset connection verdicts",
|
||||
config.ChangeEvent,
|
||||
"reset connection verdicts after global config change",
|
||||
func(ctx context.Context, _ interface{}) error {
|
||||
resetAllConnectionVerdicts()
|
||||
return nil
|
||||
@@ -52,10 +51,20 @@ func prep() error {
|
||||
// Reset connections every time profile changes
|
||||
err = module.RegisterEventHook(
|
||||
"profiles",
|
||||
profileConfigChangeEvent,
|
||||
"reset connection verdicts",
|
||||
func(ctx context.Context, _ interface{}) error {
|
||||
resetAllConnectionVerdicts()
|
||||
profile.ConfigChangeEvent,
|
||||
"reset connection verdicts after profile config change",
|
||||
func(ctx context.Context, eventData interface{}) error {
|
||||
// Expected event data: scoped profile ID.
|
||||
profileID, ok := eventData.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("event data is not a string: %v", eventData)
|
||||
}
|
||||
profileSource, profileID, ok := strings.Cut(profileID, "/")
|
||||
if !ok {
|
||||
return fmt.Errorf("event data does not seem to be a scoped profile ID: %v", eventData)
|
||||
}
|
||||
|
||||
resetProfileConnectionVerdict(profileSource, profileID)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
@@ -67,8 +76,8 @@ func prep() error {
|
||||
// connect and disconnecting is triggered on config change event but connecting takеs more time
|
||||
err = module.RegisterEventHook(
|
||||
"captain",
|
||||
onSPNConnectEvent,
|
||||
"reset connection verdicts",
|
||||
captain.SPNConnectedEvent,
|
||||
"reset connection verdicts on SPN connect",
|
||||
func(ctx context.Context, _ interface{}) error {
|
||||
resetAllConnectionVerdicts()
|
||||
return nil
|
||||
@@ -83,7 +92,7 @@ func prep() error {
|
||||
err = module.RegisterEventHook(
|
||||
"access",
|
||||
access.AccountUpdateEvent,
|
||||
"update connection feature flags",
|
||||
"update connection feature flags after account update",
|
||||
func(ctx context.Context, _ interface{}) error {
|
||||
resetAllConnectionVerdicts()
|
||||
return nil
|
||||
@@ -93,6 +102,24 @@ func prep() error {
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
err = module.RegisterEventHook(
|
||||
"network",
|
||||
network.ConnectionReattributedEvent,
|
||||
"reset verdict of re-attributed connection",
|
||||
func(ctx context.Context, eventData interface{}) error {
|
||||
// Expected event data: connection ID.
|
||||
connID, ok := eventData.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("event data is not a string: %v", eventData)
|
||||
}
|
||||
resetSingleConnectionVerdict(connID)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("filter: failed to register event hook: %s", err)
|
||||
}
|
||||
|
||||
if err := registerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,13 +43,55 @@ var (
|
||||
ownPID = os.Getpid()
|
||||
)
|
||||
|
||||
func resetAllConnectionVerdicts() {
|
||||
// Resetting will force all the connection to be evaluated by the firewall again
|
||||
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
||||
log.Info("interception: re-evaluating all connections")
|
||||
|
||||
func resetSingleConnectionVerdict(connID string) {
|
||||
// Create tracing context.
|
||||
ctx, tracer := log.AddTracer(context.Background())
|
||||
defer tracer.Submit()
|
||||
|
||||
conn, ok := network.GetConnection(connID)
|
||||
if !ok {
|
||||
conn, ok = network.GetDNSConnection(connID)
|
||||
if !ok {
|
||||
tracer.Debugf("filter: could not find re-attributed connection %s for re-evaluation", connID)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resetConnectionVerdict(ctx, conn)
|
||||
}
|
||||
|
||||
func resetProfileConnectionVerdict(profileSource, profileID string) {
|
||||
// Create tracing context.
|
||||
ctx, tracer := log.AddTracer(context.Background())
|
||||
defer tracer.Submit()
|
||||
|
||||
// Resetting will force all the connection to be evaluated by the firewall again
|
||||
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
||||
tracer.Infof("filter: re-evaluating connections of %s/%s", profileSource, profileID)
|
||||
|
||||
// Re-evaluate all connections.
|
||||
var changedVerdicts int
|
||||
for _, conn := range network.GetAllConnections() {
|
||||
// Check if connection is complete and attributed to the deleted profile.
|
||||
if conn.DataIsComplete() &&
|
||||
conn.ProcessContext.Profile == profileID &&
|
||||
conn.ProcessContext.Source == profileSource {
|
||||
if resetConnectionVerdict(ctx, conn) {
|
||||
changedVerdicts++
|
||||
}
|
||||
}
|
||||
}
|
||||
tracer.Infof("filter: changed verdict on %d connections", changedVerdicts)
|
||||
}
|
||||
|
||||
func resetAllConnectionVerdicts() {
|
||||
// Create tracing context.
|
||||
ctx, tracer := log.AddTracer(context.Background())
|
||||
defer tracer.Submit()
|
||||
|
||||
// Resetting will force all the connection to be evaluated by the firewall again
|
||||
// this will set new verdicts if configuration was update or spn has been disabled or enabled.
|
||||
tracer.Info("filter: re-evaluating all connections")
|
||||
|
||||
// Re-evaluate all connections.
|
||||
var changedVerdicts int
|
||||
@@ -59,54 +101,60 @@ func resetAllConnectionVerdicts() {
|
||||
continue
|
||||
}
|
||||
|
||||
func() {
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
// Update feature flags.
|
||||
if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
tracer.Warningf("network: failed to update connection feature flags: %s", err)
|
||||
}
|
||||
|
||||
// Skip internal connections:
|
||||
// - Pre-authenticated connections from Portmaster
|
||||
// - Redirected DNS requests
|
||||
// - SPN Uplink to Home Hub
|
||||
if conn.Internal {
|
||||
tracer.Tracef("filter: skipping internal connection %s", conn)
|
||||
return
|
||||
}
|
||||
|
||||
tracer.Debugf("filter: re-evaluating verdict of %s", conn)
|
||||
previousVerdict := conn.Verdict.Firewall
|
||||
|
||||
// Apply privacy filter and check tunneling.
|
||||
FilterConnection(ctx, conn, nil, true, true)
|
||||
|
||||
// Stop existing SPN tunnel if not needed anymore.
|
||||
if conn.Verdict.Active != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
|
||||
err := conn.TunnelContext.StopTunnel()
|
||||
if err != nil {
|
||||
tracer.Debugf("filter: failed to stopped unneeded tunnel: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Save if verdict changed.
|
||||
if conn.Verdict.Firewall != previousVerdict {
|
||||
err := interception.UpdateVerdictOfConnection(conn)
|
||||
if err != nil {
|
||||
log.Debugf("filter: failed to update connection verdict: %s", err)
|
||||
}
|
||||
conn.Save()
|
||||
tracer.Infof("filter: verdict of connection %s changed from %s to %s", conn, previousVerdict.Verb(), conn.VerdictVerb())
|
||||
changedVerdicts++
|
||||
} else {
|
||||
tracer.Tracef("filter: verdict to connection %s unchanged at %s", conn, conn.VerdictVerb())
|
||||
}
|
||||
}()
|
||||
if resetConnectionVerdict(ctx, conn) {
|
||||
changedVerdicts++
|
||||
}
|
||||
}
|
||||
tracer.Infof("filter: changed verdict on %d connections", changedVerdicts)
|
||||
tracer.Submit()
|
||||
}
|
||||
|
||||
func resetConnectionVerdict(ctx context.Context, conn *network.Connection) (verdictChanged bool) {
|
||||
tracer := log.Tracer(ctx)
|
||||
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
// Update feature flags.
|
||||
if err := conn.UpdateFeatures(); err != nil && !errors.Is(err, access.ErrNotLoggedIn) {
|
||||
tracer.Warningf("filter: failed to update connection feature flags: %s", err)
|
||||
}
|
||||
|
||||
// Skip internal connections:
|
||||
// - Pre-authenticated connections from Portmaster
|
||||
// - Redirected DNS requests
|
||||
// - SPN Uplink to Home Hub
|
||||
if conn.Internal {
|
||||
// tracer.Tracef("filter: skipping internal connection %s", conn)
|
||||
return false
|
||||
}
|
||||
|
||||
tracer.Debugf("filter: re-evaluating verdict of %s", conn)
|
||||
previousVerdict := conn.Verdict.Firewall
|
||||
|
||||
// Apply privacy filter and check tunneling.
|
||||
FilterConnection(ctx, conn, nil, true, true)
|
||||
|
||||
// Stop existing SPN tunnel if not needed anymore.
|
||||
if conn.Verdict.Active != network.VerdictRerouteToTunnel && conn.TunnelContext != nil {
|
||||
err := conn.TunnelContext.StopTunnel()
|
||||
if err != nil {
|
||||
tracer.Debugf("filter: failed to stopped unneeded tunnel: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Save if verdict changed.
|
||||
if conn.Verdict.Firewall != previousVerdict {
|
||||
err := interception.UpdateVerdictOfConnection(conn)
|
||||
if err != nil {
|
||||
log.Debugf("filter: failed to update connection verdict: %s", err)
|
||||
}
|
||||
conn.Save()
|
||||
tracer.Infof("filter: verdict of connection %s changed from %s to %s", conn, previousVerdict.Verb(), conn.VerdictVerb())
|
||||
return true
|
||||
}
|
||||
|
||||
tracer.Tracef("filter: verdict to connection %s unchanged at %s", conn, conn.VerdictVerb())
|
||||
return false
|
||||
}
|
||||
|
||||
// SetNameserverIPMatcher sets a function that is used to match the internal
|
||||
|
||||
12
go.mod
12
go.mod
@@ -1,13 +1,13 @@
|
||||
module github.com/safing/portmaster
|
||||
|
||||
go 1.21
|
||||
go 1.21.1
|
||||
|
||||
toolchain go1.21.2
|
||||
|
||||
require (
|
||||
github.com/Xuanwo/go-locale v1.1.0
|
||||
github.com/agext/levenshtein v1.2.3
|
||||
github.com/cilium/ebpf v0.12.0
|
||||
github.com/cilium/ebpf v0.12.2
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/florianl/go-conntrack v0.4.0
|
||||
github.com/florianl/go-nfqueue v1.3.1
|
||||
@@ -21,7 +21,7 @@ require (
|
||||
github.com/miekg/dns v1.1.56
|
||||
github.com/mitchellh/go-server-timing v1.0.1
|
||||
github.com/oschwald/maxminddb-golang v1.12.0
|
||||
github.com/safing/jess v0.3.1
|
||||
github.com/safing/jess v0.3.2
|
||||
github.com/safing/portbase v0.18.5
|
||||
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec
|
||||
github.com/safing/spn v0.7.4
|
||||
@@ -73,7 +73,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857 // indirect
|
||||
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1 // indirect
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
github.com/seehuhn/fortuna v1.0.1 // indirect
|
||||
github.com/seehuhn/sha256d v1.0.0 // indirect
|
||||
@@ -99,8 +99,8 @@ require (
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231017021626-e69b01c3a8f3 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20231025034759-d4973670c3c9 // indirect
|
||||
modernc.org/libc v1.28.0 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.7.2 // indirect
|
||||
modernc.org/sqlite v1.26.0 // indirect
|
||||
|
||||
122
go.sum
122
go.sum
@@ -1,12 +1,8 @@
|
||||
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/VictoriaMetrics/metrics v1.22.2/go.mod h1:rAr/llLpEnAdTehiNlUxKgnjcOuROSzpw0GvjpEbvFc=
|
||||
github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw=
|
||||
github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys=
|
||||
github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg=
|
||||
@@ -17,10 +13,8 @@ github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpY
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
|
||||
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E=
|
||||
@@ -30,26 +24,15 @@ github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0
|
||||
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKOW72Y5+4WNxUIkjM8=
|
||||
github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/cilium/ebpf v0.12.0 h1:oQEuIQIXgYhe1v7sYUG0P9vtJTYZLLdA6tiQmrOB1mo=
|
||||
github.com/cilium/ebpf v0.12.0/go.mod h1:u9H29/Iq+8cy70YqI6p5pfADkFl3vdnV2qXDg5JL0Zo=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/cilium/ebpf v0.12.2 h1:cP3qL4kkl19kr/F+hKqUo9F9pPMVz1oms8C7Qj0AwWk=
|
||||
github.com/cilium/ebpf v0.12.2/go.mod h1:u9H29/Iq+8cy70YqI6p5pfADkFl3vdnV2qXDg5JL0Zo=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -57,12 +40,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
|
||||
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
|
||||
@@ -76,10 +55,8 @@ github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM
|
||||
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
|
||||
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fxamacker/cbor v1.5.1 h1:XjQWBgdmQyqimslUh5r4tUGmoqzHmBFQOImkWGi2awg=
|
||||
github.com/fxamacker/cbor v1.5.1/go.mod h1:3aPGItF174ni7dDzd6JZ206H8cmr4GDNBGpPa971zsU=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
@@ -89,24 +66,17 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
|
||||
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
@@ -146,11 +116,7 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
|
||||
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
@@ -170,7 +136,6 @@ github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmK
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -180,13 +145,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
@@ -212,17 +172,13 @@ github.com/mdlayher/socket v0.1.0/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5A
|
||||
github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE=
|
||||
github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-server-timing v1.0.1 h1:f00/aIe8T3MrnLhQHu3tSWvnwc5GV/p5eutuu3hF/tE=
|
||||
github.com/mitchellh/go-server-timing v1.0.1/go.mod h1:Mo6GKi9FSLwWFAMn3bqVPWe20y5ri5QGQuO9D9MCOxk=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
@@ -230,8 +186,6 @@ github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjW
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
|
||||
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
|
||||
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -240,15 +194,11 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857 h1:0FEd4UaKVIMBc18S6zAHWtvqpUPcLVhGU2lweJwJhuA=
|
||||
github.com/rot256/pblind v0.0.0-20230622102829-4dc2c6e4b857/go.mod h1:SI9+Ls7HSJkgYArhh8oBPhXiNL7tJltkU1H6Pm2o8Zo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1 h1:vfAp3Jbca7Vt8axzmkS5M/RtFJmj0CKmrtWAlHtesaA=
|
||||
github.com/rot256/pblind v0.0.0-20231024115251-cd3f239f28c1/go.mod h1:2x8fbm9T+uTl919COhEVHKGkve1DnkrEnDbtGptZuW8=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safing/jess v0.3.0/go.mod h1:JbYsPk5iJZx0OXDZeMcjS9qEdkGVUg+DCA8Fw2LdN9s=
|
||||
github.com/safing/jess v0.3.1 h1:cMZVhi2whW/YdD98MPLeLIWJndQ7o2QVt2HefQ/ByFA=
|
||||
github.com/safing/jess v0.3.1/go.mod h1:aj73Eot1zm2ETkJuw9hJlIO8bRom52uBbsCHemvlZmA=
|
||||
github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4fdlOMS91Y=
|
||||
github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8=
|
||||
github.com/safing/jess v0.3.2 h1:d21+sautJlfTKcwgm6/TohUf0F8DL27Lug2XHBOBqvY=
|
||||
github.com/safing/jess v0.3.2/go.mod h1:N1Qiw9YBDHSVilocPEWHoGIEi+DvtmGds76rUhAyctU=
|
||||
github.com/safing/portbase v0.18.5 h1:BgIBpreSNOnyHqx6Ovx3xJMkX2yOa3A2uLpfVBbfJPM=
|
||||
github.com/safing/portbase v0.18.5/go.mod h1:qhhLjrr5iEGU9r7RZ6hJdtulOeycJ0d0jq95ZxGJ9Hs=
|
||||
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec h1:oSJY1seobofPwpMoJRkCgXnTwfiQWNfGMCPDfqgAEfg=
|
||||
@@ -267,38 +217,23 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY=
|
||||
github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spkg/zipfs v0.7.1 h1:+2X5lvNHTybnDMQZAIHgedRXZK1WXdc+94R/P5v2XWE=
|
||||
github.com/spkg/zipfs v0.7.1/go.mod h1:48LW+/Rh1G7aAav1ew1PdlYn52T+LM+ARmSHfDNJvg8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tannerryan/ring v1.1.2 h1:iXayOjqHQOLzuy9GwSKuG3nhWfzQkldMlQivcgIr7gQ=
|
||||
@@ -306,7 +241,6 @@ github.com/tannerryan/ring v1.1.2/go.mod h1:DkELJEjbZhJBtFKR9Xziwj3HKZnb/knRgljN
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
|
||||
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
@@ -316,48 +250,34 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/tklauser/numcpus v0.5.0/go.mod h1:OGzpTxpcIMNGYQdit2BYL1pvk/dSOaJWjKoflh+RQjo=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zalando/go-keyring v0.2.1/go.mod h1:g63M2PPn0w5vjmEbwAX3ib5I+41zdm4esSETOn9Y6Dw=
|
||||
github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms=
|
||||
github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
@@ -387,11 +307,9 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -399,21 +317,16 @@ golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -429,8 +342,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -438,13 +349,6 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -452,9 +356,7 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -481,10 +383,6 @@ google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -496,12 +394,12 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gvisor.dev/gvisor v0.0.0-20231017021626-e69b01c3a8f3 h1:mBncdd8LGjLQ3Nv2zjwREWD/mAVxYCexRnHOJgA8KAE=
|
||||
gvisor.dev/gvisor v0.0.0-20231017021626-e69b01c3a8f3/go.mod h1:8hmigyCdYtw5xJGfQDJzSH5Ju8XEIDBnpyi8+O6GRt8=
|
||||
gvisor.dev/gvisor v0.0.0-20231025034759-d4973670c3c9 h1:r9LWDJNzg5lbv/re1BdpSkqdramsmr8An9mDU9QTPiQ=
|
||||
gvisor.dev/gvisor v0.0.0-20231025034759-d4973670c3c9/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
|
||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/libc v1.28.0 h1:kHB6LtDBV8DEAK7aZT1vWvP92abW9fb8cjb1P9UTpUE=
|
||||
modernc.org/libc v1.28.0/go.mod h1:DaG/4Q3LRRdqpiLyP0C2m1B8ZMGkQ+cCgOIjEtQlYhQ=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
||||
|
||||
@@ -432,6 +432,15 @@ func (db *Database) RemoveHistoryForProfile(ctx context.Context, profileID strin
|
||||
}))
|
||||
}
|
||||
|
||||
// MigrateProfileID migrates the given profile IDs in the history database.
|
||||
// This needs to be done when profiles are deleted and replaced by a different profile.
|
||||
func (db *Database) MigrateProfileID(ctx context.Context, from string, to string) error {
|
||||
return db.ExecuteWrite(ctx, "UPDATE history.connections SET profile = :to WHERE profile = :from", orm.WithNamedArgs(map[string]any{
|
||||
":from": from,
|
||||
":to": to,
|
||||
}))
|
||||
}
|
||||
|
||||
// dumpTo is a simple helper method that dumps all rows stored in the SQLite database
|
||||
// as JSON to w.
|
||||
// Any error aborts dumping rows and is returned.
|
||||
|
||||
@@ -272,6 +272,21 @@ func (m *module) start() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate profile IDs in history database when profiles are migrated/merged.
|
||||
if err := m.RegisterEventHook(
|
||||
"profiles",
|
||||
"profile migrated",
|
||||
"migrate profile IDs in history database",
|
||||
func(ctx context.Context, data interface{}) error {
|
||||
if profileIDs, ok := data.([]string); ok && len(profileIDs) == 2 {
|
||||
return m.Store.MigrateProfileID(ctx, profileIDs[0], profileIDs[1])
|
||||
}
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -582,6 +582,11 @@ func GetAllConnections() []*Connection {
|
||||
return conns.list()
|
||||
}
|
||||
|
||||
// GetDNSConnection fetches a DNS Connection from the database.
|
||||
func GetDNSConnection(dnsConnID string) (*Connection, bool) {
|
||||
return dnsConns.get(dnsConnID)
|
||||
}
|
||||
|
||||
// SetLocalIP sets the local IP address together with its network scope. The
|
||||
// connection is not locked for this.
|
||||
func (conn *Connection) SetLocalIP(ip net.IP) {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/network/state"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -12,8 +19,14 @@ var (
|
||||
defaultFirewallHandler FirewallHandler
|
||||
)
|
||||
|
||||
// Events.
|
||||
var (
|
||||
ConnectionReattributedEvent = "connection re-attributed"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("network", prep, start, nil, "base", "netenv", "processes")
|
||||
module.RegisterEvent(ConnectionReattributedEvent, false)
|
||||
}
|
||||
|
||||
// SetDefaultFirewallHandler sets the default firewall handler.
|
||||
@@ -45,5 +58,94 @@ func start() error {
|
||||
module.StartServiceWorker("clean connections", 0, connectionCleaner)
|
||||
module.StartServiceWorker("write open dns requests", 0, openDNSRequestWriter)
|
||||
|
||||
if err := module.RegisterEventHook(
|
||||
"profiles",
|
||||
profile.DeletedEvent,
|
||||
"re-attribute connections from deleted profile",
|
||||
reAttributeConnections,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var reAttributionLock sync.Mutex
|
||||
|
||||
// reAttributeConnections finds all connections of a deleted profile and re-attributes them.
|
||||
// Expected event data: scoped profile ID.
|
||||
func reAttributeConnections(_ context.Context, eventData any) error {
|
||||
profileID, ok := eventData.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("event data is not a string: %v", eventData)
|
||||
}
|
||||
profileSource, profileID, ok := strings.Cut(profileID, "/")
|
||||
if !ok {
|
||||
return fmt.Errorf("event data does not seem to be a scoped profile ID: %v", eventData)
|
||||
}
|
||||
|
||||
// Hold a lock for re-attribution, to prevent simultaneous processing of the
|
||||
// same connections and make logging cleaner.
|
||||
reAttributionLock.Lock()
|
||||
defer reAttributionLock.Unlock()
|
||||
|
||||
// Create tracing context.
|
||||
ctx, tracer := log.AddTracer(context.Background())
|
||||
defer tracer.Submit()
|
||||
tracer.Infof("network: re-attributing connections from deleted profile %s/%s", profileSource, profileID)
|
||||
|
||||
// Count and log how many connections were re-attributed.
|
||||
var reAttributed int
|
||||
|
||||
// Re-attribute connections.
|
||||
for _, conn := range conns.clone() {
|
||||
if reAttributeConnection(ctx, conn, profileID, profileSource) {
|
||||
reAttributed++
|
||||
tracer.Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-attribute dns connections.
|
||||
for _, conn := range dnsConns.clone() {
|
||||
if reAttributeConnection(ctx, conn, profileID, profileSource) {
|
||||
reAttributed++
|
||||
tracer.Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID)
|
||||
}
|
||||
}
|
||||
|
||||
tracer.Infof("filter: re-attributed %d connections", reAttributed)
|
||||
return nil
|
||||
}
|
||||
|
||||
func reAttributeConnection(ctx context.Context, conn *Connection, profileID, profileSource string) (reAttributed bool) {
|
||||
// Lock the connection before checking anything to avoid a race condition with connection data collection.
|
||||
conn.Lock()
|
||||
defer conn.Unlock()
|
||||
|
||||
// Check if the connection has the profile we are looking for.
|
||||
switch {
|
||||
case !conn.DataIsComplete():
|
||||
return false
|
||||
case conn.ProcessContext.Profile != profileID:
|
||||
return false
|
||||
case conn.ProcessContext.Source != profileSource:
|
||||
return false
|
||||
}
|
||||
|
||||
// Attempt to assign new profile.
|
||||
err := conn.process.RefetchProfile(ctx)
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Warningf("network: failed to refetch profile for %s: %s", conn, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Set the new process context.
|
||||
conn.ProcessContext = getProcessContext(ctx, conn.process)
|
||||
conn.Save()
|
||||
|
||||
// Trigger event for re-attribution.
|
||||
module.TriggerEvent(ConnectionReattributedEvent, conn.ID)
|
||||
|
||||
log.Tracer(ctx).Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -40,6 +40,24 @@ func (p *Process) GetProfile(ctx context.Context) (changed bool, err error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RefetchProfile removes the profile and finds and assigns a new profile.
|
||||
func (p *Process) RefetchProfile(ctx context.Context) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
// Get special or regular profile.
|
||||
localProfile, err := profile.GetLocalProfile(p.getSpecialProfileID(), p.MatchingData(), p.CreateProfileCallback)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find profile: %w", err)
|
||||
}
|
||||
|
||||
// Assign profile to process.
|
||||
p.PrimaryProfileID = localProfile.ScopedID()
|
||||
p.profile = localProfile.LayeredProfile()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSpecialProfileID returns the special profile ID for the process, if any.
|
||||
func (p *Process) getSpecialProfileID() (specialProfileID string) {
|
||||
// Check if we need a special profile.
|
||||
|
||||
66
profile/api.go
Normal file
66
profile/api.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/formats/dsd"
|
||||
)
|
||||
|
||||
func registerAPIEndpoints() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Merge profiles",
|
||||
Description: "Merge multiple profiles into a new one.",
|
||||
Path: "profile/merge",
|
||||
Write: api.PermitUser,
|
||||
BelongsTo: module,
|
||||
StructFunc: handleMergeProfiles,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type mergeProfilesRequest struct {
|
||||
Name string `json:"name"` // Name of the new merged profile.
|
||||
To string `json:"to"` // Profile scoped ID.
|
||||
From []string `json:"from"` // Profile scoped IDs.
|
||||
}
|
||||
|
||||
type mergeprofilesResponse struct {
|
||||
New string `json:"new"` // Profile scoped ID.
|
||||
}
|
||||
|
||||
func handleMergeProfiles(ar *api.Request) (i interface{}, err error) {
|
||||
request := &mergeProfilesRequest{}
|
||||
_, err = dsd.MimeLoad(ar.InputData, ar.Header.Get("Content-Type"), request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse request: %w", err)
|
||||
}
|
||||
|
||||
// Get all profiles.
|
||||
var (
|
||||
primary *Profile
|
||||
secondaries = make([]*Profile, 0, len(request.From))
|
||||
)
|
||||
if primary, err = getProfile(request.To); err != nil {
|
||||
return nil, fmt.Errorf("failed to get profile %s: %w", request.To, err)
|
||||
}
|
||||
for _, from := range request.From {
|
||||
sp, err := getProfile(from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get profile %s: %w", request.To, err)
|
||||
}
|
||||
secondaries = append(secondaries, sp)
|
||||
}
|
||||
|
||||
newProfile, err := MergeProfiles(request.Name, primary, secondaries...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge profiles: %w", err)
|
||||
}
|
||||
|
||||
return &mergeprofilesResponse{
|
||||
New: newProfile.ScopedID(),
|
||||
}, nil
|
||||
}
|
||||
@@ -194,9 +194,10 @@ func registerConfiguration() error { //nolint:maintidx
|
||||
OptType: config.OptTypeString,
|
||||
DefaultValue: DefaultActionPermitValue,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.DisplayOrderAnnotation: cfgOptionDefaultActionOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.DisplayOrderAnnotation: cfgOptionDefaultActionOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
},
|
||||
PossibleValues: []config.PossibleValue{
|
||||
{
|
||||
@@ -232,9 +233,10 @@ func registerConfiguration() error { //nolint:maintidx
|
||||
ReleaseLevel: config.ReleaseLevelBeta,
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: cfgOptionDisableAutoPermitOrder,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.CategoryAnnotation: "Advanced",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayOrderAnnotation: cfgOptionDisableAutoPermitOrder,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.CategoryAnnotation: "Advanced",
|
||||
},
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
})
|
||||
@@ -256,6 +258,7 @@ In order to reduce noise optimize performance, internal and device-only (localho
|
||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||
DefaultValue: false,
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayOrderAnnotation: cfgOptionEnableHistoryOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
config.RequiresFeatureIDAnnotation: account.FeatureHistory,
|
||||
@@ -280,6 +283,7 @@ Set to 0 days to keep network history forever. Depending on your device, this mi
|
||||
ExpertiseLevel: config.ExpertiseLevelUser,
|
||||
DefaultValue: 30,
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.UnitAnnotation: "Days",
|
||||
config.DisplayOrderAnnotation: cfgOptionKeepHistoryOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
@@ -344,6 +348,7 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
OptType: config.OptTypeStringArray,
|
||||
DefaultValue: []string{},
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.StackableAnnotation: true,
|
||||
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
|
||||
config.DisplayOrderAnnotation: cfgOptionEndpointsOrder,
|
||||
@@ -370,6 +375,7 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: []string{},
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.StackableAnnotation: true,
|
||||
config.DisplayHintAnnotation: endpoints.DisplayHintEndpointList,
|
||||
config.DisplayOrderAnnotation: cfgOptionServiceEndpointsOrder,
|
||||
@@ -426,9 +432,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
OptType: config.OptTypeStringArray,
|
||||
DefaultValue: defaultFilterListsValue,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: "filter list",
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterListsOrder,
|
||||
config.CategoryAnnotation: "Filter Lists",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: "filter list",
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterListsOrder,
|
||||
config.CategoryAnnotation: "Filter Lists",
|
||||
},
|
||||
ValidationRegex: `^[a-zA-Z0-9\-]+$`,
|
||||
})
|
||||
@@ -447,9 +454,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterCNAMEOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterCNAMEOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
},
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
})
|
||||
@@ -468,9 +476,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterSubDomainsOrder,
|
||||
config.CategoryAnnotation: "Filter Lists",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionFilterSubDomainsOrder,
|
||||
config.CategoryAnnotation: "Filter Lists",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -489,9 +498,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelOff,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeLocalOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeLocalOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -509,9 +519,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelOff,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeLANOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeLANOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -529,9 +540,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelOff,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeInternetOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockScopeInternetOrder,
|
||||
config.CategoryAnnotation: "Network Scope",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -549,9 +561,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelOff,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockP2POrder,
|
||||
config.CategoryAnnotation: "Connection Types",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockP2POrder,
|
||||
config.CategoryAnnotation: "Connection Types",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -569,9 +582,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockInboundOrder,
|
||||
config.CategoryAnnotation: "Connection Types",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionBlockInboundOrder,
|
||||
config.CategoryAnnotation: "Connection Types",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -590,9 +604,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionRemoveOutOfScopeDNSOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionRemoveOutOfScopeDNSOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -611,9 +626,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionRemoveBlockedDNSOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionRemoveBlockedDNSOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -632,9 +648,10 @@ Pro Tip: You can use "#" to add a comment to a rule.
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionDomainHeuristicsOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionDomainHeuristicsOrder,
|
||||
config.CategoryAnnotation: "DNS Filtering",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -662,9 +679,10 @@ Please note that DNS bypass attempts might be additionally blocked in the Sytem
|
||||
DefaultValue: status.SecurityLevelsAll,
|
||||
PossibleValues: status.AllSecurityLevelValues,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionPreventBypassingOrder,
|
||||
config.CategoryAnnotation: "Advanced",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: status.DisplayHintSecurityLevel,
|
||||
config.DisplayOrderAnnotation: cfgOptionPreventBypassingOrder,
|
||||
config.CategoryAnnotation: "Advanced",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -681,8 +699,9 @@ Please note that DNS bypass attempts might be additionally blocked in the Sytem
|
||||
OptType: config.OptTypeBool,
|
||||
DefaultValue: true,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: cfgOptionUseSPNOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayOrderAnnotation: cfgOptionUseSPNOrder,
|
||||
config.CategoryAnnotation: "General",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -701,6 +720,7 @@ Please note that DNS bypass attempts might be additionally blocked in the Sytem
|
||||
OptType: config.OptTypeStringArray,
|
||||
DefaultValue: []string{},
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.StackableAnnotation: true,
|
||||
config.CategoryAnnotation: "General",
|
||||
config.DisplayOrderAnnotation: cfgOptionSPNUsagePolicyOrder,
|
||||
@@ -727,6 +747,7 @@ Please note that DNS bypass attempts might be additionally blocked in the Sytem
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
DefaultValue: []string{},
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.StackableAnnotation: true,
|
||||
config.CategoryAnnotation: "Routing",
|
||||
config.DisplayOrderAnnotation: cfgOptionTransitHubPolicyOrder,
|
||||
@@ -755,6 +776,7 @@ By default, the Portmaster tries to choose the node closest to the destination a
|
||||
OptType: config.OptTypeStringArray,
|
||||
DefaultValue: []string{},
|
||||
Annotations: config.Annotations{
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.StackableAnnotation: true,
|
||||
config.CategoryAnnotation: "Routing",
|
||||
config.DisplayOrderAnnotation: cfgOptionExitHubPolicyOrder,
|
||||
@@ -779,9 +801,10 @@ By default, the Portmaster tries to choose the node closest to the destination a
|
||||
OptType: config.OptTypeString,
|
||||
DefaultValue: DefaultRoutingProfileID,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.DisplayOrderAnnotation: cfgOptionRoutingAlgorithmOrder,
|
||||
config.CategoryAnnotation: "Routing",
|
||||
config.SettablePerAppAnnotation: true,
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.DisplayOrderAnnotation: cfgOptionRoutingAlgorithmOrder,
|
||||
config.CategoryAnnotation: "Routing",
|
||||
},
|
||||
PossibleValues: []config.PossibleValue{
|
||||
{
|
||||
|
||||
@@ -16,7 +16,8 @@ import (
|
||||
// core:profiles/<scope>/<id>
|
||||
// cache:profiles/index/<identifier>/<value>
|
||||
|
||||
const profilesDBPath = "core:profiles/"
|
||||
// ProfilesDBPath is the base database path for profiles.
|
||||
const ProfilesDBPath = "core:profiles/"
|
||||
|
||||
var profileDB = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
@@ -28,17 +29,17 @@ func makeScopedID(source profileSource, id string) string {
|
||||
}
|
||||
|
||||
func makeProfileKey(source profileSource, id string) string {
|
||||
return profilesDBPath + string(source) + "/" + id
|
||||
return ProfilesDBPath + string(source) + "/" + id
|
||||
}
|
||||
|
||||
func registerValidationDBHook() (err error) {
|
||||
_, err = database.RegisterHook(query.New(profilesDBPath), &databaseHook{})
|
||||
_, err = database.RegisterHook(query.New(ProfilesDBPath), &databaseHook{})
|
||||
return
|
||||
}
|
||||
|
||||
func startProfileUpdateChecker() error {
|
||||
module.StartServiceWorker("update active profiles", 0, func(ctx context.Context) (err error) {
|
||||
profilesSub, err := profileDB.Subscribe(query.New(profilesDBPath))
|
||||
profilesSub, err := profileDB.Subscribe(query.New(ProfilesDBPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,8 +60,14 @@ func startProfileUpdateChecker() error {
|
||||
}
|
||||
|
||||
// Get active profile.
|
||||
activeProfile := getActiveProfile(strings.TrimPrefix(r.Key(), profilesDBPath))
|
||||
scopedID := strings.TrimPrefix(r.Key(), ProfilesDBPath)
|
||||
activeProfile := getActiveProfile(scopedID)
|
||||
if activeProfile == nil {
|
||||
// Check if profile is being deleted.
|
||||
if r.Meta().IsDeleted() {
|
||||
meta.MarkDeleted(scopedID)
|
||||
}
|
||||
|
||||
// Don't do any additional actions if the profile is not active.
|
||||
continue profileFeed
|
||||
}
|
||||
@@ -74,7 +81,9 @@ func startProfileUpdateChecker() error {
|
||||
// Always mark as outdated if the record is being deleted.
|
||||
if r.Meta().IsDeleted() {
|
||||
activeProfile.outdated.Set()
|
||||
module.TriggerEvent(profileConfigChange, nil)
|
||||
|
||||
meta.MarkDeleted(scopedID)
|
||||
module.TriggerEvent(DeletedEvent, scopedID)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -83,7 +92,7 @@ func startProfileUpdateChecker() error {
|
||||
receivedProfile, err := EnsureProfile(r)
|
||||
if err != nil || !receivedProfile.savedInternally {
|
||||
activeProfile.outdated.Set()
|
||||
module.TriggerEvent(profileConfigChange, nil)
|
||||
module.TriggerEvent(ConfigChangeEvent, scopedID)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
@@ -105,6 +114,11 @@ func (h *databaseHook) UsesPrePut() bool {
|
||||
|
||||
// PrePut implements the Hook interface.
|
||||
func (h *databaseHook) PrePut(r record.Record) (record.Record, error) {
|
||||
// Do not intervene with metadata key.
|
||||
if r.Key() == profilesMetadataKey {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// convert
|
||||
profile, err := EnsureProfile(r)
|
||||
if err != nil {
|
||||
|
||||
@@ -202,7 +202,8 @@ func invalidDefinitionError(fields []string, msg string) error {
|
||||
return fmt.Errorf(`invalid endpoint definition: "%s" - %s`, strings.Join(fields, " "), msg)
|
||||
}
|
||||
|
||||
func parseEndpoint(value string) (endpoint Endpoint, err error) { //nolint:gocognit
|
||||
//nolint:gocognit,nakedret
|
||||
func parseEndpoint(value string) (endpoint Endpoint, err error) {
|
||||
fields := strings.Fields(value)
|
||||
if len(fields) < 2 {
|
||||
return nil, fmt.Errorf(`invalid endpoint definition: "%s"`, value)
|
||||
|
||||
@@ -177,7 +177,7 @@ func GetLocalProfile(id string, md MatchingData, createProfileCallback func() *P
|
||||
// getProfile fetches the profile for the given scoped ID.
|
||||
func getProfile(scopedID string) (profile *Profile, err error) {
|
||||
// Get profile from the database.
|
||||
r, err := profileDB.Get(profilesDBPath + scopedID)
|
||||
r, err := profileDB.Get(ProfilesDBPath + scopedID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -193,7 +193,7 @@ func findProfile(source profileSource, md MatchingData) (profile *Profile, err e
|
||||
// process might be quite expensive. Measure impact and possibly improve.
|
||||
|
||||
// Get iterator over all profiles.
|
||||
it, err := profileDB.Query(query.New(profilesDBPath + makeScopedID(source, "")))
|
||||
it, err := profileDB.Query(query.New(ProfilesDBPath + makeScopedID(source, "")))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query for profiles: %w", err)
|
||||
}
|
||||
@@ -287,6 +287,9 @@ func loadProfile(r record.Record) (*Profile, error) {
|
||||
// Set saved internally to suppress outdating profiles if saving internally.
|
||||
profile.savedInternally = true
|
||||
|
||||
// Mark as recently seen.
|
||||
meta.UpdateLastSeen(profile.ScopedID())
|
||||
|
||||
// return parsed profile
|
||||
return profile, nil
|
||||
}
|
||||
@@ -299,7 +302,7 @@ func notifyConflictingProfiles(a, b record.Record, md MatchingData) {
|
||||
idA = profileA.ScopedID()
|
||||
nameA = profileA.Name
|
||||
} else {
|
||||
idA = strings.TrimPrefix(a.Key(), profilesDBPath)
|
||||
idA = strings.TrimPrefix(a.Key(), ProfilesDBPath)
|
||||
nameA = path.Base(idA)
|
||||
}
|
||||
profileB, err := EnsureProfile(b)
|
||||
@@ -307,7 +310,7 @@ func notifyConflictingProfiles(a, b record.Record, md MatchingData) {
|
||||
idB = profileB.ScopedID()
|
||||
nameB = profileB.Name
|
||||
} else {
|
||||
idB = strings.TrimPrefix(b.Key(), profilesDBPath)
|
||||
idB = strings.TrimPrefix(b.Key(), ProfilesDBPath)
|
||||
nameB = path.Base(idB)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
)
|
||||
@@ -11,19 +13,42 @@ import (
|
||||
// The new profile is saved and returned.
|
||||
// Only the icon and fingerprints are inherited from other profiles.
|
||||
// All other information is taken only from the primary profile.
|
||||
func MergeProfiles(primary *Profile, secondaries ...*Profile) (newProfile *Profile, err error) {
|
||||
func MergeProfiles(name string, primary *Profile, secondaries ...*Profile) (newProfile *Profile, err error) {
|
||||
if primary == nil || len(secondaries) == 0 {
|
||||
return nil, errors.New("must supply both a primary and at least one secondary profile for merging")
|
||||
}
|
||||
|
||||
// Fill info from primary profile.
|
||||
nowUnix := time.Now().Unix()
|
||||
newProfile = &Profile{
|
||||
Base: record.Base{},
|
||||
RWMutex: sync.RWMutex{},
|
||||
ID: "", // Omit ID to derive it from the new fingerprints.
|
||||
Source: primary.Source,
|
||||
Name: primary.Name,
|
||||
Name: name,
|
||||
Description: primary.Description,
|
||||
Homepage: primary.Homepage,
|
||||
UsePresentationPath: false, // Disable presentation path.
|
||||
SecurityLevel: primary.SecurityLevel,
|
||||
Config: primary.Config,
|
||||
Created: nowUnix,
|
||||
}
|
||||
|
||||
// Fall back to name of primary profile, if none is set.
|
||||
if newProfile.Name == "" {
|
||||
newProfile.Name = primary.Name
|
||||
}
|
||||
|
||||
// If any profile was edited, set LastEdited to now.
|
||||
if primary.LastEdited > 0 {
|
||||
newProfile.LastEdited = nowUnix
|
||||
} else {
|
||||
for _, sp := range secondaries {
|
||||
if sp.LastEdited > 0 {
|
||||
newProfile.LastEdited = nowUnix
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect all icons.
|
||||
@@ -35,7 +60,7 @@ func MergeProfiles(primary *Profile, secondaries ...*Profile) (newProfile *Profi
|
||||
newProfile.Icons = sortAndCompactIcons(newProfile.Icons)
|
||||
|
||||
// Collect all fingerprints.
|
||||
newProfile.Fingerprints = make([]Fingerprint, 0, len(secondaries)+1) // Guess the needed space.
|
||||
newProfile.Fingerprints = make([]Fingerprint, 0, len(primary.Fingerprints)+len(secondaries)) // Guess the needed space.
|
||||
newProfile.Fingerprints = addFingerprints(newProfile.Fingerprints, primary.Fingerprints, primary.ScopedID())
|
||||
for _, sp := range secondaries {
|
||||
newProfile.Fingerprints = addFingerprints(newProfile.Fingerprints, sp.Fingerprints, sp.ScopedID())
|
||||
@@ -44,26 +69,21 @@ func MergeProfiles(primary *Profile, secondaries ...*Profile) (newProfile *Profi
|
||||
|
||||
// Save new profile.
|
||||
newProfile = New(newProfile)
|
||||
err = newProfile.Save()
|
||||
if err != nil {
|
||||
if err := newProfile.Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save merged profile: %w", err)
|
||||
}
|
||||
// FIXME: Should we ... ?
|
||||
// newProfile.updateMetadata()
|
||||
// newProfile.updateMetadataFromSystem()
|
||||
|
||||
// Delete all previous profiles.
|
||||
// FIXME:
|
||||
/*
|
||||
primary.Meta().Delete()
|
||||
// Set as outdated and remove from active profiles.
|
||||
// Signify that profile was deleted and save for sync.
|
||||
for _, sp := range secondaries {
|
||||
sp.Meta().Delete()
|
||||
// Set as outdated and remove from active profiles.
|
||||
// Signify that profile was deleted and save for sync.
|
||||
if err := primary.delete(); err != nil {
|
||||
return nil, fmt.Errorf("failed to delete primary profile %s: %w", primary.ScopedID(), err)
|
||||
}
|
||||
module.TriggerEvent(MigratedEvent, []string{primary.ScopedID(), newProfile.ScopedID()})
|
||||
for _, sp := range secondaries {
|
||||
if err := sp.delete(); err != nil {
|
||||
return nil, fmt.Errorf("failed to delete secondary profile %s: %w", sp.ScopedID(), err)
|
||||
}
|
||||
*/
|
||||
module.TriggerEvent(MigratedEvent, []string{sp.ScopedID(), newProfile.ScopedID()})
|
||||
}
|
||||
|
||||
return newProfile, nil
|
||||
}
|
||||
|
||||
184
profile/meta.go
Normal file
184
profile/meta.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
)
|
||||
|
||||
// ProfilesMetadata holds metadata about all profiles that are not fit to be
|
||||
// stored with the profiles themselves.
|
||||
type ProfilesMetadata struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
States map[string]*MetaState
|
||||
}
|
||||
|
||||
// MetaState describes the state of a profile.
|
||||
type MetaState struct {
|
||||
State string
|
||||
At time.Time
|
||||
}
|
||||
|
||||
// Profile metadata states.
|
||||
const (
|
||||
MetaStateSeen = "seen"
|
||||
MetaStateDeleted = "deleted"
|
||||
)
|
||||
|
||||
// EnsureProfilesMetadata ensures that the given record is a *ProfilesMetadata, and returns it.
|
||||
func EnsureProfilesMetadata(r record.Record) (*ProfilesMetadata, error) {
|
||||
// unwrap
|
||||
if r.IsWrapped() {
|
||||
// only allocate a new struct, if we need it
|
||||
newMeta := &ProfilesMetadata{}
|
||||
err := record.Unwrap(r, newMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newMeta, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
newMeta, ok := r.(*ProfilesMetadata)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *Profile, but %T", r)
|
||||
}
|
||||
return newMeta, nil
|
||||
}
|
||||
|
||||
var (
|
||||
profilesMetadataKey = "core:profile-states"
|
||||
|
||||
meta *ProfilesMetadata
|
||||
|
||||
removeDeletedEntriesAfter = 30 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// loadProfilesMetadata loads the profile metadata from the database.
|
||||
// It may only be called during module starting, as there is no lock for "meta" itself.
|
||||
func loadProfilesMetadata() error {
|
||||
r, err := profileDB.Get(profilesMetadataKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
loadedMeta, err := EnsureProfilesMetadata(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set package variable.
|
||||
meta = loadedMeta
|
||||
return nil
|
||||
}
|
||||
|
||||
func (meta *ProfilesMetadata) check() {
|
||||
if meta.States == nil {
|
||||
meta.States = make(map[string]*MetaState)
|
||||
}
|
||||
}
|
||||
|
||||
// Save saves the profile metadata to the database.
|
||||
func (meta *ProfilesMetadata) Save() error {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
func() {
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
if !meta.KeyIsSet() {
|
||||
meta.SetKey(profilesMetadataKey)
|
||||
}
|
||||
}()
|
||||
|
||||
meta.Clean()
|
||||
return profileDB.Put(meta)
|
||||
}
|
||||
|
||||
// Clean removes old entries.
|
||||
func (meta *ProfilesMetadata) Clean() {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
for key, state := range meta.States {
|
||||
switch {
|
||||
case state == nil:
|
||||
delete(meta.States, key)
|
||||
case state.State != MetaStateDeleted:
|
||||
continue
|
||||
case time.Since(state.At) > removeDeletedEntriesAfter:
|
||||
delete(meta.States, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLastSeen returns when the profile with the given ID was last seen.
|
||||
func (meta *ProfilesMetadata) GetLastSeen(scopedID string) *time.Time {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
state := meta.States[scopedID]
|
||||
switch {
|
||||
case state == nil:
|
||||
return nil
|
||||
case state.State == MetaStateSeen:
|
||||
return &state.At
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateLastSeen sets the profile with the given ID as last seen now.
|
||||
func (meta *ProfilesMetadata) UpdateLastSeen(scopedID string) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
meta.States[scopedID] = &MetaState{
|
||||
State: MetaStateSeen,
|
||||
At: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkDeleted marks the profile with the given ID as deleted.
|
||||
func (meta *ProfilesMetadata) MarkDeleted(scopedID string) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
meta.States[scopedID] = &MetaState{
|
||||
State: MetaStateDeleted,
|
||||
At: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveState removes any state of the profile with the given ID.
|
||||
func (meta *ProfilesMetadata) RemoveState(scopedID string) {
|
||||
if meta == nil {
|
||||
return
|
||||
}
|
||||
|
||||
meta.Lock()
|
||||
defer meta.Unlock()
|
||||
|
||||
delete(meta.States, scopedID)
|
||||
}
|
||||
@@ -32,11 +32,11 @@ func registerMigrations() error {
|
||||
Version: "v1.4.7",
|
||||
MigrateFunc: migrateIcons,
|
||||
},
|
||||
// migration.Migration{
|
||||
// Description: "Migrate from random profile IDs to fingerprint-derived IDs",
|
||||
// Version: "v1.5.1",
|
||||
// MigrateFunc: migrateToDerivedIDs,
|
||||
// },
|
||||
migration.Migration{
|
||||
Description: "Migrate from random profile IDs to fingerprint-derived IDs",
|
||||
Version: "v1.6.0",
|
||||
MigrateFunc: migrateToDerivedIDs,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func migrateNetworkRatingSystem(ctx context.Context, _, to *version.Version, db
|
||||
|
||||
func migrateLinkedPath(ctx context.Context, _, to *version.Version, db *database.Interface) error {
|
||||
// Get iterator over all profiles.
|
||||
it, err := db.Query(query.New(profilesDBPath))
|
||||
it, err := db.Query(query.New(ProfilesDBPath))
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Errorf("profile: failed to migrate from linked path: failed to start query: %s", err)
|
||||
return nil
|
||||
@@ -112,7 +112,7 @@ func migrateLinkedPath(ctx context.Context, _, to *version.Version, db *database
|
||||
|
||||
func migrateIcons(ctx context.Context, _, to *version.Version, db *database.Interface) error {
|
||||
// Get iterator over all profiles.
|
||||
it, err := db.Query(query.New(profilesDBPath))
|
||||
it, err := db.Query(query.New(ProfilesDBPath))
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Errorf("profile: failed to migrate from icon fields: failed to start query: %s", err)
|
||||
return nil
|
||||
@@ -181,7 +181,7 @@ func migrateToDerivedIDs(ctx context.Context, _, to *version.Version, db *databa
|
||||
var profilesToDelete []string //nolint:prealloc // We don't know how many profiles there are.
|
||||
|
||||
// Get iterator over all profiles.
|
||||
it, err := db.Query(query.New(profilesDBPath))
|
||||
it, err := db.Query(query.New(ProfilesDBPath))
|
||||
if err != nil {
|
||||
log.Tracer(ctx).Errorf("profile: failed to migrate to derived profile IDs: failed to start query: %s", err)
|
||||
return nil
|
||||
@@ -243,7 +243,7 @@ func migrateToDerivedIDs(ctx context.Context, _, to *version.Version, db *databa
|
||||
|
||||
// Delete old migrated profiles.
|
||||
for _, scopedID := range profilesToDelete {
|
||||
if err := db.Delete(profilesDBPath + scopedID); err != nil {
|
||||
if err := db.Delete(ProfilesDBPath + scopedID); err != nil {
|
||||
log.Tracer(ctx).Errorf("profile: failed to delete old profile %s during migration: %s", scopedID, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/migration"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
@@ -16,13 +18,18 @@ var (
|
||||
updatesPath string
|
||||
)
|
||||
|
||||
// Events.
|
||||
const (
|
||||
profileConfigChange = "profile config change"
|
||||
ConfigChangeEvent = "profile config change"
|
||||
DeletedEvent = "profile deleted"
|
||||
MigratedEvent = "profile migrated"
|
||||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("profiles", prep, start, nil, "base", "updates")
|
||||
module.RegisterEvent(profileConfigChange, true)
|
||||
module = modules.Register("profiles", prep, start, stop, "base", "updates")
|
||||
module.RegisterEvent(ConfigChangeEvent, true)
|
||||
module.RegisterEvent(DeletedEvent, true)
|
||||
module.RegisterEvent(MigratedEvent, true)
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
@@ -47,6 +54,14 @@ func start() error {
|
||||
updatesPath += string(os.PathSeparator)
|
||||
}
|
||||
|
||||
if err := loadProfilesMetadata(); err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
log.Warningf("profile: failed to load profiles metadata, falling back to empty state: %s", err)
|
||||
}
|
||||
meta = &ProfilesMetadata{}
|
||||
}
|
||||
meta.check()
|
||||
|
||||
if err := migrations.Migrate(module.Ctx); err != nil {
|
||||
log.Errorf("profile: migrations failed: %s", err)
|
||||
}
|
||||
@@ -73,5 +88,13 @@ func start() error {
|
||||
log.Warningf("profile: error during loading global profile from configuration: %s", err)
|
||||
}
|
||||
|
||||
if err := registerAPIEndpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
return meta.Save()
|
||||
}
|
||||
|
||||
@@ -304,6 +304,24 @@ func (profile *Profile) Save() error {
|
||||
return profileDB.Put(profile)
|
||||
}
|
||||
|
||||
// delete deletes the profile from the database.
|
||||
func (profile *Profile) delete() error {
|
||||
// Check if a key is set.
|
||||
if !profile.KeyIsSet() {
|
||||
return errors.New("key is not set")
|
||||
}
|
||||
|
||||
// Delete from database.
|
||||
profile.Meta().Delete()
|
||||
err := profileDB.Put(profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Post handling is done by the profile update feed.
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkStillActive marks the profile as still active.
|
||||
func (profile *Profile) MarkStillActive() {
|
||||
atomic.StoreInt64(profile.lastActive, time.Now().Unix())
|
||||
|
||||
29
sync/module.go
Normal file
29
sync/module.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
module *modules.Module
|
||||
|
||||
db = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("sync", prep, nil, nil, "profiles")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
if err := registerSettingsAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := registerSingleSettingAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
45
sync/profile.go
Normal file
45
sync/profile.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// ProfileExport holds an export of a profile.
|
||||
type ProfileExport struct { //nolint:maligned
|
||||
Type Type
|
||||
|
||||
// Identification (sync or import as new only)
|
||||
ID string
|
||||
Source string
|
||||
|
||||
// Human Metadata
|
||||
Name string
|
||||
Description string
|
||||
Homepage string
|
||||
Icons []profile.Icon
|
||||
PresentationPath string
|
||||
UsePresentationPath bool
|
||||
|
||||
// Process matching
|
||||
Fingerprints []profile.Fingerprint
|
||||
|
||||
// Settings
|
||||
Config map[string]any
|
||||
|
||||
// Metadata (sync only)
|
||||
LastEdited time.Time
|
||||
Created time.Time
|
||||
Internal bool
|
||||
}
|
||||
|
||||
// ProfileImportRequest is a request to import Profile.
|
||||
type ProfileImportRequest struct {
|
||||
ImportRequest
|
||||
|
||||
// Reset all settings and fingerprints of target before import.
|
||||
Reset bool
|
||||
|
||||
Export *ProfileExport
|
||||
}
|
||||
270
sync/setting_single.go
Normal file
270
sync/setting_single.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/formats/dsd"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// SingleSettingExport holds an export of a single setting.
|
||||
type SingleSettingExport struct {
|
||||
Type Type `json:"type"` // Must be TypeSingleSetting
|
||||
ID string `json:"id"` // Settings Key
|
||||
|
||||
Value any `json:"value"`
|
||||
}
|
||||
|
||||
// SingleSettingImportRequest is a request to import a single setting.
|
||||
type SingleSettingImportRequest struct {
|
||||
ImportRequest `json:",inline"`
|
||||
|
||||
Export *SingleSettingExport `json:"export"`
|
||||
}
|
||||
|
||||
func registerSingleSettingAPI() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Export Single Setting",
|
||||
Description: "Exports a single setting in a share-able format.",
|
||||
Path: "sync/single-setting/export",
|
||||
Read: api.PermitAdmin,
|
||||
Write: api.PermitAdmin,
|
||||
Parameters: []api.Parameter{{
|
||||
Method: http.MethodGet,
|
||||
Field: "from",
|
||||
Description: "Specify where to export from.",
|
||||
}, {
|
||||
Method: http.MethodGet,
|
||||
Field: "key",
|
||||
Description: "Specify which settings key to export.",
|
||||
}},
|
||||
BelongsTo: module,
|
||||
DataFunc: handleExportSingleSetting,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Import Single Setting",
|
||||
Description: "Imports a single setting from the share-able format.",
|
||||
Path: "sync/single-setting/import",
|
||||
Read: api.PermitAdmin,
|
||||
Write: api.PermitAdmin,
|
||||
Parameters: []api.Parameter{{
|
||||
Method: http.MethodPost,
|
||||
Field: "to",
|
||||
Description: "Specify where to import to.",
|
||||
}, {
|
||||
Method: http.MethodPost,
|
||||
Field: "key",
|
||||
Description: "Specify which setting key to import.",
|
||||
}, {
|
||||
Method: http.MethodPost,
|
||||
Field: "validate",
|
||||
Description: "Validate only.",
|
||||
}},
|
||||
BelongsTo: module,
|
||||
StructFunc: handleImportSingleSetting,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleExportSingleSetting(ar *api.Request) (data []byte, err error) {
|
||||
var request *ExportRequest
|
||||
|
||||
// Get parameters.
|
||||
q := ar.URL.Query()
|
||||
if len(q) > 0 {
|
||||
request = &ExportRequest{
|
||||
From: q.Get("from"),
|
||||
Keys: q["key"], // Get []string by direct map access.
|
||||
}
|
||||
} else {
|
||||
request = &ExportRequest{}
|
||||
if err := json.Unmarshal(ar.InputData, request); err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to parse export request: %w", ErrExportFailed, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check parameters.
|
||||
if request.From == "" || len(request.Keys) != 1 {
|
||||
return nil, errors.New("missing or malformed parameters")
|
||||
}
|
||||
|
||||
// Export.
|
||||
export, err := ExportSingleSetting(request.Keys[0], request.From)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializeExport(export, ar)
|
||||
}
|
||||
|
||||
func handleImportSingleSetting(ar *api.Request) (any, error) {
|
||||
var request *SingleSettingImportRequest
|
||||
|
||||
// Get parameters.
|
||||
q := ar.URL.Query()
|
||||
if len(q) > 0 {
|
||||
request = &SingleSettingImportRequest{
|
||||
ImportRequest: ImportRequest{
|
||||
Target: q.Get("to"),
|
||||
ValidateOnly: q.Has("validate"),
|
||||
RawExport: string(ar.InputData),
|
||||
RawMime: ar.Header.Get("Content-Type"),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
request = &SingleSettingImportRequest{}
|
||||
if _, err := dsd.MimeLoad(ar.InputData, ar.Header.Get("Accept"), request); err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to parse import request: %w", ErrInvalidImportRequest, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to parse the export.
|
||||
switch {
|
||||
case request.Export != nil && request.RawExport != "":
|
||||
return nil, fmt.Errorf("%w: both Export and RawExport are defined", ErrInvalidImportRequest)
|
||||
case request.RawExport != "":
|
||||
// Parse export.
|
||||
export := &SingleSettingExport{}
|
||||
if err := parseExport(&request.ImportRequest, export); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Export = export
|
||||
case request.Export != nil:
|
||||
// Export is aleady parsed.
|
||||
default:
|
||||
return nil, ErrInvalidImportRequest
|
||||
}
|
||||
|
||||
// Optional check if the setting key matches.
|
||||
if len(q) > 0 && q.Has("key") && q.Get("key") != request.Export.ID {
|
||||
return nil, ErrMismatch
|
||||
}
|
||||
|
||||
// Import.
|
||||
return ImportSingeSetting(request)
|
||||
}
|
||||
|
||||
// ExportSingleSetting export a single setting.
|
||||
func ExportSingleSetting(key, from string) (*SingleSettingExport, error) {
|
||||
option, err := config.GetOption(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: configuration %w", ErrSettingNotFound, err)
|
||||
}
|
||||
|
||||
var value any
|
||||
if from == ExportTargetGlobal {
|
||||
value = option.UserValue()
|
||||
if value == nil {
|
||||
return nil, ErrUnchanged
|
||||
}
|
||||
} else {
|
||||
// Check if the setting is settable per app.
|
||||
if !option.AnnotationEquals(config.SettablePerAppAnnotation, true) {
|
||||
return nil, ErrNotSettablePerApp
|
||||
}
|
||||
// Get and load profile.
|
||||
r, err := db.Get(profile.ProfilesDBPath + from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to find profile: %w", ErrTargetNotFound, err)
|
||||
}
|
||||
p, err := profile.EnsureProfile(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to load profile: %w", ErrExportFailed, err)
|
||||
}
|
||||
// Flatten config and get key we are looking for.
|
||||
flattened := config.Flatten(p.Config)
|
||||
value = flattened[key]
|
||||
if value == nil {
|
||||
return nil, ErrUnchanged
|
||||
}
|
||||
}
|
||||
|
||||
return &SingleSettingExport{
|
||||
Type: TypeSingleSetting,
|
||||
ID: key,
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ImportSingeSetting imports a single setting.
|
||||
func ImportSingeSetting(r *SingleSettingImportRequest) (*ImportResult, error) {
|
||||
// Check import.
|
||||
if r.Export.Type != TypeSingleSetting {
|
||||
return nil, ErrMismatch
|
||||
}
|
||||
|
||||
// Get option and validate value.
|
||||
option, err := config.GetOption(r.Export.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: configuration %w", ErrSettingNotFound, err)
|
||||
}
|
||||
if err := option.ValidateValue(r.Export.Value); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrInvalidSettingValue, err)
|
||||
}
|
||||
|
||||
// Import single global setting.
|
||||
if r.Target == ExportTargetGlobal {
|
||||
// Stop here if we are only validating.
|
||||
if r.ValidateOnly {
|
||||
return &ImportResult{
|
||||
RestartRequired: option.RequiresRestart,
|
||||
ReplacesExisting: option.IsSetByUser(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Actually import the setting.
|
||||
if err := config.SetConfigOption(r.Export.ID, r.Export.Value); err != nil {
|
||||
return nil, fmt.Errorf("%w: %w", ErrInvalidSettingValue, err)
|
||||
}
|
||||
} else {
|
||||
// Check if the setting is settable per app.
|
||||
if !option.AnnotationEquals(config.SettablePerAppAnnotation, true) {
|
||||
return nil, ErrNotSettablePerApp
|
||||
}
|
||||
// Import single setting into profile.
|
||||
rec, err := db.Get(profile.ProfilesDBPath + r.Target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to find profile: %w", ErrTargetNotFound, err)
|
||||
}
|
||||
p, err := profile.EnsureProfile(rec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to load profile: %w", ErrImportFailed, err)
|
||||
}
|
||||
|
||||
// Stop here if we are only validating.
|
||||
if r.ValidateOnly {
|
||||
return &ImportResult{
|
||||
RestartRequired: option.RequiresRestart,
|
||||
ReplacesExisting: option.IsSetByUser(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Set imported setting on profile.
|
||||
config.PutValueIntoHierarchicalConfig(p.Config, r.Export.ID, r.Export.Value)
|
||||
|
||||
// Mark profile as edited by user.
|
||||
p.LastEdited = time.Now().Unix()
|
||||
|
||||
// Save profile back to db.
|
||||
if err := p.Save(); err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to save profile: %w", ErrImportFailed, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &ImportResult{
|
||||
RestartRequired: option.RequiresRestart,
|
||||
ReplacesExisting: option.IsSetByUser(),
|
||||
}, nil
|
||||
}
|
||||
336
sync/settings.go
Normal file
336
sync/settings.go
Normal file
@@ -0,0 +1,336 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// SettingsExport holds an export of settings.
|
||||
type SettingsExport struct {
|
||||
Type Type `json:"type"`
|
||||
|
||||
Config map[string]any `json:"config"`
|
||||
}
|
||||
|
||||
// SettingsImportRequest is a request to import settings.
|
||||
type SettingsImportRequest struct {
|
||||
ImportRequest `json:",inline"`
|
||||
|
||||
// Reset all settings of target before import.
|
||||
// The ImportResult also reacts to this flag and correctly reports whether
|
||||
// any settings would be replaced or deleted.
|
||||
Reset bool `json:"reset"`
|
||||
|
||||
// AllowUnknown allows the import of unknown settings.
|
||||
// Otherwise, attempting to import an unknown setting will result in an error.
|
||||
AllowUnknown bool `json:"allowUnknown"`
|
||||
|
||||
Export *SettingsExport `json:"export"`
|
||||
}
|
||||
|
||||
func registerSettingsAPI() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Export Settings",
|
||||
Description: "Exports settings in a share-able format.",
|
||||
Path: "sync/settings/export",
|
||||
Read: api.PermitAdmin,
|
||||
Write: api.PermitAdmin,
|
||||
Parameters: []api.Parameter{{
|
||||
Method: http.MethodGet,
|
||||
Field: "from",
|
||||
Description: "Specify where to export from.",
|
||||
}, {
|
||||
Method: http.MethodGet,
|
||||
Field: "key",
|
||||
Description: "Optionally select a single setting to export. Repeat to export selection.",
|
||||
}},
|
||||
BelongsTo: module,
|
||||
DataFunc: handleExportSettings,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Import Settings",
|
||||
Description: "Imports settings from the share-able format.",
|
||||
Path: "sync/settings/import",
|
||||
Read: api.PermitAdmin,
|
||||
Write: api.PermitAdmin,
|
||||
Parameters: []api.Parameter{{
|
||||
Method: http.MethodPost,
|
||||
Field: "to",
|
||||
Description: "Specify where to import to.",
|
||||
}, {
|
||||
Method: http.MethodPost,
|
||||
Field: "validate",
|
||||
Description: "Validate only.",
|
||||
}, {
|
||||
Method: http.MethodPost,
|
||||
Field: "reset",
|
||||
Description: "Replace all existing settings.",
|
||||
}, {
|
||||
Method: http.MethodPost,
|
||||
Field: "allowUnknown",
|
||||
Description: "Allow importing of unknown values.",
|
||||
}},
|
||||
BelongsTo: module,
|
||||
StructFunc: handleImportSettings,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleExportSettings(ar *api.Request) (data []byte, err error) {
|
||||
var request *ExportRequest
|
||||
|
||||
// Get parameters.
|
||||
q := ar.URL.Query()
|
||||
if len(q) > 0 {
|
||||
request = &ExportRequest{
|
||||
From: q.Get("from"),
|
||||
Keys: q["key"], // Get []string by direct map access.
|
||||
}
|
||||
} else {
|
||||
request = &ExportRequest{}
|
||||
if err := json.Unmarshal(ar.InputData, request); err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to parse export request: %w", ErrExportFailed, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check parameters.
|
||||
if request.From == "" {
|
||||
return nil, errors.New("missing parameters")
|
||||
}
|
||||
|
||||
// Export.
|
||||
export, err := ExportSettings(request.From, request.Keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serializeExport(export, ar)
|
||||
}
|
||||
|
||||
func handleImportSettings(ar *api.Request) (any, error) {
|
||||
var request *SettingsImportRequest
|
||||
|
||||
// Get parameters.
|
||||
q := ar.URL.Query()
|
||||
if len(q) > 0 {
|
||||
request = &SettingsImportRequest{
|
||||
ImportRequest: ImportRequest{
|
||||
Target: q.Get("to"),
|
||||
ValidateOnly: q.Has("validate"),
|
||||
RawExport: string(ar.InputData),
|
||||
RawMime: ar.Header.Get("Content-Type"),
|
||||
},
|
||||
Reset: q.Has("reset"),
|
||||
AllowUnknown: q.Has("allowUnknown"),
|
||||
}
|
||||
} else {
|
||||
request = &SettingsImportRequest{}
|
||||
if err := json.Unmarshal(ar.InputData, request); err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to parse import request: %w", ErrInvalidImportRequest, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need to parse the export.
|
||||
switch {
|
||||
case request.Export != nil && request.RawExport != "":
|
||||
return nil, fmt.Errorf("%w: both Export and RawExport are defined", ErrInvalidImportRequest)
|
||||
case request.RawExport != "":
|
||||
// Parse export.
|
||||
export := &SettingsExport{}
|
||||
if err := parseExport(&request.ImportRequest, export); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
request.Export = export
|
||||
case request.Export != nil:
|
||||
// Export is aleady parsed.
|
||||
default:
|
||||
return nil, ErrInvalidImportRequest
|
||||
}
|
||||
|
||||
// Import.
|
||||
return ImportSettings(request)
|
||||
}
|
||||
|
||||
// ExportSettings exports the global settings.
|
||||
func ExportSettings(from string, keys []string) (*SettingsExport, error) {
|
||||
var settings map[string]any
|
||||
if from == ExportTargetGlobal {
|
||||
// Collect all changed global settings.
|
||||
settings = make(map[string]any)
|
||||
_ = config.ForEachOption(func(option *config.Option) error {
|
||||
v := option.UserValue()
|
||||
if v != nil {
|
||||
settings[option.Key] = v
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
r, err := db.Get(profile.ProfilesDBPath + from)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to find profile: %w", ErrTargetNotFound, err)
|
||||
}
|
||||
p, err := profile.EnsureProfile(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to load profile: %w", ErrExportFailed, err)
|
||||
}
|
||||
settings = config.Flatten(p.Config)
|
||||
}
|
||||
|
||||
// Only extract some setting keys, if wanted.
|
||||
if len(keys) > 0 {
|
||||
selection := make(map[string]any, len(keys))
|
||||
for _, key := range keys {
|
||||
if v, ok := settings[key]; ok {
|
||||
selection[key] = v
|
||||
}
|
||||
}
|
||||
settings = selection
|
||||
}
|
||||
|
||||
// Check if there any changed settings.
|
||||
if len(settings) == 0 {
|
||||
return nil, ErrUnchanged
|
||||
}
|
||||
|
||||
// Expand config to hierarchical form.
|
||||
settings = config.Expand(settings)
|
||||
|
||||
return &SettingsExport{
|
||||
Type: TypeSettings,
|
||||
Config: settings,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ImportSettings imports the global settings.
|
||||
func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) {
|
||||
// Check import.
|
||||
if r.Export.Type != TypeSettings {
|
||||
return nil, ErrMismatch
|
||||
}
|
||||
|
||||
// Flatten config.
|
||||
settings := config.Flatten(r.Export.Config)
|
||||
|
||||
// Validate config and gather some metadata.
|
||||
var (
|
||||
result = &ImportResult{}
|
||||
checked int
|
||||
globalOnlySettingFound bool
|
||||
)
|
||||
err := config.ForEachOption(func(option *config.Option) error {
|
||||
// Check if any setting is set.
|
||||
if r.Reset && option.IsSetByUser() {
|
||||
result.ReplacesExisting = true
|
||||
}
|
||||
|
||||
newValue, ok := settings[option.Key]
|
||||
if ok {
|
||||
checked++
|
||||
|
||||
// Validate the new value.
|
||||
if err := option.ValidateValue(newValue); err != nil {
|
||||
return fmt.Errorf("%w: configuration value for %s is invalid: %w", ErrInvalidSettingValue, option.Key, err)
|
||||
}
|
||||
|
||||
// Collect metadata.
|
||||
if option.RequiresRestart {
|
||||
result.RestartRequired = true
|
||||
}
|
||||
if !r.Reset && option.IsSetByUser() {
|
||||
result.ReplacesExisting = true
|
||||
}
|
||||
if !option.AnnotationEquals(config.SettablePerAppAnnotation, true) {
|
||||
globalOnlySettingFound = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if checked < len(settings) {
|
||||
result.ContainsUnknown = true
|
||||
if !r.AllowUnknown && !r.ValidateOnly {
|
||||
return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImportRequest)
|
||||
}
|
||||
}
|
||||
|
||||
// Import global settings.
|
||||
if r.Target == ExportTargetGlobal {
|
||||
// Stop here if we are only validating.
|
||||
if r.ValidateOnly {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Import to global config.
|
||||
vErrs, restartRequired := config.ReplaceConfig(settings)
|
||||
if len(vErrs) > 0 {
|
||||
s := make([]string, 0, len(vErrs))
|
||||
for _, err := range vErrs {
|
||||
s = append(s, err.Error())
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"%w: the supplied configuration could not be applied:\n%s",
|
||||
ErrImportFailed,
|
||||
strings.Join(s, "\n"),
|
||||
)
|
||||
}
|
||||
|
||||
result.RestartRequired = restartRequired
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Check if a setting is settable per app.
|
||||
if globalOnlySettingFound {
|
||||
return nil, fmt.Errorf("%w: export contains settings that cannot be set per app", ErrNotSettablePerApp)
|
||||
}
|
||||
|
||||
// Get and load profile.
|
||||
rec, err := db.Get(profile.ProfilesDBPath + r.Target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to find profile: %w", ErrTargetNotFound, err)
|
||||
}
|
||||
p, err := profile.EnsureProfile(rec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to load profile: %w", ErrImportFailed, err)
|
||||
}
|
||||
|
||||
// Stop here if we are only validating.
|
||||
if r.ValidateOnly {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Import settings into profile.
|
||||
if r.Reset {
|
||||
p.Config = config.Expand(settings)
|
||||
} else {
|
||||
for k, v := range settings {
|
||||
config.PutValueIntoHierarchicalConfig(p.Config, k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark profile as edited by user.
|
||||
p.LastEdited = time.Now().Unix()
|
||||
|
||||
// Save profile back to db.
|
||||
err = p.Save()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: failed to save profile: %w", ErrImportFailed, err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
148
sync/util.go
Normal file
148
sync/util.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/safing/jess/filesig"
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/formats/dsd"
|
||||
)
|
||||
|
||||
// Type is the type of an export.
|
||||
type Type string
|
||||
|
||||
// Export Types.
|
||||
const (
|
||||
TypeProfile = "profile"
|
||||
TypeSettings = "settings"
|
||||
TypeSingleSetting = "single-setting"
|
||||
)
|
||||
|
||||
// Export IDs.
|
||||
const (
|
||||
ExportTargetGlobal = "global"
|
||||
)
|
||||
|
||||
// Messages.
|
||||
var (
|
||||
MsgNone = ""
|
||||
MsgValid = "Import is valid."
|
||||
MsgSuccess = "Import successful."
|
||||
MsgRequireRestart = "Import successful. Restart required for setting to take effect."
|
||||
)
|
||||
|
||||
// ExportRequest is a request for an export.
|
||||
type ExportRequest struct {
|
||||
From string `json:"from"`
|
||||
Keys []string `json:"keys"`
|
||||
}
|
||||
|
||||
// ImportRequest is a request to import an export.
|
||||
type ImportRequest struct {
|
||||
// Where the export should be import to.
|
||||
Target string `json:"target"`
|
||||
// Only validate, but do not actually change anything.
|
||||
ValidateOnly bool `json:"validateOnly"`
|
||||
|
||||
RawExport string `json:"rawExport"`
|
||||
RawMime string `json:"rawMime"`
|
||||
}
|
||||
|
||||
// ImportResult is returned by successful import operations.
|
||||
type ImportResult struct {
|
||||
RestartRequired bool `json:"restartRequired"`
|
||||
ReplacesExisting bool `json:"replacesExisting"`
|
||||
ContainsUnknown bool `json:"containsUnknown"`
|
||||
}
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
ErrMismatch = api.ErrorWithStatus(
|
||||
errors.New("the supplied export cannot be imported here"),
|
||||
http.StatusPreconditionFailed,
|
||||
)
|
||||
ErrSettingNotFound = api.ErrorWithStatus(
|
||||
errors.New("setting not found"),
|
||||
http.StatusPreconditionFailed,
|
||||
)
|
||||
ErrTargetNotFound = api.ErrorWithStatus(
|
||||
errors.New("import/export target does not exist"),
|
||||
http.StatusGone,
|
||||
)
|
||||
ErrUnchanged = api.ErrorWithStatus(
|
||||
errors.New("cannot export unchanged setting"),
|
||||
http.StatusGone,
|
||||
)
|
||||
ErrNotSettablePerApp = api.ErrorWithStatus(
|
||||
errors.New("cannot be set per app"),
|
||||
http.StatusGone,
|
||||
)
|
||||
ErrInvalidImportRequest = api.ErrorWithStatus(
|
||||
errors.New("invalid import request"),
|
||||
http.StatusUnprocessableEntity,
|
||||
)
|
||||
ErrInvalidSettingValue = api.ErrorWithStatus(
|
||||
errors.New("invalid setting value"),
|
||||
http.StatusUnprocessableEntity,
|
||||
)
|
||||
ErrInvalidProfileData = api.ErrorWithStatus(
|
||||
errors.New("invalid profile data"),
|
||||
http.StatusUnprocessableEntity,
|
||||
)
|
||||
ErrImportFailed = api.ErrorWithStatus(
|
||||
errors.New("import failed"),
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
ErrExportFailed = api.ErrorWithStatus(
|
||||
errors.New("export failed"),
|
||||
http.StatusInternalServerError,
|
||||
)
|
||||
)
|
||||
|
||||
func serializeExport(export any, ar *api.Request) ([]byte, error) {
|
||||
// Serialize data.
|
||||
data, mimeType, format, err := dsd.MimeDump(export, ar.Header.Get("Accept"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize data: %w", err)
|
||||
}
|
||||
ar.ResponseHeader.Set("Content-Type", mimeType)
|
||||
|
||||
// Add checksum.
|
||||
switch format {
|
||||
case dsd.JSON:
|
||||
data, err = filesig.AddJSONChecksum(data)
|
||||
case dsd.YAML:
|
||||
data, err = filesig.AddYAMLChecksum(data, filesig.TextPlacementBottom)
|
||||
default:
|
||||
return nil, dsd.ErrIncompatibleFormat
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add checksum: %w", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func parseExport(request *ImportRequest, export any) error {
|
||||
format, err := dsd.MimeLoad([]byte(request.RawExport), request.RawMime, export)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: failed to parse export: %w", ErrInvalidImportRequest, err)
|
||||
}
|
||||
|
||||
// Verify checksum, if available.
|
||||
switch format {
|
||||
case dsd.JSON:
|
||||
err = filesig.VerifyJSONChecksum([]byte(request.RawExport))
|
||||
case dsd.YAML:
|
||||
err = filesig.VerifyYAMLChecksum([]byte(request.RawExport))
|
||||
default:
|
||||
// Checksums not supported.
|
||||
}
|
||||
if err != nil && !errors.Is(err, filesig.ErrChecksumMissing) {
|
||||
return fmt.Errorf("failed to verify checksum: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func registerAPIEndpoints() error {
|
||||
|
||||
// If we could not convert to acceptable format, return an error.
|
||||
if !converted {
|
||||
http.Error(w, err.Error(), http.StatusNotAcceptable)
|
||||
http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ func SetIndexes(
|
||||
Path: indexPath,
|
||||
AutoDownload: autoDownload,
|
||||
})
|
||||
usePreReleases = true
|
||||
} else if deleteUnusedIndexes {
|
||||
err := deleteIndex(registry, indexPath)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user