Merge branch 'develop' into feature/winkext
This commit is contained in:
12
README.md
12
README.md
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user