Merge branch 'develop' into feature/winkext

This commit is contained in:
Daniel
2019-04-01 14:47:42 +02:00
13 changed files with 133 additions and 43 deletions

View File

@@ -75,3 +75,15 @@ Documentation _in progress_ can be found here: [docs.safing.io](http://docs.safi
- debian/ubuntu: `sudo apt-get install libnetfilter-queue-dev` - debian/ubuntu: `sudo apt-get install libnetfilter-queue-dev`
- fedora: `?` - fedora: `?`
- arch: `?` - arch: `?`
## TCP/UDP Ports
The Portmaster (with Gate17) uses the following ports:
- ` 17` Gate17 port for connecting to Gate17 nodes
- ` 53` DNS server (local only)
- `717` Gate17 entrypoint as the local endpoint for tunneled connections (local only)
- `817` Portmaster API for integration with UI elements and other helpers (local only)
Learn more about [why we chose these ports](https://docs.safing.io/docs/portmaster/os-integration.html).
Gate17 nodes additionally uses other common ports like `80` and `443` to provide access in restricted network environments.

View File

@@ -28,8 +28,6 @@ var (
packetsDropped *uint64 packetsDropped *uint64
localNet4 *net.IPNet localNet4 *net.IPNet
// Yes, this would normally be 127.0.0.0/8
// TODO: figure out any side effects
localhost4 = net.IPv4(127, 0, 0, 1) localhost4 = net.IPv4(127, 0, 0, 1)
localhost6 = net.IPv6loopback localhost6 = net.IPv6loopback

View File

@@ -36,6 +36,7 @@ func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string)
// check if communication needs reevaluation // check if communication needs reevaluation
if comm.NeedsReevaluation() { if comm.NeedsReevaluation() {
log.Infof("firewall: re-evaluating verdict on %s", comm)
comm.ResetVerdict() comm.ResetVerdict()
} }
@@ -89,6 +90,7 @@ func DecideOnCommunicationBeforeIntel(comm *network.Communication, fqdn string)
// DecideOnCommunicationAfterIntel makes a decision about a communication after the dns query is resolved and intel is gathered. // DecideOnCommunicationAfterIntel makes a decision about a communication after the dns query is resolved and intel is gathered.
func DecideOnCommunicationAfterIntel(comm *network.Communication, fqdn string, rrCache *intel.RRCache) { func DecideOnCommunicationAfterIntel(comm *network.Communication, fqdn string, rrCache *intel.RRCache) {
// rrCache may be nil, when function is called for re-evaluation by DecideOnCommunication
// check if need to run // check if need to run
if comm.GetVerdict() != network.VerdictUndecided { if comm.GetVerdict() != network.VerdictUndecided {
@@ -353,9 +355,16 @@ func FilterDNSResponse(comm *network.Communication, fqdn string, rrCache *intel.
// DecideOnCommunication makes a decision about a communication with its first packet. // DecideOnCommunication makes a decision about a communication with its first packet.
func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) { func DecideOnCommunication(comm *network.Communication, pkt packet.Packet) {
// check if communication needs reevaluation // check if communication needs reevaluation, if it's not with a domain
if comm.NeedsReevaluation() { if comm.NeedsReevaluation() {
log.Infof("firewall: re-evaluating verdict on %s", comm)
comm.ResetVerdict() comm.ResetVerdict()
// if communicating with a domain entity, re-evaluate with Before/AfterIntel
if strings.HasSuffix(comm.Domain, ".") {
DecideOnCommunicationBeforeIntel(comm, comm.Domain)
DecideOnCommunicationAfterIntel(comm, comm.Domain, nil)
}
} }
// check if need to run // check if need to run

View File

@@ -31,7 +31,7 @@ func init() {
func main() { func main() {
// Set Info // Set Info
info.Set("Portmaster", "0.2.3", "AGPLv3", true) info.Set("Portmaster", "0.2.4", "AGPLv3", true)
// Start // Start
err := modules.Start() err := modules.Start()

View File

@@ -131,9 +131,13 @@ func (comm *Communication) NeedsReevaluation() bool {
comm.Lock() comm.Lock()
defer comm.Unlock() defer comm.Unlock()
updateVersion := profile.GetUpdateVersion() oldVersion := comm.profileUpdateVersion
if comm.profileUpdateVersion != updateVersion { comm.profileUpdateVersion = profile.GetUpdateVersion()
comm.profileUpdateVersion = updateVersion
if oldVersion == 0 {
return false
}
if oldVersion != comm.profileUpdateVersion {
return true return true
} }
return false return false

View File

@@ -70,16 +70,19 @@ func (p *Process) Delete() {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
// delete from internal storage
processesLock.Lock() processesLock.Lock()
delete(processes, p.Pid) delete(processes, p.Pid)
processesLock.Unlock() processesLock.Unlock()
// propagate delete
p.Meta().Delete() p.Meta().Delete()
if dbControllerFlag.IsSet() { if dbControllerFlag.IsSet() {
go dbController.PushUpdate(p) 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 { if p.profileSet != nil {
profile.DeactivateProfileSet(p.profileSet) profile.DeactivateProfileSet(p.profileSet)
} }

View File

@@ -1,6 +1,8 @@
package process package process
import ( import (
"fmt"
"github.com/Safing/portbase/database" "github.com/Safing/portbase/database"
"github.com/Safing/portbase/database/query" "github.com/Safing/portbase/database/query"
"github.com/Safing/portbase/log" "github.com/Safing/portbase/log"
@@ -64,7 +66,7 @@ func (p *Process) FindProfiles() error {
// FIXME: implement! // FIXME: implement!
p.UserProfileKey = userProfile.Key() 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() go p.Save()
return nil return nil

View File

@@ -1,6 +1,10 @@
package profile package profile
import "sync" import (
"sync"
"github.com/Safing/portbase/log"
)
var ( var (
activeProfileSets = make(map[string]*Set) activeProfileSets = make(map[string]*Set)
@@ -8,47 +12,64 @@ var (
) )
func activateProfileSet(set *Set) { func activateProfileSet(set *Set) {
set.Lock()
defer set.Unlock()
activeProfileSetsLock.Lock() activeProfileSetsLock.Lock()
defer activeProfileSetsLock.Unlock() 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. // DeactivateProfileSet marks a profile set as not active.
func DeactivateProfileSet(set *Set) { func DeactivateProfileSet(set *Set) {
set.Lock()
defer set.Unlock()
activeProfileSetsLock.Lock() activeProfileSetsLock.Lock()
defer activeProfileSetsLock.Unlock() 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) { func updateActiveProfile(profile *Profile, userProfile bool) {
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) {
activeProfileSetsLock.RLock() activeProfileSetsLock.RLock()
defer activeProfileSetsLock.RUnlock() defer activeProfileSetsLock.RUnlock()
var activeProfile *Profile
var profilesUpdated bool
// iterate all active profile sets
for _, activeSet := range activeProfileSets { for _, activeSet := range activeProfileSets {
activeSet.Lock() 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 { if activeProfile != nil {
activeProfile.Lock() activeProfile.Lock()
// check if the stamp profile has the same ID
if activeProfile.ID == profile.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() activeProfile.Unlock()
} }
activeSet.Unlock() activeSet.Unlock()
} }
if profilesUpdated {
increaseUpdateVersion()
}
} }

View File

@@ -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) { func (ep EndpointPermission) matchesDomainOnly(domain string) (matches bool, reason string) {
dotInFront := strings.HasPrefix(ep.Value, ".")
wildcardInFront := strings.HasPrefix(ep.Value, "*") wildcardInFront := strings.HasPrefix(ep.Value, "*")
wildcardInBack := strings.HasSuffix(ep.Value, "*") wildcardInBack := strings.HasSuffix(ep.Value, "*")
switch { 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: case wildcardInFront && wildcardInBack:
if strings.Contains(domain, strings.Trim(ep.Value, "*")) { if strings.Contains(domain, strings.Trim(ep.Value, "*")) {
return true, fmt.Sprintf("%s matches %s", domain, ep.Value) return true, fmt.Sprintf("%s matches %s", domain, ep.Value)

View File

@@ -59,13 +59,39 @@ func TestEndpointMatching(t *testing.T) {
ep.Value = "*example.com." ep.Value = "*example.com."
testEndpointDomainMatch(t, ep, "example.com.", Permitted) testEndpointDomainMatch(t, ep, "example.com.", Permitted)
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, 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.*" ep.Value = "example.*"
testEndpointDomainMatch(t, ep, "example.com.", Permitted) testEndpointDomainMatch(t, ep, "example.com.", Permitted)
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, 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*" ep.Value = "*.exampl*"
testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted) testEndpointDomainMatch(t, ep, "abc.example.com.", Permitted)
testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted) testEndpointIPMatch(t, ep, "abc.example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)

View File

@@ -15,6 +15,7 @@ var (
type Set struct { type Set struct {
sync.Mutex sync.Mutex
id string
profiles [4]*Profile profiles [4]*Profile
// Application // Application
// Global // Global
@@ -26,8 +27,9 @@ type Set struct {
} }
// NewSet returns a new profile set with given the profiles. // 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{ new := &Set{
id: id,
profiles: [4]*Profile{ profiles: [4]*Profile{
user, // Application user, // Application
nil, // Global nil, // Global

View File

@@ -140,7 +140,7 @@ func testEndpointIP(t *testing.T, set *Set, domain string, ip net.IP, protocol u
func TestProfileSet(t *testing.T) { func TestProfileSet(t *testing.T) {
set := NewSet(testUserProfile, testStampProfile) set := NewSet("[pid]-/path/to/bin", testUserProfile, testStampProfile)
set.Update(status.SecurityLevelDynamic) set.Update(status.SecurityLevelDynamic)
testFlag(t, set, Whitelist, false) testFlag(t, set, Whitelist, false)

View File

@@ -10,7 +10,7 @@ import (
) )
func initUpdateListener() error { func initUpdateListener() error {
sub, err := profileDB.Subscribe(query.New(MakeProfileKey(SpecialNamespace, ""))) sub, err := profileDB.Subscribe(query.New("core:profiles/"))
if err != nil { if err != nil {
return err return err
} }
@@ -36,34 +36,40 @@ func updateListener(sub *database.Subscription) {
continue continue
} }
log.Infof("profile: updated %s", profile.ID)
switch profile.DatabaseKey() { switch profile.DatabaseKey() {
case "profiles/special/global": case "profiles/special/global":
specialProfileLock.Lock() specialProfileLock.Lock()
globalProfile = profile globalProfile = profile
specialProfileLock.Unlock() specialProfileLock.Unlock()
case "profiles/special/fallback": case "profiles/special/fallback":
profile.Lock() profile.Lock()
if ensureServiceEndpointsDenyAll(profile) { profileChanged := ensureServiceEndpointsDenyAll(profile)
profile.Unlock() profile.Unlock()
if profileChanged {
profile.Save(SpecialNamespace) profile.Save(SpecialNamespace)
continue continue
} }
profile.Unlock()
specialProfileLock.Lock() specialProfileLock.Lock()
fallbackProfile = profile fallbackProfile = profile
specialProfileLock.Unlock() specialProfileLock.Unlock()
default: default:
switch { switch {
case strings.HasPrefix(profile.Key(), MakeProfileKey(UserNamespace, "")): case strings.HasPrefix(profile.Key(), MakeProfileKey(UserNamespace, "")):
updateActiveUserProfile(profile) updateActiveProfile(profile, true /* User Profile */)
increaseUpdateVersion()
case strings.HasPrefix(profile.Key(), MakeProfileKey(StampNamespace, "")): case strings.HasPrefix(profile.Key(), MakeProfileKey(StampNamespace, "")):
updateActiveStampProfile(profile) updateActiveProfile(profile, false /* Stamp Profile */)
increaseUpdateVersion()
} }
}
}
} }
} }
} }