Working on portmaster restructure

This commit is contained in:
Daniel
2018-12-07 21:28:45 +01:00
parent 8fb21fd900
commit 8c11a35590
24 changed files with 850 additions and 554 deletions

View File

@@ -35,29 +35,17 @@ func updateActiveUserProfile(profile *Profile) {
}
}
func updateActiveGlobalProfile(profile *Profile) {
updateActiveProfile(1, profile)
}
func updateActiveStampProfile(profile *Profile) {
updateActiveProfile(2, profile)
}
func updateActiveFallbackProfile(profile *Profile) {
updateActiveProfile(3, profile)
}
func updateActiveProfile(setID int, profile *Profile) {
activeProfileSetsLock.RLock()
defer activeProfileSetsLock.RUnlock()
for _, activeSet := range activeProfileSets {
activeSet.Lock()
activeProfile := activeSet.profiles[setID]
activeProfile := activeSet.profiles[2]
if activeProfile != nil {
activeProfile.Lock()
if activeProfile.ID == profile.ID {
activeSet.profiles[setID] = profile
activeSet.profiles[2] = profile
}
activeProfile.Unlock()
}

View File

@@ -1,5 +1,7 @@
package profile
import "time"
var (
fingerprintWeights = map[string]int{
"full_path": 2,
@@ -10,41 +12,21 @@ var (
}
)
// Fingerprint links processes to profiles.
type Fingerprint struct {
OS string
Type string
Value string
Comment string
OS string
Type string
Value string
Comment string
LastUsed int64
}
// MatchesOS returns whether the Fingerprint is applicable for the current OS.
func (fp *Fingerprint) MatchesOS() bool {
return fp.OS == osIdentifier
}
//
// func (fp *Fingerprint) Equals(other *Fingerprint) bool {
// return fp.OS == other.OS &&
// fp.Type == other.Type &&
// fp.Value == other.Value
// }
//
// func (fp *Fingerprint) Check(type, value string) (weight int) {
// if fp.Match(fpType, value) {
// return GetFingerprintWeight(fpType)
// }
// return 0
// }
//
// func (fp *Fingerprint) Match(fpType, value string) (matches bool) {
// switch fp.Type {
// case "partial_path":
// return
// default:
// return fp.OS == osIdentifier &&
// fp.Type == fpType &&
// fp.Value == value
// }
//
// GetFingerprintWeight returns the weight of the given fingerprint type.
func GetFingerprintWeight(fpType string) (weight int) {
weight, ok := fingerprintWeights[fpType]
if ok {
@@ -53,47 +35,14 @@ func GetFingerprintWeight(fpType string) (weight int) {
return 0
}
//
// func (p *Profile) GetApplicableFingerprints() (fingerprints []*Fingerprint) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier {
// fingerprints = append(fingerprints, fp)
// }
// }
// return
// }
//
// AddFingerprint adds the given fingerprint to the profile.
func (p *Profile) AddFingerprint(fp *Fingerprint) {
if fp.OS == "" {
fp.OS = osIdentifier
}
if fp.LastUsed == 0 {
fp.LastUsed = time.Now().Unix()
}
p.Fingerprints = append(p.Fingerprints, fp)
}
//
// func (p *Profile) GetApplicableFingerprintTypes() (types []string) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier && !utils.StringInSlice(types, fp.Type) {
// types = append(types, fp.Type)
// }
// }
// return
// }
//
// func (p *Profile) MatchFingerprints(fingerprints map[string]string) (score int) {
// for _, fp := range p.Fingerprints {
// if fp.OS == osIdentifier {
//
// }
// }
// return
// }
//
// func FindUserProfiles() {
//
// }
//
// func FindProfiles(path string) (*ProfileSet, error) {
//
// }

View File

@@ -77,19 +77,6 @@ var (
}
)
// FlagsFromNames creates Flags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
// func FlagsFromNames(words []string) (*Flags, error) {
// var flags Flags
// for _, entry := range words {
// flag, ok := flagIDs[entry]
// if !ok {
// return nil, ErrFlagsParseFailed
// }
// flags = append(flags, flag)
// }
// return &flags, nil
// }
// Check checks if a flag is set at all and if it's active in the given security level.
func (flags Flags) Check(flag, level uint8) (active bool, ok bool) {
if flags == nil {

View File

@@ -15,12 +15,14 @@ type ProfileIndex struct {
record.Base
sync.Mutex
ID string
UserProfiles []string
StampProfiles []string
}
func makeIndexRecordKey(id string) string {
return fmt.Sprintf("index:profiles/%s", base64.RawURLEncoding.EncodeToString([]byte(id)))
func makeIndexRecordKey(fpType, id string) string {
return fmt.Sprintf("index:profiles/%s:%s", fpType, base64.RawURLEncoding.EncodeToString([]byte(id)))
}
// NewIndex returns a new ProfileIndex.
@@ -32,8 +34,8 @@ func NewIndex(id string) *ProfileIndex {
// AddUserProfile adds a User Profile to the index.
func (pi *ProfileIndex) AddUserProfile(identifier string) (changed bool) {
if !utils.StringInSlice(pi.UserProfiles, id) {
pi.UserProfiles = append(pi.UserProfiles, id)
if !utils.StringInSlice(pi.UserProfiles, identifier) {
pi.UserProfiles = append(pi.UserProfiles, identifier)
return true
}
return false
@@ -41,8 +43,8 @@ func (pi *ProfileIndex) AddUserProfile(identifier string) (changed bool) {
// AddStampProfile adds a Stamp Profile to the index.
func (pi *ProfileIndex) AddStampProfile(identifier string) (changed bool) {
if !utils.StringInSlice(pi.StampProfiles, id) {
pi.StampProfiles = append(pi.StampProfiles, id)
if !utils.StringInSlice(pi.StampProfiles, identifier) {
pi.StampProfiles = append(pi.StampProfiles, identifier)
return true
}
return false
@@ -59,8 +61,8 @@ func (pi *ProfileIndex) RemoveStampProfile(id string) {
}
// Get gets a ProfileIndex from the database.
func Get(id string) (*ProfileIndex, error) {
key := makeIndexRecordKey(id)
func Get(fpType, id string) (*ProfileIndex, error) {
key := makeIndexRecordKey(fpType, id)
r, err := indexDB.Get(key)
if err != nil {

View File

@@ -1,8 +1,6 @@
package index
import (
"strings"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/database/query"
"github.com/Safing/portbase/database/record"
@@ -25,7 +23,7 @@ var (
)
func init() {
modules.Register("profile:index", nil, start, stop, "database")
modules.Register("profile:index", nil, start, stop, "profile", "database")
}
func start() (err error) {
@@ -49,13 +47,17 @@ func indexer() {
case <-shutdownIndexer:
return
case r := <-indexSub.Feed:
if r == nil {
return
}
prof := ensureProfile(r)
if prof != nil {
for _, id := range prof.Identifiers {
if strings.HasPrefix(id, profile.IdentifierPrefix) {
for _, fp := range prof.Fingerprints {
if fp.MatchesOS() && fp.Type == "full_path" {
// get Profile and ensure identifier is set
pi, err := GetIndex(id)
pi, err := Get("full_path", fp.Value)
if err != nil {
if err == database.ErrNotFound {
pi = NewIndex(id)

View File

@@ -1,3 +1,24 @@
package profile
.
import "github.com/Safing/portbase/modules"
var (
shutdownSignal = make(chan struct{})
)
func init() {
modules.Register("profile", nil, start, stop, "database")
}
func start() error {
err := initSpecialProfiles()
if err != nil {
return err
}
return initUpdateListener()
}
func stop() error {
close(shutdownSignal)
return nil
}

46
profile/ports_test.go Normal file
View File

@@ -0,0 +1,46 @@
package profile
import (
"testing"
"time"
)
func TestPorts(t *testing.T) {
var ports Ports
ports = map[int16][]*Port{
6: []*Port{
&Port{ // SSH
Permit: true,
Created: time.Now().Unix(),
Start: 22,
End: 22,
},
},
-17: []*Port{
&Port{ // HTTP
Permit: false,
Created: time.Now().Unix(),
Start: 80,
End: 81,
},
},
93: []*Port{
&Port{ // HTTP
Permit: true,
Created: time.Now().Unix(),
Start: 93,
End: 93,
},
},
}
if ports.String() != "TCP:[permit:22], <UDP:[deny:80-81], 93:[permit:93]" {
t.Errorf("unexpected result: %s", ports.String())
}
var noPorts Ports
noPorts = map[int16][]*Port{}
if noPorts.String() != "None" {
t.Errorf("unexpected result: %s", ports.String())
}
}

View File

@@ -37,8 +37,10 @@ type Profile struct {
Domains Domains
Ports Ports
StampProfileKey string
StampProfileAssigned int64
// User Profile Only
CoupledPath string `json:",omitempty"`
StampProfileKey string `json:",omitempty"`
StampProfileAssigned int64 `json:",omitempty"`
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
// Framework *Framework `json:",omitempty bson:",omitempty"`

View File

@@ -42,6 +42,8 @@ func NewSet(user, stamp *Profile) *Set {
// Update gets the new global and default profile and updates the independence status. It must be called when reusing a profile set for a series of calls.
func (set *Set) Update(securityLevel uint8) {
set.Lock()
specialProfileLock.RLock()
defer specialProfileLock.RUnlock()
@@ -58,15 +60,36 @@ func (set *Set) Update(securityLevel uint8) {
}
// update independence
set.Unlock()
if set.CheckFlag(Independent) {
set.Lock()
set.independent = true
set.Unlock()
} else {
set.Lock()
set.independent = false
set.Unlock()
}
}
// GetProfileMode returns the active profile mode.
func (set *Set) GetProfileMode() uint8 {
switch {
case set.CheckFlag(Whitelist):
return Whitelist
case set.CheckFlag(Prompt):
return Prompt
case set.CheckFlag(Blacklist):
return Blacklist
default:
return Whitelist
}
}
// CheckFlag returns whether a given flag is set.
func (set *Set) CheckFlag(flag uint8) (active bool) {
set.Lock()
defer set.Unlock()
for i, profile := range set.profiles {
if i == 2 && set.independent {
@@ -86,6 +109,8 @@ func (set *Set) CheckFlag(flag uint8) (active bool) {
// CheckDomain checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
func (set *Set) CheckDomain(domain string) (permit, ok bool) {
set.Lock()
defer set.Unlock()
for i, profile := range set.profiles {
if i == 2 && set.independent {
@@ -105,6 +130,8 @@ func (set *Set) CheckDomain(domain string) (permit, ok bool) {
// CheckPort checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) {
set.Lock()
defer set.Unlock()
signedProtocol := int16(protocol)
if listen {

View File

@@ -1,17 +1,167 @@
package profile
import "testing"
import (
"testing"
"time"
"github.com/Safing/portmaster/status"
)
var (
testUserProfile *Profile
testStampProfile *Profile
)
func init() {
specialProfileLock.Lock()
defer specialProfileLock.Unlock()
globalProfile = makeDefaultGlobalProfile()
fallbackProfile = makeDefaultFallbackProfile()
testUserProfile = &Profile{
ID: "unit-test-user",
Name: "Unit Test User Profile",
SecurityLevel: status.SecurityLevelDynamic,
Flags: map[uint8]uint8{
Independent: status.SecurityLevelFortress,
},
Domains: map[string]*DomainDecision{
"example.com": &DomainDecision{
Permit: true,
Created: time.Now().Unix(),
IncludeSubdomains: false,
},
"bad.example.com": &DomainDecision{
Permit: false,
Created: time.Now().Unix(),
IncludeSubdomains: true,
},
},
Ports: map[int16][]*Port{
6: []*Port{
&Port{
Permit: true,
Created: time.Now().Unix(),
Start: 22000,
End: 22000,
},
},
},
}
testStampProfile = &Profile{
ID: "unit-test-stamp",
Name: "Unit Test Stamp Profile",
SecurityLevel: status.SecurityLevelFortress,
Flags: map[uint8]uint8{
Internet: status.SecurityLevelsAll,
},
Domains: map[string]*DomainDecision{
"bad2.example.com": &DomainDecision{
Permit: false,
Created: time.Now().Unix(),
IncludeSubdomains: true,
},
"good.bad.example.com": &DomainDecision{
Permit: true,
Created: time.Now().Unix(),
IncludeSubdomains: false,
},
},
Ports: map[int16][]*Port{
6: []*Port{
&Port{
Permit: false,
Created: time.Now().Unix(),
Start: 80,
End: 80,
},
},
-17: []*Port{
&Port{
Permit: true,
Created: time.Now().Unix(),
Start: 12345,
End: 12347,
},
},
},
}
}
func testFlag(t *testing.T, set *Set, flag uint8, shouldBeActive bool) {
active := set.CheckFlag(flag)
if active != shouldBeActive {
t.Errorf("unexpected result: flag %s: permitted=%v, expected=%v", flagNames[flag], active, shouldBeActive)
}
}
func testDomain(t *testing.T, set *Set, domain string, shouldBePermitted bool) {
permitted, ok := set.CheckDomain(domain)
if !ok {
t.Errorf("domain %s should be in test profile set", domain)
}
if permitted != shouldBePermitted {
t.Errorf("unexpected result: domain %s: permitted=%v, expected=%v", domain, permitted, shouldBePermitted)
}
}
func testUnregulatedDomain(t *testing.T, set *Set, domain string) {
_, ok := set.CheckDomain(domain)
if ok {
t.Errorf("domain %s should not be in test profile set", domain)
}
}
func testPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16, shouldBePermitted bool) {
permitted, ok := set.CheckPort(listen, protocol, port)
if !ok {
t.Errorf("port [%v %d %d] should be in test profile set", listen, protocol, port)
}
if permitted != shouldBePermitted {
t.Errorf("unexpected result: port [%v %d %d]: permitted=%v, expected=%v", listen, protocol, port, permitted, shouldBePermitted)
}
}
func testUnregulatedPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16) {
_, ok := set.CheckPort(listen, protocol, port)
if ok {
t.Errorf("port [%v %d %d] should not be in test profile set", listen, protocol, port)
}
}
func TestProfileSet(t *testing.T) {
// new := &Set{
// profiles: [4]*Profile{
// user, // Application
// nil, // Global
// stamp, // Stamp
// nil, // Default
// },
// }
// new.Update(status.SecurityLevelFortress)
set := NewSet(testUserProfile, testStampProfile)
set.Update(status.SecurityLevelDynamic)
testFlag(t, set, Whitelist, false)
testFlag(t, set, Internet, true)
testDomain(t, set, "example.com", true)
testDomain(t, set, "bad.example.com", false)
testDomain(t, set, "other.bad.example.com", false)
testDomain(t, set, "good.bad.example.com", false)
testDomain(t, set, "bad2.example.com", false)
testPort(t, set, false, 6, 443, true)
testPort(t, set, false, 6, 143, true)
testPort(t, set, false, 6, 22, true)
testPort(t, set, false, 6, 80, false)
testPort(t, set, false, 6, 80, false)
testPort(t, set, true, 17, 12345, true)
testPort(t, set, true, 17, 12346, true)
testPort(t, set, true, 17, 12347, true)
testUnregulatedDomain(t, set, "other.example.com")
testUnregulatedPort(t, set, false, 17, 53)
testUnregulatedPort(t, set, false, 17, 443)
testUnregulatedPort(t, set, true, 6, 443)
set.Update(status.SecurityLevelSecure)
testFlag(t, set, Internet, true)
set.Update(status.SecurityLevelFortress) // Independent!
testFlag(t, set, Internet, false)
testPort(t, set, false, 6, 80, true)
testUnregulatedDomain(t, set, "bad2.example.com")
testUnregulatedPort(t, set, true, 17, 12346)
}

View File

@@ -1,7 +1,6 @@
package profile
import (
"fmt"
"strings"
"github.com/Safing/portbase/database"
@@ -19,35 +18,41 @@ func initUpdateListener() error {
return nil
}
var (
slashedUserNamespace = fmt.Sprintf("/%s/", userNamespace)
slashedStampNamespace = fmt.Sprintf("/%s/", stampNamespace)
)
func updateListener(sub *database.Subscription) {
for r := range sub.Feed {
profile, err := ensureProfile(r)
if err != nil {
log.Errorf("profile: received update for special profile, but could not read: %s", err)
continue
}
for {
select {
case <-shutdownSignal:
return
case r := <-sub.Feed:
specialProfileLock.Lock()
switch profile.ID {
case "global":
globalProfile = profile
updateActiveGlobalProfile(profile)
case "fallback":
fallbackProfile = profile
updateActiveFallbackProfile(profile)
default:
switch {
case strings.HasPrefix(profile.Key(), makeProfileKey(userNamespace, "")):
updateActiveUserProfile(profile)
case strings.HasPrefix(profile.Key(), makeProfileKey(stampNamespace, "")):
updateActiveStampProfile(profile)
if r.Meta().IsDeleted() {
continue
}
profile, err := ensureProfile(r)
if err != nil {
log.Errorf("profile: received update for special profile, but could not read: %s", err)
continue
}
switch profile.ID {
case "global":
specialProfileLock.Lock()
globalProfile = profile
specialProfileLock.Unlock()
case "fallback":
specialProfileLock.Lock()
fallbackProfile = profile
specialProfileLock.Unlock()
default:
switch {
case strings.HasPrefix(profile.Key(), makeProfileKey(userNamespace, "")):
updateActiveUserProfile(profile)
case strings.HasPrefix(profile.Key(), makeProfileKey(stampNamespace, "")):
updateActiveStampProfile(profile)
}
}
}
specialProfileLock.Unlock()
}
}