From 046dd9b5adbb3e1f40271b5cb48c9f6dc1fd7a1d Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Mar 2019 16:29:00 +0100 Subject: [PATCH] Fix immediate profile application, update endpoint domain syntax --- network/communication.go | 10 +++++-- process/database.go | 5 +++- process/matching.go | 4 ++- profile/active.go | 63 ++++++++++++++++++++++++++------------- profile/endpoints.go | 7 +++++ profile/endpoints_test.go | 30 +++++++++++++++++-- profile/set.go | 4 ++- profile/set_test.go | 2 +- profile/updates.go | 24 +++++++++------ 9 files changed, 110 insertions(+), 39 deletions(-) diff --git a/network/communication.go b/network/communication.go index 685e36cf..85bfca1a 100644 --- a/network/communication.go +++ b/network/communication.go @@ -131,9 +131,13 @@ func (comm *Communication) NeedsReevaluation() bool { comm.Lock() defer comm.Unlock() - updateVersion := profile.GetUpdateVersion() - if comm.profileUpdateVersion != updateVersion { - comm.profileUpdateVersion = updateVersion + oldVersion := comm.profileUpdateVersion + comm.profileUpdateVersion = profile.GetUpdateVersion() + + if oldVersion == 0 { + return false + } + if oldVersion != comm.profileUpdateVersion { return true } return false diff --git a/process/database.go b/process/database.go index 6c4a2e43..4f43785f 100644 --- a/process/database.go +++ b/process/database.go @@ -70,16 +70,19 @@ func (p *Process) Delete() { p.Lock() defer p.Unlock() + // delete from internal storage processesLock.Lock() delete(processes, p.Pid) processesLock.Unlock() + // propagate delete p.Meta().Delete() if dbControllerFlag.IsSet() { go dbController.PushUpdate(p) } - // TODO: this should not be necessary, as processes should always have a profileSet. + // deactivate profile + // TODO: check if there is another process using the same profile set if p.profileSet != nil { profile.DeactivateProfileSet(p.profileSet) } diff --git a/process/matching.go b/process/matching.go index bce476a3..44b62541 100644 --- a/process/matching.go +++ b/process/matching.go @@ -1,6 +1,8 @@ package process import ( + "fmt" + "github.com/Safing/portbase/database" "github.com/Safing/portbase/database/query" "github.com/Safing/portbase/log" @@ -64,7 +66,7 @@ func (p *Process) FindProfiles() error { // FIXME: implement! p.UserProfileKey = userProfile.Key() - p.profileSet = profile.NewSet(userProfile, nil) + p.profileSet = profile.NewSet(fmt.Sprintf("%d-%s", p.Pid, p.Path), userProfile, nil) go p.Save() return nil diff --git a/profile/active.go b/profile/active.go index fdf1f057..e6b05c8a 100644 --- a/profile/active.go +++ b/profile/active.go @@ -1,6 +1,10 @@ package profile -import "sync" +import ( + "sync" + + "github.com/Safing/portbase/log" +) var ( activeProfileSets = make(map[string]*Set) @@ -8,47 +12,64 @@ var ( ) func activateProfileSet(set *Set) { - set.Lock() - defer set.Unlock() activeProfileSetsLock.Lock() defer activeProfileSetsLock.Unlock() - activeProfileSets[set.profiles[0].ID] = set + set.Lock() + defer set.Unlock() + activeProfileSets[set.id] = set + log.Tracef("profile: activated profile set %s", set.id) } // DeactivateProfileSet marks a profile set as not active. func DeactivateProfileSet(set *Set) { - set.Lock() - defer set.Unlock() activeProfileSetsLock.Lock() defer activeProfileSetsLock.Unlock() - delete(activeProfileSets, set.profiles[0].ID) + set.Lock() + defer set.Unlock() + delete(activeProfileSets, set.id) + log.Tracef("profile: deactivated profile set %s", set.id) } -func updateActiveUserProfile(profile *Profile) { - activeProfileSetsLock.RLock() - defer activeProfileSetsLock.RUnlock() - activeSet, ok := activeProfileSets[profile.ID] - if ok { - activeSet.Lock() - defer activeSet.Unlock() - activeSet.profiles[0] = profile - } -} - -func updateActiveStampProfile(profile *Profile) { +func updateActiveProfile(profile *Profile, userProfile bool) { activeProfileSetsLock.RLock() defer activeProfileSetsLock.RUnlock() + var activeProfile *Profile + var profilesUpdated bool + + // iterate all active profile sets for _, activeSet := range activeProfileSets { activeSet.Lock() - activeProfile := activeSet.profiles[2] + + if userProfile { + activeProfile = activeSet.profiles[0] + } else { + activeProfile = activeSet.profiles[2] + } + + // check if profile exists (for stamp profiles) if activeProfile != nil { activeProfile.Lock() + + // check if the stamp profile has the same ID if activeProfile.ID == profile.ID { - activeSet.profiles[2] = profile + if userProfile { + activeSet.profiles[0] = profile + log.Infof("profile: updated active user profile %s (%s)", profile.ID, profile.LinkedPath) + } else { + activeSet.profiles[2] = profile + log.Infof("profile: updated active stamp profile %s", profile.ID) + } + profilesUpdated = true } + activeProfile.Unlock() } + activeSet.Unlock() } + + if profilesUpdated { + increaseUpdateVersion() + } } diff --git a/profile/endpoints.go b/profile/endpoints.go index 25431361..ad0e9dbc 100644 --- a/profile/endpoints.go +++ b/profile/endpoints.go @@ -114,9 +114,16 @@ func (e Endpoints) CheckIP(domain string, ip net.IP, protocol uint8, port uint16 } func (ep EndpointPermission) matchesDomainOnly(domain string) (matches bool, reason string) { + dotInFront := strings.HasPrefix(ep.Value, ".") wildcardInFront := strings.HasPrefix(ep.Value, "*") wildcardInBack := strings.HasSuffix(ep.Value, "*") + switch { + case dotInFront && !wildcardInFront && !wildcardInBack: + // subdomain or domain + if strings.HasSuffix(domain, ep.Value) || domain == strings.TrimPrefix(ep.Value, ".") { + return true, fmt.Sprintf("%s matches %s", domain, ep.Value) + } case wildcardInFront && wildcardInBack: if strings.Contains(domain, strings.Trim(ep.Value, "*")) { return true, fmt.Sprintf("%s matches %s", domain, ep.Value) diff --git a/profile/endpoints_test.go b/profile/endpoints_test.go index ff67d961..56e1c472 100644 --- a/profile/endpoints_test.go +++ b/profile/endpoints_test.go @@ -59,13 +59,39 @@ func TestEndpointMatching(t *testing.T) { ep.Value = "*example.com." testEndpointDomainMatch(t, ep, "example.com.", Permitted) testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted) + testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc-example.com.", Permitted) + testEndpointIPMatch(t, ep, "abc-example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + + ep.Value = "*.example.com." + testEndpointDomainMatch(t, ep, "example.com.", NoMatch) + testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) + testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted) + testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc-example.com.", NoMatch) + testEndpointIPMatch(t, ep, "abc-example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) + + ep.Value = ".example.com." + testEndpointDomainMatch(t, ep, "example.com.", Permitted) + testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted) + testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc-example.com.", NoMatch) + testEndpointIPMatch(t, ep, "abc-example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) - ep.Type = EptDomain ep.Value = "example.*" testEndpointDomainMatch(t, ep, "example.com.", Permitted) testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) + testEndpointDomainMatch(t, ep, "abc.example.com.", NoMatch) + testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) + + ep.Value = ".example.*" + testEndpointDomainMatch(t, ep, "example.com.", NoMatch) + testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) + testEndpointDomainMatch(t, ep, "abc.example.com.", NoMatch) + testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, NoMatch) - ep.Type = EptDomain ep.Value = "*.exampl*" testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted) testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) diff --git a/profile/set.go b/profile/set.go index 04d63a85..a1a5120c 100644 --- a/profile/set.go +++ b/profile/set.go @@ -15,6 +15,7 @@ var ( type Set struct { sync.Mutex + id string profiles [4]*Profile // Application // Global @@ -26,8 +27,9 @@ type Set struct { } // NewSet returns a new profile set with given the profiles. -func NewSet(user, stamp *Profile) *Set { +func NewSet(id string, user, stamp *Profile) *Set { new := &Set{ + id: id, profiles: [4]*Profile{ user, // Application nil, // Global diff --git a/profile/set_test.go b/profile/set_test.go index 984b1f3a..a5a1df4a 100644 --- a/profile/set_test.go +++ b/profile/set_test.go @@ -140,7 +140,7 @@ func testEndpointIP(t *testing.T, set *Set, domain string, ip net.IP, protocol u func TestProfileSet(t *testing.T) { - set := NewSet(testUserProfile, testStampProfile) + set := NewSet("[pid]-/path/to/bin", testUserProfile, testStampProfile) set.Update(status.SecurityLevelDynamic) testFlag(t, set, Whitelist, false) diff --git a/profile/updates.go b/profile/updates.go index b8bc603f..cd923c10 100644 --- a/profile/updates.go +++ b/profile/updates.go @@ -10,7 +10,7 @@ import ( ) func initUpdateListener() error { - sub, err := profileDB.Subscribe(query.New(MakeProfileKey(SpecialNamespace, ""))) + sub, err := profileDB.Subscribe(query.New("core:profiles/")) if err != nil { return err } @@ -36,34 +36,40 @@ func updateListener(sub *database.Subscription) { continue } + log.Infof("profile: updated %s", profile.ID) + switch profile.DatabaseKey() { case "profiles/special/global": + specialProfileLock.Lock() globalProfile = profile specialProfileLock.Unlock() + case "profiles/special/fallback": + profile.Lock() - if ensureServiceEndpointsDenyAll(profile) { - profile.Unlock() + profileChanged := ensureServiceEndpointsDenyAll(profile) + profile.Unlock() + + if profileChanged { profile.Save(SpecialNamespace) continue } - profile.Unlock() specialProfileLock.Lock() fallbackProfile = profile specialProfileLock.Unlock() + default: + switch { case strings.HasPrefix(profile.Key(), MakeProfileKey(UserNamespace, "")): - updateActiveUserProfile(profile) - increaseUpdateVersion() + updateActiveProfile(profile, true /* User Profile */) case strings.HasPrefix(profile.Key(), MakeProfileKey(StampNamespace, "")): - updateActiveStampProfile(profile) - increaseUpdateVersion() + updateActiveProfile(profile, false /* Stamp Profile */) } - } + } } } }