From cf3bd9f67107f87a4596f1dbb3cd0e751ded0522 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 19 Sep 2023 16:10:10 +0200 Subject: [PATCH 01/18] Add first set of import/export APIs --- core/core.go | 3 +- profile/database.go | 10 +- profile/get.go | 8 +- profile/migrations.go | 8 +- sync/module.go | 114 +++++++++++++++ sync/profile.go | 45 ++++++ sync/setting_single.go | 264 +++++++++++++++++++++++++++++++++++ sync/settings.go | 305 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 743 insertions(+), 14 deletions(-) create mode 100644 sync/module.go create mode 100644 sync/profile.go create mode 100644 sync/setting_single.go create mode 100644 sync/settings.go diff --git a/core/core.go b/core/core.go index a9462fdf..1637e47c 100644 --- a/core/core.go +++ b/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", diff --git a/profile/database.go b/profile/database.go index c59235fd..49ecb3c1 100644 --- a/profile/database.go +++ b/profile/database.go @@ -16,7 +16,7 @@ import ( // core:profiles// // cache:profiles/index// -const profilesDBPath = "core:profiles/" +const ProfilesDBPath = "core:profiles/" var profileDB = database.NewInterface(&database.Options{ Local: true, @@ -28,17 +28,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,7 +59,7 @@ func startProfileUpdateChecker() error { } // Get active profile. - activeProfile := getActiveProfile(strings.TrimPrefix(r.Key(), profilesDBPath)) + activeProfile := getActiveProfile(strings.TrimPrefix(r.Key(), ProfilesDBPath)) if activeProfile == nil { // Don't do any additional actions if the profile is not active. continue profileFeed diff --git a/profile/get.go b/profile/get.go index 7ed686e8..3a705b4a 100644 --- a/profile/get.go +++ b/profile/get.go @@ -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) } @@ -299,7 +299,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 +307,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) } diff --git a/profile/migrations.go b/profile/migrations.go index fa19a8cc..72cd1133 100644 --- a/profile/migrations.go +++ b/profile/migrations.go @@ -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) } } diff --git a/sync/module.go b/sync/module.go new file mode 100644 index 00000000..2bc5d268 --- /dev/null +++ b/sync/module.go @@ -0,0 +1,114 @@ +package sync + +import ( + "errors" + "net/http" + + "github.com/safing/portbase/api" + "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 +} + +// 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"` + Key string `json:"key"` +} + +// 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:"validate_only"` + + RawExport string `json:"raw_export"` +} + +// ImportResult is returned by successful import operations. +type ImportResult struct { + RestartRequired bool `json:"restart_required"` + ReplacesExisting bool `json:"replaces_existing"` +} + +// Errors. +var ( + ErrMismatch = api.ErrorWithStatus( + errors.New("the supplied export cannot be imported here"), + 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, + ) + ErrInvalidImport = api.ErrorWithStatus( + errors.New("invalid import"), + http.StatusUnprocessableEntity, + ) + ErrInvalidSetting = api.ErrorWithStatus( + errors.New("invalid setting"), + http.StatusUnprocessableEntity, + ) + ErrInvalidProfile = api.ErrorWithStatus( + errors.New("invalid profile"), + http.StatusUnprocessableEntity, + ) + ErrImportFailed = api.ErrorWithStatus( + errors.New("import failed"), + http.StatusInternalServerError, + ) + ErrExportFailed = api.ErrorWithStatus( + errors.New("export failed"), + http.StatusInternalServerError, + ) +) diff --git a/sync/profile.go b/sync/profile.go new file mode 100644 index 00000000..5e7b66c5 --- /dev/null +++ b/sync/profile.go @@ -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 +} diff --git a/sync/setting_single.go b/sync/setting_single.go new file mode 100644 index 00000000..67a171b0 --- /dev/null +++ b/sync/setting_single.go @@ -0,0 +1,264 @@ +package sync + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/ghodss/yaml" + + "github.com/safing/portbase/api" + "github.com/safing/portbase/config" + "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"), + Key: q.Get("key"), + } + } else { + request = &ExportRequest{} + if err := json.Unmarshal(ar.InputData, request); err != nil { + return nil, fmt.Errorf("%w: failed to parse export request: %s", ErrExportFailed, err) + } + } + + // Check parameters. + if request.From == "" || request.Key == "" { + return nil, errors.New("missing parameters") + } + + // Export. + export, err := ExportSingleSetting(request.Key, request.From) + if err != nil { + return nil, err + } + + // Make some yummy yaml. + yamlData, err := yaml.Marshal(export) + if err != nil { + return nil, fmt.Errorf("%w: failed to marshal to yaml: %s", ErrExportFailed, err) + } + + // TODO: Add checksum for integrity. + + return yamlData, nil +} + +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), + }, + } + } else { + request = &SingleSettingImportRequest{} + if err := json.Unmarshal(ar.InputData, request); err != nil { + return nil, fmt.Errorf("%w: failed to parse import request: %s", ErrInvalidImport, 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", ErrInvalidImport) + case request.RawExport != "": + // TODO: Verify checksum for integrity. + + export := &SingleSettingExport{} + if err := yaml.Unmarshal([]byte(request.RawExport), export); err != nil { + return nil, fmt.Errorf("%w: failed to parse export: %s", ErrInvalidImport, err) + } + request.Export = export + } + + // Optional check if the setting key matches. + if 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) { + var value any + if from == ExportTargetGlobal { + option, err := config.GetOption(key) + if err != nil { + return nil, fmt.Errorf("%w: configuration %s", ErrTargetNotFound, err) + } + value = option.UserValue() + if value == nil { + return nil, ErrUnchanged + } + } else { + r, err := db.Get(profile.ProfilesDBPath + from) + if err != nil { + return nil, fmt.Errorf("%w: failed to find profile: %s", ErrTargetNotFound, err) + } + p, err := profile.EnsureProfile(r) + if err != nil { + return nil, fmt.Errorf("%w: failed to load profile: %s", ErrExportFailed, err) + } + 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 %s", ErrTargetNotFound, err) + } + if option.ValidateValue(r.Export.Value) != nil { + return nil, fmt.Errorf("%w: configuration value is invalid: %s", ErrInvalidSetting, 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. + err = config.SetConfigOption(r.Export.ID, r.Export.Value) + if err != nil { + return nil, fmt.Errorf("%w: configuration value is invalid: %s", ErrInvalidSetting, err) + } + } else { + // 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: %s", ErrTargetNotFound, err) + } + p, err := profile.EnsureProfile(rec) + if err != nil { + return nil, fmt.Errorf("%w: failed to load profile: %s", 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. + flattened := config.Flatten(p.Config) + flattened[r.Export.ID] = r.Export.Value + p.Config = config.Expand(flattened) + + // Save profile back to db. + err = p.Save() + if err != nil { + return nil, fmt.Errorf("%w: failed to save profile: %s", ErrImportFailed, err) + } + } + + return &ImportResult{ + RestartRequired: option.RequiresRestart, + ReplacesExisting: option.IsSetByUser(), + }, nil +} diff --git a/sync/settings.go b/sync/settings.go new file mode 100644 index 00000000..57160174 --- /dev/null +++ b/sync/settings.go @@ -0,0 +1,305 @@ +package sync + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/ghodss/yaml" + + "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"` + + 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.", + }}, + 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.", + }}, + 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"), + } + } else { + request = &ExportRequest{} + if err := json.Unmarshal(ar.InputData, request); err != nil { + return nil, fmt.Errorf("%w: failed to parse export request: %s", ErrExportFailed, err) + } + } + + // Check parameters. + if request.From == "" { + return nil, errors.New("missing parameters") + } + + // Export. + export, err := ExportSettings(request.From) + if err != nil { + return nil, err + } + + // Make some yummy yaml. + yamlData, err := yaml.Marshal(export) + if err != nil { + return nil, fmt.Errorf("%w: failed to marshal to yaml: %s", ErrExportFailed, err) + } + + // TODO: Add checksum for integrity. + + return yamlData, nil +} + +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), + }, + Reset: q.Has("reset"), + } + } else { + request = &SettingsImportRequest{} + if err := json.Unmarshal(ar.InputData, request); err != nil { + return nil, fmt.Errorf("%w: failed to parse import request: %s", ErrInvalidImport, 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", ErrInvalidImport) + case request.RawExport != "": + // TODO: Verify checksum for integrity. + + export := &SettingsExport{} + if err := yaml.Unmarshal([]byte(request.RawExport), export); err != nil { + return nil, fmt.Errorf("%w: failed to parse export: %s", ErrInvalidImport, err) + } + request.Export = export + } + + // Import. + return ImportSettings(request) +} + +// ExportSettings exports the global settings. +func ExportSettings(from 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: %s", ErrTargetNotFound, err) + } + p, err := profile.EnsureProfile(r) + if err != nil { + return nil, fmt.Errorf("%w: failed to load profile: %s", ErrExportFailed, err) + } + settings = config.Flatten(p.Config) + } + + // 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 + ) + 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: %s", ErrInvalidSetting, option.Key, err) + } + + // Collect metadata. + if option.RequiresRestart { + result.RestartRequired = true + } + if !r.Reset && option.IsSetByUser() { + result.ReplacesExisting = true + } + } + return nil + }) + if err != nil { + return nil, err + } + if checked < len(settings) { + return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImport) + } + + // 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 + } + + // Import settings into profile. + rec, err := db.Get(profile.ProfilesDBPath + r.Target) + if err != nil { + return nil, fmt.Errorf("%w: failed to find profile: %s", ErrTargetNotFound, err) + } + p, err := profile.EnsureProfile(rec) + if err != nil { + return nil, fmt.Errorf("%w: failed to load profile: %s", ErrImportFailed, err) + } + + // FIXME: check if there are any global-only setting in the import + + // 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 { + flattenedProfileConfig := config.Flatten(p.Config) + for k, v := range settings { + flattenedProfileConfig[k] = v + } + p.Config = config.Expand(flattenedProfileConfig) + } + + // Save profile back to db. + err = p.Save() + if err != nil { + return nil, fmt.Errorf("%w: failed to save profile: %s", ErrImportFailed, err) + } + + return result, nil +} From 1316a5606f95887e06049888c982b729a74eb7a0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Sep 2023 12:50:51 +0200 Subject: [PATCH 02/18] Us Pre-Releases in support channel --- updates/helper/indexes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/updates/helper/indexes.go b/updates/helper/indexes.go index 948ab33a..7af8a610 100644 --- a/updates/helper/indexes.go +++ b/updates/helper/indexes.go @@ -98,6 +98,7 @@ func SetIndexes( Path: indexPath, AutoDownload: autoDownload, }) + usePreReleases = true } else if deleteUnusedIndexes { err := deleteIndex(registry, indexPath) if err != nil { From a3457637e8c6fbdde576896dbd9114026d590286 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Sep 2023 12:51:03 +0200 Subject: [PATCH 03/18] Always scan for pre-releases --- cmds/updatemgr/scan.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/updatemgr/scan.go b/cmds/updatemgr/scan.go index ff9e0dce..674a581b 100644 --- a/cmds/updatemgr/scan.go +++ b/cmds/updatemgr/scan.go @@ -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 } From 9d6c7b400ba15dc4a47052634deb4b13a118bdef Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Sep 2023 12:51:25 +0200 Subject: [PATCH 04/18] Add per-app setting annotations --- profile/config.go | 117 +++++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 47 deletions(-) diff --git a/profile/config.go b/profile/config.go index 4c748cbf..d65f10bd 100644 --- a/profile/config.go +++ b/profile/config.go @@ -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{ { From 32342ec91a8c461f3cef99982cad3aa4844610ff Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 26 Sep 2023 12:55:23 +0200 Subject: [PATCH 05/18] Add support for mime types and checksums to import/export --- sync/module.go | 85 ------------------------ sync/setting_single.go | 82 ++++++++++++----------- sync/settings.go | 65 +++++++++--------- sync/util.go | 147 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+), 158 deletions(-) create mode 100644 sync/util.go diff --git a/sync/module.go b/sync/module.go index 2bc5d268..bdecf442 100644 --- a/sync/module.go +++ b/sync/module.go @@ -1,10 +1,6 @@ package sync import ( - "errors" - "net/http" - - "github.com/safing/portbase/api" "github.com/safing/portbase/database" "github.com/safing/portbase/modules" ) @@ -31,84 +27,3 @@ func prep() error { } return nil } - -// 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"` - Key string `json:"key"` -} - -// 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:"validate_only"` - - RawExport string `json:"raw_export"` -} - -// ImportResult is returned by successful import operations. -type ImportResult struct { - RestartRequired bool `json:"restart_required"` - ReplacesExisting bool `json:"replaces_existing"` -} - -// Errors. -var ( - ErrMismatch = api.ErrorWithStatus( - errors.New("the supplied export cannot be imported here"), - 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, - ) - ErrInvalidImport = api.ErrorWithStatus( - errors.New("invalid import"), - http.StatusUnprocessableEntity, - ) - ErrInvalidSetting = api.ErrorWithStatus( - errors.New("invalid setting"), - http.StatusUnprocessableEntity, - ) - ErrInvalidProfile = api.ErrorWithStatus( - errors.New("invalid profile"), - http.StatusUnprocessableEntity, - ) - ErrImportFailed = api.ErrorWithStatus( - errors.New("import failed"), - http.StatusInternalServerError, - ) - ErrExportFailed = api.ErrorWithStatus( - errors.New("export failed"), - http.StatusInternalServerError, - ) -) diff --git a/sync/setting_single.go b/sync/setting_single.go index 67a171b0..0d2f0d64 100644 --- a/sync/setting_single.go +++ b/sync/setting_single.go @@ -6,10 +6,9 @@ import ( "fmt" "net/http" - "github.com/ghodss/yaml" - "github.com/safing/portbase/api" "github.com/safing/portbase/config" + "github.com/safing/portbase/formats/dsd" "github.com/safing/portmaster/profile" ) @@ -91,7 +90,7 @@ func handleExportSingleSetting(ar *api.Request) (data []byte, err error) { } else { request = &ExportRequest{} if err := json.Unmarshal(ar.InputData, request); err != nil { - return nil, fmt.Errorf("%w: failed to parse export request: %s", ErrExportFailed, err) + return nil, fmt.Errorf("%w: failed to parse export request: %w", ErrExportFailed, err) } } @@ -106,15 +105,7 @@ func handleExportSingleSetting(ar *api.Request) (data []byte, err error) { return nil, err } - // Make some yummy yaml. - yamlData, err := yaml.Marshal(export) - if err != nil { - return nil, fmt.Errorf("%w: failed to marshal to yaml: %s", ErrExportFailed, err) - } - - // TODO: Add checksum for integrity. - - return yamlData, nil + return serializeExport(export, ar) } func handleImportSingleSetting(ar *api.Request) (any, error) { @@ -128,31 +119,35 @@ func handleImportSingleSetting(ar *api.Request) (any, error) { Target: q.Get("to"), ValidateOnly: q.Has("validate"), RawExport: string(ar.InputData), + RawMime: ar.Header.Get("Content-Type"), }, } } else { request = &SingleSettingImportRequest{} - if err := json.Unmarshal(ar.InputData, request); err != nil { - return nil, fmt.Errorf("%w: failed to parse import request: %s", ErrInvalidImport, err) + 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", ErrInvalidImport) + return nil, fmt.Errorf("%w: both Export and RawExport are defined", ErrInvalidImportRequest) case request.RawExport != "": - // TODO: Verify checksum for integrity. - + // Parse export. export := &SingleSettingExport{} - if err := yaml.Unmarshal([]byte(request.RawExport), export); err != nil { - return nil, fmt.Errorf("%w: failed to parse export: %s", ErrInvalidImport, err) + 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 q.Has("key") && q.Get("key") != request.Export.ID { + if len(q) > 0 && q.Has("key") && q.Get("key") != request.Export.ID { return nil, ErrMismatch } @@ -162,25 +157,32 @@ func handleImportSingleSetting(ar *api.Request) (any, error) { // 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 { - option, err := config.GetOption(key) - if err != nil { - return nil, fmt.Errorf("%w: configuration %s", ErrTargetNotFound, err) - } 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: %s", ErrTargetNotFound, err) + 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: %s", ErrExportFailed, err) + 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 { @@ -205,10 +207,10 @@ func ImportSingeSetting(r *SingleSettingImportRequest) (*ImportResult, error) { // Get option and validate value. option, err := config.GetOption(r.Export.ID) if err != nil { - return nil, fmt.Errorf("%w: configuration %s", ErrTargetNotFound, err) + return nil, fmt.Errorf("%w: configuration %w", ErrSettingNotFound, err) } - if option.ValidateValue(r.Export.Value) != nil { - return nil, fmt.Errorf("%w: configuration value is invalid: %s", ErrInvalidSetting, err) + if err := option.ValidateValue(r.Export.Value); err != nil { + return nil, fmt.Errorf("%w: %w", ErrInvalidSettingValue, err) } // Import single global setting. @@ -222,19 +224,22 @@ func ImportSingeSetting(r *SingleSettingImportRequest) (*ImportResult, error) { } // Actually import the setting. - err = config.SetConfigOption(r.Export.ID, r.Export.Value) - if err != nil { - return nil, fmt.Errorf("%w: configuration value is invalid: %s", ErrInvalidSetting, err) + 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: %s", ErrTargetNotFound, err) + 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: %s", ErrImportFailed, err) + return nil, fmt.Errorf("%w: failed to load profile: %w", ErrImportFailed, err) } // Stop here if we are only validating. @@ -246,14 +251,11 @@ func ImportSingeSetting(r *SingleSettingImportRequest) (*ImportResult, error) { } // Set imported setting on profile. - flattened := config.Flatten(p.Config) - flattened[r.Export.ID] = r.Export.Value - p.Config = config.Expand(flattened) + config.PutValueIntoHierarchicalConfig(p.Config, r.Export.ID, r.Export.Value) // Save profile back to db. - err = p.Save() - if err != nil { - return nil, fmt.Errorf("%w: failed to save profile: %s", ErrImportFailed, err) + if err := p.Save(); err != nil { + return nil, fmt.Errorf("%w: failed to save profile: %w", ErrImportFailed, err) } } diff --git a/sync/settings.go b/sync/settings.go index 57160174..e6f789cd 100644 --- a/sync/settings.go +++ b/sync/settings.go @@ -7,8 +7,6 @@ import ( "net/http" "strings" - "github.com/ghodss/yaml" - "github.com/safing/portbase/api" "github.com/safing/portbase/config" "github.com/safing/portmaster/profile" @@ -91,7 +89,7 @@ func handleExportSettings(ar *api.Request) (data []byte, err error) { } else { request = &ExportRequest{} if err := json.Unmarshal(ar.InputData, request); err != nil { - return nil, fmt.Errorf("%w: failed to parse export request: %s", ErrExportFailed, err) + return nil, fmt.Errorf("%w: failed to parse export request: %w", ErrExportFailed, err) } } @@ -106,15 +104,7 @@ func handleExportSettings(ar *api.Request) (data []byte, err error) { return nil, err } - // Make some yummy yaml. - yamlData, err := yaml.Marshal(export) - if err != nil { - return nil, fmt.Errorf("%w: failed to marshal to yaml: %s", ErrExportFailed, err) - } - - // TODO: Add checksum for integrity. - - return yamlData, nil + return serializeExport(export, ar) } func handleImportSettings(ar *api.Request) (any, error) { @@ -128,28 +118,32 @@ func handleImportSettings(ar *api.Request) (any, error) { Target: q.Get("to"), ValidateOnly: q.Has("validate"), RawExport: string(ar.InputData), + RawMime: ar.Header.Get("Content-Type"), }, Reset: q.Has("reset"), } } else { request = &SettingsImportRequest{} if err := json.Unmarshal(ar.InputData, request); err != nil { - return nil, fmt.Errorf("%w: failed to parse import request: %s", ErrInvalidImport, err) + 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", ErrInvalidImport) + return nil, fmt.Errorf("%w: both Export and RawExport are defined", ErrInvalidImportRequest) case request.RawExport != "": - // TODO: Verify checksum for integrity. - + // Parse export. export := &SettingsExport{} - if err := yaml.Unmarshal([]byte(request.RawExport), export); err != nil { - return nil, fmt.Errorf("%w: failed to parse export: %s", ErrInvalidImport, err) + 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. @@ -172,11 +166,11 @@ func ExportSettings(from string) (*SettingsExport, error) { } else { r, err := db.Get(profile.ProfilesDBPath + from) if err != nil { - return nil, fmt.Errorf("%w: failed to find profile: %s", ErrTargetNotFound, err) + 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: %s", ErrExportFailed, err) + return nil, fmt.Errorf("%w: failed to load profile: %w", ErrExportFailed, err) } settings = config.Flatten(p.Config) } @@ -207,8 +201,9 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { // Validate config and gather some metadata. var ( - result = &ImportResult{} - checked int + result = &ImportResult{} + checked int + globalOnlySettingFound bool ) err := config.ForEachOption(func(option *config.Option) error { // Check if any setting is set. @@ -222,7 +217,7 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { // Validate the new value. if err := option.ValidateValue(newValue); err != nil { - return fmt.Errorf("%w: configuration value for %s is invalid: %s", ErrInvalidSetting, option.Key, err) + return fmt.Errorf("%w: configuration value for %s is invalid: %w", ErrInvalidSettingValue, option.Key, err) } // Collect metadata. @@ -232,6 +227,9 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { if !r.Reset && option.IsSetByUser() { result.ReplacesExisting = true } + if !option.AnnotationEquals(config.SettablePerAppAnnotation, true) { + globalOnlySettingFound = true + } } return nil }) @@ -239,7 +237,7 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { return nil, err } if checked < len(settings) { - return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImport) + return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImportRequest) } // Import global settings. @@ -267,18 +265,21 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { return result, nil } - // Import settings into profile. + // 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: %s", ErrTargetNotFound, err) + 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: %s", ErrImportFailed, err) + return nil, fmt.Errorf("%w: failed to load profile: %w", ErrImportFailed, err) } - // FIXME: check if there are any global-only setting in the import - // Stop here if we are only validating. if r.ValidateOnly { return result, nil @@ -288,17 +289,15 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { if r.Reset { p.Config = config.Expand(settings) } else { - flattenedProfileConfig := config.Flatten(p.Config) for k, v := range settings { - flattenedProfileConfig[k] = v + config.PutValueIntoHierarchicalConfig(p.Config, k, v) } - p.Config = config.Expand(flattenedProfileConfig) } // Save profile back to db. err = p.Save() if err != nil { - return nil, fmt.Errorf("%w: failed to save profile: %s", ErrImportFailed, err) + return nil, fmt.Errorf("%w: failed to save profile: %w", ErrImportFailed, err) } return result, nil diff --git a/sync/util.go b/sync/util.go new file mode 100644 index 00000000..5d9a6154 --- /dev/null +++ b/sync/util.go @@ -0,0 +1,147 @@ +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"` + Key string `json:"key"` +} + +// 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:"validate_only"` + + RawExport string `json:"raw_export"` + RawMime string `json:"raw_mime"` +} + +// ImportResult is returned by successful import operations. +type ImportResult struct { + RestartRequired bool `json:"restart_required"` + ReplacesExisting bool `json:"replaces_existing"` +} + +// 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.TextPlacementTop) + 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 +} From bed5c72a6b6faf80f4d74b034a91018e84e7e36f Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 27 Sep 2023 14:23:02 +0200 Subject: [PATCH 06/18] Finalize profile merging, add profile metadata state handling, re-attribute connections after profile deletion --- firewall/module.go | 57 ++++++++--- firewall/packet_handler.go | 150 +++++++++++++++++---------- network/connection.go | 5 + network/module.go | 103 +++++++++++++++++++ process/profile.go | 18 ++++ profile/api.go | 66 ++++++++++++ profile/database.go | 20 +++- profile/endpoints/endpoint.go | 3 +- profile/get.go | 3 + profile/merge.go | 54 ++++++---- profile/meta.go | 184 ++++++++++++++++++++++++++++++++++ profile/migrations.go | 10 +- profile/module.go | 27 ++++- profile/profile.go | 18 ++++ 14 files changed, 622 insertions(+), 96 deletions(-) create mode 100644 profile/api.go create mode 100644 profile/meta.go diff --git a/firewall/module.go b/firewall/module.go index 999c3888..de6ca88a 100644 --- a/firewall/module.go +++ b/firewall/module.go @@ -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 } diff --git a/firewall/packet_handler.go b/firewall/packet_handler.go index e30d68aa..5b092495 100644 --- a/firewall/packet_handler.go +++ b/firewall/packet_handler.go @@ -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 diff --git a/network/connection.go b/network/connection.go index ab3ec3fb..55fc6a3d 100644 --- a/network/connection.go +++ b/network/connection.go @@ -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) { diff --git a/network/module.go b/network/module.go index 1a7ee708..265ad018 100644 --- a/network/module.go +++ b/network/module.go @@ -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,95 @@ 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() { + // Check if connection is complete and attributed to the deleted profile. + if conn.DataIsComplete() && + conn.ProcessContext.Profile == profileID && + conn.ProcessContext.Source == profileSource { + + reAttributeConnection(ctx, conn) + reAttributed++ + tracer.Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID) + } + } + + // Re-attribute dns connections. + for _, conn := range dnsConns.clone() { + // Check if connection is complete and attributed to the deleted profile. + if conn.DataIsComplete() && + conn.ProcessContext.Profile == profileID && + conn.ProcessContext.Source == profileSource { + + reAttributeConnection(ctx, conn) + 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) { + // Check if data is complete. + if !conn.DataIsComplete() { + return + } + + conn.Lock() + defer conn.Unlock() + + // Attempt to assign new profile. + err := conn.process.RefetchProfile(ctx) + if err != nil { + log.Warningf("network: failed to refetch profile for %s: %s", conn, err) + return + } + + // Set the new process context. + conn.ProcessContext = getProcessContext(ctx, conn.process) + conn.Save() + + // Trigger event for re-attribution. + module.TriggerEvent(ConnectionReattributedEvent, conn.ID) +} diff --git a/process/profile.go b/process/profile.go index 65680639..27c0f985 100644 --- a/process/profile.go +++ b/process/profile.go @@ -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. diff --git a/profile/api.go b/profile/api.go new file mode 100644 index 00000000..e8213bd6 --- /dev/null +++ b/profile/api.go @@ -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 +} diff --git a/profile/database.go b/profile/database.go index 49ecb3c1..d311bd32 100644 --- a/profile/database.go +++ b/profile/database.go @@ -16,6 +16,7 @@ import ( // core:profiles// // cache:profiles/index// +// ProfilesDBPath is the base database path for profiles. const ProfilesDBPath = "core:profiles/" var profileDB = database.NewInterface(&database.Options{ @@ -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 { diff --git a/profile/endpoints/endpoint.go b/profile/endpoints/endpoint.go index ad23c074..b893a634 100644 --- a/profile/endpoints/endpoint.go +++ b/profile/endpoints/endpoint.go @@ -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) diff --git a/profile/get.go b/profile/get.go index 3a705b4a..dc37efea 100644 --- a/profile/get.go +++ b/profile/get.go @@ -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 } diff --git a/profile/merge.go b/profile/merge.go index 581ba07e..2c0d5cfc 100644 --- a/profile/merge.go +++ b/profile/merge.go @@ -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,19 @@ 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) + } + for _, sp := range secondaries { + if err := sp.delete(); err != nil { + return nil, fmt.Errorf("failed to delete secondary profile %s: %w", sp.ScopedID(), err) } - */ + } return newProfile, nil } diff --git a/profile/meta.go b/profile/meta.go new file mode 100644 index 00000000..e91a4074 --- /dev/null +++ b/profile/meta.go @@ -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 = ProfilesDBPath + "meta" + + 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) +} diff --git a/profile/migrations.go b/profile/migrations.go index 72cd1133..d45bbfd3 100644 --- a/profile/migrations.go +++ b/profile/migrations.go @@ -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.5.0", + MigrateFunc: migrateToDerivedIDs, + }, ) } diff --git a/profile/module.go b/profile/module.go index c41c9004..280579d0 100644 --- a/profile/module.go +++ b/profile/module.go @@ -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,16 @@ var ( updatesPath string ) +// Events. const ( - profileConfigChange = "profile config change" + ConfigChangeEvent = "profile config change" + DeletedEvent = "profile deleted" ) 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) } func prep() error { @@ -47,6 +52,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 +86,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() +} diff --git a/profile/profile.go b/profile/profile.go index c57c52d7..db309080 100644 --- a/profile/profile.go +++ b/profile/profile.go @@ -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()) From d19efe7103154d98521b5ff8a0a751681e90652b Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 3 Oct 2023 10:49:45 +0200 Subject: [PATCH 07/18] Allow importing unknown settings, add export key selection, improve json keys --- sync/setting_single.go | 8 ++++---- sync/settings.go | 34 ++++++++++++++++++++++++++++++---- sync/util.go | 17 +++++++++-------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/sync/setting_single.go b/sync/setting_single.go index 0d2f0d64..af116302 100644 --- a/sync/setting_single.go +++ b/sync/setting_single.go @@ -85,7 +85,7 @@ func handleExportSingleSetting(ar *api.Request) (data []byte, err error) { if len(q) > 0 { request = &ExportRequest{ From: q.Get("from"), - Key: q.Get("key"), + Keys: q["key"], // Get []string by direct map access. } } else { request = &ExportRequest{} @@ -95,12 +95,12 @@ func handleExportSingleSetting(ar *api.Request) (data []byte, err error) { } // Check parameters. - if request.From == "" || request.Key == "" { - return nil, errors.New("missing parameters") + if request.From == "" || len(request.Keys) != 1 { + return nil, errors.New("missing or malformed parameters") } // Export. - export, err := ExportSingleSetting(request.Key, request.From) + export, err := ExportSingleSetting(request.Keys[0], request.From) if err != nil { return nil, err } diff --git a/sync/settings.go b/sync/settings.go index e6f789cd..921350bd 100644 --- a/sync/settings.go +++ b/sync/settings.go @@ -28,6 +28,8 @@ type SettingsImportRequest struct { // any settings would be replaced or deleted. Reset bool `json:"reset"` + AllowUnknown bool `json:"allowUnknown"` + Export *SettingsExport `json:"export"` } @@ -42,6 +44,10 @@ func registerSettingsAPI() error { 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, @@ -67,6 +73,10 @@ func registerSettingsAPI() error { 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, @@ -85,6 +95,7 @@ func handleExportSettings(ar *api.Request) (data []byte, err error) { if len(q) > 0 { request = &ExportRequest{ From: q.Get("from"), + Keys: q["key"], // Get []string by direct map access. } } else { request = &ExportRequest{} @@ -99,7 +110,7 @@ func handleExportSettings(ar *api.Request) (data []byte, err error) { } // Export. - export, err := ExportSettings(request.From) + export, err := ExportSettings(request.From, request.Keys) if err != nil { return nil, err } @@ -120,7 +131,8 @@ func handleImportSettings(ar *api.Request) (any, error) { RawExport: string(ar.InputData), RawMime: ar.Header.Get("Content-Type"), }, - Reset: q.Has("reset"), + Reset: q.Has("reset"), + AllowUnknown: q.Has("allowUnknown"), } } else { request = &SettingsImportRequest{} @@ -151,7 +163,7 @@ func handleImportSettings(ar *api.Request) (any, error) { } // ExportSettings exports the global settings. -func ExportSettings(from string) (*SettingsExport, error) { +func ExportSettings(from string, keys []string) (*SettingsExport, error) { var settings map[string]any if from == ExportTargetGlobal { // Collect all changed global settings. @@ -175,6 +187,17 @@ func ExportSettings(from string) (*SettingsExport, error) { 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 @@ -237,7 +260,10 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { return nil, err } if checked < len(settings) { - return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImportRequest) + result.ContainsUnknown = true + if !r.AllowUnknown { + return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImportRequest) + } } // Import global settings. diff --git a/sync/util.go b/sync/util.go index 5d9a6154..5a0e5f3a 100644 --- a/sync/util.go +++ b/sync/util.go @@ -35,8 +35,8 @@ var ( // ExportRequest is a request for an export. type ExportRequest struct { - From string `json:"from"` - Key string `json:"key"` + From string `json:"from"` + Keys []string `json:"keys"` } // ImportRequest is a request to import an export. @@ -44,16 +44,17 @@ 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:"validate_only"` + ValidateOnly bool `json:"validateOnly"` - RawExport string `json:"raw_export"` - RawMime string `json:"raw_mime"` + RawExport string `json:"rawExport"` + RawMime string `json:"rawMime"` } // ImportResult is returned by successful import operations. type ImportResult struct { - RestartRequired bool `json:"restart_required"` - ReplacesExisting bool `json:"replaces_existing"` + RestartRequired bool `json:"restartRequired"` + ReplacesExisting bool `json:"replacesExisting"` + ContainsUnknown bool `json:"containsUnknown"` } // Errors. @@ -113,7 +114,7 @@ func serializeExport(export any, ar *api.Request) ([]byte, error) { case dsd.JSON: data, err = filesig.AddJSONChecksum(data) case dsd.YAML: - data, err = filesig.AddYAMLChecksum(data, filesig.TextPlacementTop) + data, err = filesig.AddYAMLChecksum(data, filesig.TextPlacementBottom) default: return nil, dsd.ErrIncompatibleFormat } From cd7a100d57833e95f76630b152c426f95e7f857d Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 3 Oct 2023 11:41:51 +0200 Subject: [PATCH 08/18] Add comment --- sync/settings.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sync/settings.go b/sync/settings.go index 921350bd..586edad0 100644 --- a/sync/settings.go +++ b/sync/settings.go @@ -28,6 +28,8 @@ type SettingsImportRequest struct { // 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"` From 76ab7127215ae71cd10e0c666cc5abdbf2731ebf Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 3 Oct 2023 12:44:24 +0200 Subject: [PATCH 09/18] Update jess to working version for PR --- go.mod | 16 +++++++--------- go.sum | 2 ++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index d092023b..b42d0366 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/safing/portmaster -go 1.21 - -toolchain go1.21.2 +go 1.20 require ( github.com/Xuanwo/go-locale v1.1.0 @@ -21,10 +19,10 @@ 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/portbase v0.18.5 + github.com/safing/jess v0.3.2-0.20230926112644-fa3982d07a28 + github.com/safing/portbase v0.18.2 github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec - github.com/safing/spn v0.7.4 + github.com/safing/spn v0.6.23 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cobra v1.7.0 github.com/spkg/zipfs v0.7.1 @@ -92,11 +90,11 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zalando/go-keyring v0.2.3 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/mod v0.13.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.14.0 // indirect + golang.org/x/tools v0.13.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 diff --git a/go.sum b/go.sum index 2c41a7df..b10d5e96 100644 --- a/go.sum +++ b/go.sum @@ -247,6 +247,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD 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/jess v0.3.2-0.20230926112644-fa3982d07a28 h1:7enCIKQTOB1Ajdx6tEDgO7lTDXGw4nbS32e3RzBqYTI= +github.com/safing/jess v0.3.2-0.20230926112644-fa3982d07a28/go.mod h1:T+lt5Ns8CWLUyD4+oBMmZ7jDletFKGL6xy1XY7FcEJE= 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/portbase v0.18.5 h1:BgIBpreSNOnyHqx6Ovx3xJMkX2yOa3A2uLpfVBbfJPM= From 489c21ab5ab8738adb5e41ab6c4fc9efb7be981e Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 12 Oct 2023 14:08:53 +0200 Subject: [PATCH 10/18] Change profiles metadata database key to avoid issues --- profile/meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profile/meta.go b/profile/meta.go index e91a4074..de7c945c 100644 --- a/profile/meta.go +++ b/profile/meta.go @@ -51,7 +51,7 @@ func EnsureProfilesMetadata(r record.Record) (*ProfilesMetadata, error) { } var ( - profilesMetadataKey = ProfilesDBPath + "meta" + profilesMetadataKey = "core:profile-states" meta *ProfilesMetadata From 8cb44bb11d2a872ce5df08d442622babafd92ebb Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 20 Oct 2023 10:20:12 +0200 Subject: [PATCH 11/18] Fix settings import validation --- sync/settings.go | 2 +- sync/util.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sync/settings.go b/sync/settings.go index 586edad0..071c6c1e 100644 --- a/sync/settings.go +++ b/sync/settings.go @@ -263,7 +263,7 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { } if checked < len(settings) { result.ContainsUnknown = true - if !r.AllowUnknown { + if !r.AllowUnknown && !r.ValidateOnly { return nil, fmt.Errorf("%w: the export contains unknown settings", ErrInvalidImportRequest) } } diff --git a/sync/util.go b/sync/util.go index 5a0e5f3a..bd343096 100644 --- a/sync/util.go +++ b/sync/util.go @@ -140,7 +140,7 @@ func parseExport(request *ImportRequest, export any) error { default: // Checksums not supported. } - if err != nil && errors.Is(err, filesig.ErrChecksumMissing) { + if err != nil && !errors.Is(err, filesig.ErrChecksumMissing) { return fmt.Errorf("failed to verify checksum: %w", err) } From 89cf31e40e43871e338987db09bd60c75ab53b6e Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 20 Oct 2023 10:24:27 +0200 Subject: [PATCH 12/18] Mark profiles as edited when importing settings into them --- sync/setting_single.go | 4 ++++ sync/settings.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/sync/setting_single.go b/sync/setting_single.go index af116302..32d43088 100644 --- a/sync/setting_single.go +++ b/sync/setting_single.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "time" "github.com/safing/portbase/api" "github.com/safing/portbase/config" @@ -253,6 +254,9 @@ func ImportSingeSetting(r *SingleSettingImportRequest) (*ImportResult, error) { // 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) diff --git a/sync/settings.go b/sync/settings.go index 071c6c1e..c9ada9a8 100644 --- a/sync/settings.go +++ b/sync/settings.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "strings" + "time" "github.com/safing/portbase/api" "github.com/safing/portbase/config" @@ -322,6 +323,9 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) { } } + // Mark profile as edited by user. + p.LastEdited = time.Now().Unix() + // Save profile back to db. err = p.Save() if err != nil { From b20565adc3d7ca69c790d858287eec8ff25afa1e Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 23 Oct 2023 13:39:06 +0200 Subject: [PATCH 13/18] Fix race condition when re-attributing connections --- network/module.go | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/network/module.go b/network/module.go index 265ad018..1a7fe891 100644 --- a/network/module.go +++ b/network/module.go @@ -99,12 +99,7 @@ func reAttributeConnections(_ context.Context, eventData any) error { // Re-attribute connections. for _, conn := range conns.clone() { - // Check if connection is complete and attributed to the deleted profile. - if conn.DataIsComplete() && - conn.ProcessContext.Profile == profileID && - conn.ProcessContext.Source == profileSource { - - reAttributeConnection(ctx, conn) + if reAttributeConnection(ctx, conn, profileID, profileSource) { reAttributed++ tracer.Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID) } @@ -112,12 +107,7 @@ func reAttributeConnections(_ context.Context, eventData any) error { // Re-attribute dns connections. for _, conn := range dnsConns.clone() { - // Check if connection is complete and attributed to the deleted profile. - if conn.DataIsComplete() && - conn.ProcessContext.Profile == profileID && - conn.ProcessContext.Source == profileSource { - - reAttributeConnection(ctx, conn) + if reAttributeConnection(ctx, conn, profileID, profileSource) { reAttributed++ tracer.Debugf("filter: re-attributed %s to %s", conn, conn.process.PrimaryProfileID) } @@ -127,20 +117,26 @@ func reAttributeConnections(_ context.Context, eventData any) error { return nil } -func reAttributeConnection(ctx context.Context, conn *Connection) { - // Check if data is complete. - if !conn.DataIsComplete() { - return - } - +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.Warningf("network: failed to refetch profile for %s: %s", conn, err) - return + log.Tracer(ctx).Warningf("network: failed to refetch profile for %s: %s", conn, err) + return false } // Set the new process context. @@ -149,4 +145,7 @@ func reAttributeConnection(ctx context.Context, conn *Connection) { // 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 } From b2b6217265f55bca50d20d04aedd5182ca92e434 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 20 Oct 2023 11:55:18 +0200 Subject: [PATCH 14/18] Migrate profile IDs in history database when merging profiles --- netquery/database.go | 9 +++++++++ netquery/module_api.go | 15 +++++++++++++++ profile/merge.go | 2 ++ profile/module.go | 2 ++ 4 files changed, 28 insertions(+) diff --git a/netquery/database.go b/netquery/database.go index 7adae82b..e3287345 100644 --- a/netquery/database.go +++ b/netquery/database.go @@ -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. diff --git a/netquery/module_api.go b/netquery/module_api.go index 63a3d07f..b4e56b02 100644 --- a/netquery/module_api.go +++ b/netquery/module_api.go @@ -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 } diff --git a/profile/merge.go b/profile/merge.go index 2c0d5cfc..29e274dc 100644 --- a/profile/merge.go +++ b/profile/merge.go @@ -77,10 +77,12 @@ func MergeProfiles(name string, primary *Profile, secondaries ...*Profile) (newP 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 diff --git a/profile/module.go b/profile/module.go index 280579d0..a462cd7e 100644 --- a/profile/module.go +++ b/profile/module.go @@ -22,12 +22,14 @@ var ( const ( ConfigChangeEvent = "profile config change" DeletedEvent = "profile deleted" + MigratedEvent = "profile migrated" ) func init() { module = modules.Register("profiles", prep, start, stop, "base", "updates") module.RegisterEvent(ConfigChangeEvent, true) module.RegisterEvent(DeletedEvent, true) + module.RegisterEvent(MigratedEvent, true) } func prep() error { From bb75b0962ea213ecdd4673bfbe5ef900b142d1b3 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Oct 2023 14:17:59 +0200 Subject: [PATCH 15/18] Update deps --- go.mod | 24 ++++++----- go.sum | 124 +++++---------------------------------------------------- 2 files changed, 23 insertions(+), 125 deletions(-) diff --git a/go.mod b/go.mod index b42d0366..f7cf4ecd 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,13 @@ module github.com/safing/portmaster -go 1.20 +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 @@ -19,10 +21,10 @@ 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.2-0.20230926112644-fa3982d07a28 - github.com/safing/portbase v0.18.2 + 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.6.23 + github.com/safing/spn v0.7.4 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cobra v1.7.0 github.com/spkg/zipfs v0.7.1 @@ -71,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 @@ -90,15 +92,15 @@ require ( github.com/yusufpapurcu/wmi v1.2.3 // indirect github.com/zalando/go-keyring v0.2.3 // indirect go.etcd.io/bbolt v1.3.7 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect + 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 diff --git a/go.sum b/go.sum index b10d5e96..2bc4f40e 100644 --- a/go.sum +++ b/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,17 +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/jess v0.3.2-0.20230926112644-fa3982d07a28 h1:7enCIKQTOB1Ajdx6tEDgO7lTDXGw4nbS32e3RzBqYTI= -github.com/safing/jess v0.3.2-0.20230926112644-fa3982d07a28/go.mod h1:T+lt5Ns8CWLUyD4+oBMmZ7jDletFKGL6xy1XY7FcEJE= -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= @@ -269,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= @@ -308,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= @@ -318,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= @@ -389,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= @@ -401,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= @@ -431,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= @@ -440,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= @@ -454,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= @@ -483,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= @@ -498,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= From 6486f02bacaf2fe520273000ac632f23d47184b1 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Oct 2023 14:29:01 +0200 Subject: [PATCH 16/18] Load persisted metrics later --- core/core.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/core.go b/core/core.go index 1637e47c..d0c95418 100644 --- a/core/core.go +++ b/core/core.go @@ -63,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 } @@ -76,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 } From 743453452369bf8284aca08b7b4dfe048f311f12 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Oct 2023 14:29:15 +0200 Subject: [PATCH 17/18] Set correct migration version --- profile/migrations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profile/migrations.go b/profile/migrations.go index d45bbfd3..f2b33158 100644 --- a/profile/migrations.go +++ b/profile/migrations.go @@ -34,7 +34,7 @@ func registerMigrations() error { }, migration.Migration{ Description: "Migrate from random profile IDs to fingerprint-derived IDs", - Version: "v1.5.0", + Version: "v1.6.0", MigrateFunc: migrateToDerivedIDs, }, ) From d760bfb4ef7aa9dab3bca1495eb85729dc9ecb5c Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 25 Oct 2023 14:29:39 +0200 Subject: [PATCH 18/18] Improve get resource api error --- updates/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updates/api.go b/updates/api.go index 88513b3a..57b75a3c 100644 --- a/updates/api.go +++ b/updates/api.go @@ -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 } }