Reevaluate and update firewall core logic

This commit is contained in:
Daniel
2019-02-22 16:18:58 +01:00
parent d28ed664aa
commit f7a07cbb2f
39 changed files with 1469 additions and 915 deletions

View File

@@ -27,17 +27,15 @@ func makeDefaultFallbackProfile() *Profile {
Localhost: status.SecurityLevelsAll,
// Specials
Related: status.SecurityLevelDynamic,
PeerToPeer: status.SecurityLevelDynamic,
Related: status.SecurityLevelDynamic,
},
ServiceEndpoints: []*EndpointPermission{
&EndpointPermission{
DomainOrIP: "",
Wildcard: true,
Protocol: 0,
StartPort: 0,
EndPort: 0,
Permit: false,
Type: EptAny,
Protocol: 0,
StartPort: 0,
EndPort: 0,
Permit: false,
},
},
}

View File

@@ -2,6 +2,7 @@ package profile
import (
"fmt"
"net"
"strconv"
"strings"
@@ -13,15 +14,44 @@ type Endpoints []*EndpointPermission
// EndpointPermission holds a decision about an endpoint.
type EndpointPermission struct {
DomainOrIP string
Wildcard bool
Protocol uint8
StartPort uint16
EndPort uint16
Permit bool
Created int64
Type EPType
Value string
Protocol uint8
StartPort uint16
EndPort uint16
Permit bool
Created int64
}
// EPType represents the type of an EndpointPermission
type EPType uint8
// EPType values
const (
EptUnknown EPType = 0
EptAny EPType = 1
EptDomain EPType = 2
EptIPv4 EPType = 3
EptIPv6 EPType = 4
EptIPv4Range EPType = 5
EptIPv6Range EPType = 6
EptASN EPType = 7
EptCountry EPType = 8
)
// EPResult represents the result of a check against an EndpointPermission
type EPResult uint8
// EndpointPermission return values
const (
NoMatch EPResult = iota
Undeterminable
Denied
Permitted
)
// IsSet returns whether the Endpoints object is "set".
func (e Endpoints) IsSet() bool {
if len(e) > 0 {
@@ -30,9 +60,28 @@ func (e Endpoints) IsSet() bool {
return false
}
// Check checks if the given domain is governed in the list of domains and returns whether it is permitted.
// If getDomainOfIP (returns reverse and forward dns matching domain name) is supplied, an IP will be resolved to a domain, if necessary.
func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkReverseIP bool, securityLevel uint8) (permit bool, reason string, ok bool) {
// CheckDomain checks the if the given endpoint matches a EndpointPermission in the list.
func (e Endpoints) CheckDomain(domain string) (result EPResult, reason string) {
if domain == "" {
return Denied, "internal error"
}
for _, entry := range e {
if entry != nil {
if result, reason = entry.MatchesDomain(domain); result != NoMatch {
return
}
}
}
return NoMatch, ""
}
// CheckIP checks the if the given endpoint matches a EndpointPermission in the list. If _checkReverseIP_ and no domain is given, the IP will be resolved to a domain, if necessary.
func (e Endpoints) CheckIP(domain string, ip net.IP, protocol uint8, port uint16, checkReverseIP bool, securityLevel uint8) (result EPResult, reason string) {
if ip == nil {
return Denied, "internal error"
}
// ip resolving
var cachedGetDomainOfIP func() string
@@ -42,7 +91,7 @@ func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkRe
// setup caching wrapper
cachedGetDomainOfIP = func() string {
if !ipResolved {
result, err := intel.ResolveIPAndValidate(domainOrIP, securityLevel)
result, err := intel.ResolveIPAndValidate(ip.String(), securityLevel)
if err != nil {
// log.Debug()
ipName = result
@@ -53,54 +102,139 @@ func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16, checkRe
}
}
isDomain := strings.HasSuffix(domainOrIP, ".")
for _, entry := range e {
if entry != nil {
if ok, reason := entry.Matches(domainOrIP, protocol, port, isDomain, cachedGetDomainOfIP); ok {
return entry.Permit, reason, true
if result, reason := entry.MatchesIP(domain, ip, protocol, port, cachedGetDomainOfIP); result != NoMatch {
return result, reason
}
}
}
return false, "", false
return NoMatch, ""
}
func isSubdomainOf(domain, subdomain string) bool {
dotPrefixedDomain := "." + domain
return strings.HasSuffix(subdomain, dotPrefixedDomain)
}
// Matches checks whether the given endpoint has a managed permission. If getDomainOfIP (returns reverse and forward dns matching domain name) is supplied, this declares an incoming connection.
func (ep EndpointPermission) Matches(domainOrIP string, protocol uint8, port uint16, isDomain bool, getDomainOfIP func() string) (match bool, reason string) {
if ep.Protocol > 0 && protocol != ep.Protocol {
return false, ""
}
if ep.StartPort > 0 && (port < ep.StartPort || port > ep.EndPort) {
return false, ""
}
func (ep EndpointPermission) matchesDomainOnly(domain string) (matches bool, reason string) {
wildcardInFront := strings.HasPrefix(ep.Value, "*")
wildcardInBack := strings.HasSuffix(ep.Value, "*")
switch {
case ep.Wildcard && len(ep.DomainOrIP) == 0:
// host wildcard
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
case domainOrIP == ep.DomainOrIP:
// host match
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
case isDomain && ep.Wildcard && isSubdomainOf(ep.DomainOrIP, domainOrIP):
// subdomain match
return true, fmt.Sprintf("%s matches %s", domainOrIP, ep)
case !isDomain && getDomainOfIP != nil && getDomainOfIP() == ep.DomainOrIP:
// resolved IP match
return true, fmt.Sprintf("%s->%s matches %s", domainOrIP, getDomainOfIP(), ep)
case !isDomain && getDomainOfIP != nil && ep.Wildcard && isSubdomainOf(ep.DomainOrIP, getDomainOfIP()):
// resolved IP subdomain match
return true, fmt.Sprintf("%s->%s matches %s", domainOrIP, getDomainOfIP(), ep)
case wildcardInFront && wildcardInBack:
if strings.Contains(domain, strings.Trim(ep.Value, "*")) {
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
}
case wildcardInFront:
if strings.HasSuffix(domain, strings.TrimLeft(ep.Value, "*")) {
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
}
case wildcardInBack:
if strings.HasPrefix(domain, strings.TrimRight(ep.Value, "*")) {
return true, fmt.Sprintf("%s matches %s", domain, ep.Value)
}
default:
// no match
return false, ""
if domain == ep.Value {
return true, ""
}
}
return false, ""
}
func (ep EndpointPermission) matchProtocolAndPortsAndReturn(protocol uint8, port uint16) (result EPResult) {
// only check if protocol is defined
if ep.Protocol > 0 {
// if protocol is unknown, return Undeterminable
if protocol == 0 {
return Undeterminable
}
// if protocol does not match, return NoMatch
if protocol != ep.Protocol {
return NoMatch
}
}
// only check if port is defined
if ep.StartPort > 0 {
// if port is unknown, return Undeterminable
if port == 0 {
return Undeterminable
}
// if port does not match, return NoMatch
if port < ep.StartPort || port > ep.EndPort {
return NoMatch
}
}
// protocol and port matched or were defined as any
if ep.Permit {
return Permitted
}
return Denied
}
// MatchesDomain checks if the given endpoint matches the EndpointPermission.
func (ep EndpointPermission) MatchesDomain(domain string) (result EPResult, reason string) {
switch ep.Type {
case EptAny:
// always matches
case EptDomain:
var matched bool
matched, reason = ep.matchesDomainOnly(domain)
if !matched {
return NoMatch, ""
}
case EptIPv4:
return Undeterminable, ""
case EptIPv6:
return Undeterminable, ""
case EptIPv4Range:
return Undeterminable, ""
case EptIPv6Range:
return Undeterminable, ""
case EptASN:
return Undeterminable, ""
case EptCountry:
return Undeterminable, ""
default:
return Denied, "encountered unknown enpoint permission type"
}
return ep.matchProtocolAndPortsAndReturn(0, 0), reason
}
// MatchesIP checks if the given endpoint matches the EndpointPermission. _getDomainOfIP_, if given, will be used to get the domain if not given.
func (ep EndpointPermission) MatchesIP(domain string, ip net.IP, protocol uint8, port uint16, getDomainOfIP func() string) (result EPResult, reason string) {
switch ep.Type {
case EptAny:
// always matches
case EptDomain:
if domain == "" {
if getDomainOfIP == nil {
return NoMatch, ""
}
domain = getDomainOfIP()
}
var matched bool
matched, reason = ep.matchesDomainOnly(domain)
if !matched {
return NoMatch, ""
}
case EptIPv4, EptIPv6:
if ep.Value != ip.String() {
return NoMatch, ""
}
case EptIPv4Range:
return Denied, "endpoint type IP Range not yet implemented"
case EptIPv6Range:
return Denied, "endpoint type IP Range not yet implemented"
case EptASN:
return Denied, "endpoint type ASN not yet implemented"
case EptCountry:
return Denied, "endpoint type country not yet implemented"
default:
return Denied, "encountered unknown enpoint permission type"
}
return ep.matchProtocolAndPortsAndReturn(protocol, port), reason
}
func (e Endpoints) String() string {
@@ -111,9 +245,36 @@ func (e Endpoints) String() string {
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
}
func (ep EndpointPermission) String() string {
s := ep.DomainOrIP
func (ept EPType) String() string {
switch ept {
case EptAny:
return "Any"
case EptDomain:
return "Domain"
case EptIPv4:
return "IPv4"
case EptIPv6:
return "IPv6"
case EptIPv4Range:
return "IPv4-Range"
case EptIPv6Range:
return "IPv6-Range"
case EptASN:
return "ASN"
case EptCountry:
return "Country"
default:
return "Unknown"
}
}
func (ep EndpointPermission) String() string {
s := ep.Type.String()
if ep.Type != EptAny {
s += ":"
s += ep.Value
}
s += " "
if ep.Protocol > 0 {
@@ -136,3 +297,18 @@ func (ep EndpointPermission) String() string {
return s
}
func (epr EPResult) String() string {
switch epr {
case NoMatch:
return "No Match"
case Undeterminable:
return "Undeterminable"
case Denied:
return "Denied"
case Permitted:
return "Permitted"
default:
return "Unknown"
}
}

View File

@@ -1,54 +1,159 @@
package profile
import (
"net"
"testing"
"github.com/Safing/portbase/utils/testutils"
)
// TODO: RETIRED
// func testdeMatcher(t *testing.T, value string, expectedResult bool) {
// if domainEndingMatcher.MatchString(value) != expectedResult {
// if expectedResult {
// t.Errorf("domainEndingMatcher should match %s", value)
// } else {
// t.Errorf("domainEndingMatcher should not match %s", value)
// }
// }
// }
//
// func TestdomainEndingMatcher(t *testing.T) {
// testdeMatcher(t, "example.com", true)
// testdeMatcher(t, "com", true)
// testdeMatcher(t, "example.xn--lgbbat1ad8j", true)
// testdeMatcher(t, "xn--lgbbat1ad8j", true)
// testdeMatcher(t, "fe80::beef", false)
// testdeMatcher(t, "fe80::dead:beef", false)
// testdeMatcher(t, "10.2.3.4", false)
// testdeMatcher(t, "4", false)
// }
func testEndpointDomainMatch(t *testing.T, ep *EndpointPermission, domain string, expectedResult EPResult) {
var result EPResult
result, _ = ep.MatchesDomain(domain)
if result != expectedResult {
t.Errorf(
"line %d: unexpected result for endpoint domain match %s: result=%s, expected=%s",
testutils.GetLineNumberOfCaller(1),
domain,
result,
expectedResult,
)
}
}
func testEndpointIPMatch(t *testing.T, ep *EndpointPermission, domain string, ip net.IP, protocol uint8, port uint16, expectedResult EPResult) {
var result EPResult
result, _ = ep.MatchesIP(domain, ip, protocol, port, nil)
if result != expectedResult {
t.Errorf(
"line %d: unexpected result for endpoint %s/%s/%d/%d: result=%s, expected=%s",
testutils.GetLineNumberOfCaller(1),
domain,
ip,
protocol,
port,
result,
expectedResult,
)
}
}
func TestEndpointMatching(t *testing.T) {
ep := &EndpointPermission{
Type: EptAny,
Protocol: 0,
StartPort: 0,
EndPort: 0,
Permit: true,
}
// ANY
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
// DOMAIN
// wildcard domains
ep.Type = EptDomain
ep.Value = "*example.com."
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
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)
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)
ep.Value = "*.com."
testEndpointDomainMatch(t, ep, "example.com.", Permitted)
testEndpointIPMatch(t, ep, "example.com.", net.ParseIP("10.2.3.4"), 6, 443, Permitted)
// edge case
ep.Value = ""
testEndpointDomainMatch(t, ep, "example.com", NoMatch)
// edge case
ep.Value = "*"
testEndpointDomainMatch(t, ep, "example.com", Permitted)
// edge case
ep.Value = "**"
testEndpointDomainMatch(t, ep, "example.com", Permitted)
// edge case
ep.Value = "***"
testEndpointDomainMatch(t, ep, "example.com", Permitted)
// protocol
ep.Value = "example.com"
ep.Protocol = 17
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 6, 443, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
// ports
ep.StartPort = 442
ep.EndPort = 444
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
ep.StartPort = 442
ep.StartPort = 443
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
ep.StartPort = 443
ep.EndPort = 444
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
ep.StartPort = 443
ep.EndPort = 443
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 80, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
// IP
ep.Type = EptIPv4
ep.Value = "10.2.3.4"
ep.Protocol = 0
ep.StartPort = 0
ep.EndPort = 0
testEndpointIPMatch(t, ep, "", net.ParseIP("10.2.3.4"), 6, 80, Permitted)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.4"), 17, 443, Permitted)
testEndpointIPMatch(t, ep, "", net.ParseIP("10.2.3.5"), 6, 80, NoMatch)
testEndpointIPMatch(t, ep, "example.com", net.ParseIP("10.2.3.5"), 17, 443, NoMatch)
testEndpointDomainMatch(t, ep, "example.com", Undeterminable)
}
func TestEPString(t *testing.T) {
var endpoints Endpoints
endpoints = []*EndpointPermission{
&EndpointPermission{
DomainOrIP: "example.com",
Wildcard: false,
Protocol: 6,
Permit: true,
Type: EptDomain,
Value: "example.com",
Protocol: 6,
Permit: true,
},
&EndpointPermission{
DomainOrIP: "8.8.8.8",
Protocol: 17, // TCP
StartPort: 53, // DNS
EndPort: 53,
Permit: false,
Type: EptIPv4,
Value: "1.1.1.1",
Protocol: 17, // TCP
StartPort: 53, // DNS
EndPort: 53,
Permit: false,
},
&EndpointPermission{
DomainOrIP: "google.com",
Wildcard: true,
Permit: false,
Type: EptDomain,
Value: "example.org",
Permit: false,
},
}
if endpoints.String() != "[example.com 6/*, 8.8.8.8 17/53, google.com */*]" {
if endpoints.String() != "[Domain:example.com 6/*, IPv4:1.1.1.1 17/53, Domain:example.org */*]" {
t.Errorf("unexpected result: %s", endpoints.String())
}
@@ -57,5 +162,4 @@ func TestEPString(t *testing.T) {
if noEndpoints.String() != "[]" {
t.Errorf("unexpected result: %s", noEndpoints.String())
}
}

View File

@@ -1,13 +1,18 @@
package profile
import "github.com/Safing/portbase/modules"
import (
"github.com/Safing/portbase/modules"
// module dependencies
_ "github.com/Safing/portmaster/core"
)
var (
shutdownSignal = make(chan struct{})
)
func init() {
modules.Register("profile", nil, start, stop, "global", "database")
modules.Register("profile", nil, start, stop, "core")
}
func start() error {

View File

@@ -1,6 +1,7 @@
package profile
import (
"net"
"sync"
"github.com/Safing/portmaster/status"
@@ -119,8 +120,28 @@ func (set *Set) CheckFlag(flag uint8) (active bool) {
return false
}
// CheckEndpoint checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
func (set *Set) CheckEndpoint(domainOrIP string, protocol uint8, port uint16, inbound bool) (permit bool, reason string, ok bool) {
// CheckEndpointDomain checks if the given endpoint matches an entry in the corresponding list. This is for outbound communication only.
func (set *Set) CheckEndpointDomain(domain string) (result EPResult, reason string) {
set.Lock()
defer set.Unlock()
for i, profile := range set.profiles {
if i == 2 && set.independent {
continue
}
if profile != nil {
if result, reason = profile.Endpoints.CheckDomain(domain); result != NoMatch {
return
}
}
}
return NoMatch, ""
}
// CheckEndpointIP checks if the given endpoint matches an entry in the corresponding list.
func (set *Set) CheckEndpointIP(domain string, ip net.IP, protocol uint8, port uint16, inbound bool) (result EPResult, reason string) {
set.Lock()
defer set.Unlock()
@@ -131,18 +152,18 @@ func (set *Set) CheckEndpoint(domainOrIP string, protocol uint8, port uint16, in
if profile != nil {
if inbound {
if permit, reason, ok = profile.ServiceEndpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
if result, reason = profile.ServiceEndpoints.CheckIP(domain, ip, protocol, port, inbound, set.combinedSecurityLevel); result != NoMatch {
return
}
} else {
if permit, reason, ok = profile.Endpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
if result, reason = profile.Endpoints.CheckIP(domain, ip, protocol, port, inbound, set.combinedSecurityLevel); result != NoMatch {
return
}
}
}
}
return false, "", false
return NoMatch, ""
}
// getSecurityLevel returns the highest prioritized security level.

View File

@@ -1,9 +1,11 @@
package profile
import (
"net"
"testing"
"time"
"github.com/Safing/portbase/utils/testutils"
"github.com/Safing/portmaster/status"
)
@@ -28,31 +30,30 @@ func init() {
},
Endpoints: []*EndpointPermission{
&EndpointPermission{
DomainOrIP: "good.bad.example.com.",
Wildcard: false,
Permit: true,
Created: time.Now().Unix(),
Type: EptDomain,
Value: "good.bad.example.com.",
Permit: true,
Created: time.Now().Unix(),
},
&EndpointPermission{
DomainOrIP: "bad.example.com.",
Wildcard: true,
Permit: false,
Created: time.Now().Unix(),
Type: EptDomain,
Value: "*bad.example.com.",
Permit: false,
Created: time.Now().Unix(),
},
&EndpointPermission{
DomainOrIP: "example.com.",
Wildcard: false,
Permit: true,
Created: time.Now().Unix(),
Type: EptDomain,
Value: "example.com.",
Permit: true,
Created: time.Now().Unix(),
},
&EndpointPermission{
DomainOrIP: "",
Wildcard: true,
Permit: true,
Protocol: 6,
StartPort: 22000,
EndPort: 22000,
Created: time.Now().Unix(),
Type: EptAny,
Permit: true,
Protocol: 6,
StartPort: 22000,
EndPort: 22000,
Created: time.Now().Unix(),
},
},
}
@@ -66,36 +67,33 @@ func init() {
// },
Endpoints: []*EndpointPermission{
&EndpointPermission{
DomainOrIP: "bad2.example.com.",
Wildcard: true,
Permit: false,
Created: time.Now().Unix(),
Type: EptDomain,
Value: "*bad2.example.com.",
Permit: false,
Created: time.Now().Unix(),
},
&EndpointPermission{
DomainOrIP: "",
Wildcard: true,
Permit: true,
Protocol: 6,
StartPort: 80,
EndPort: 80,
Created: time.Now().Unix(),
Type: EptAny,
Permit: true,
Protocol: 6,
StartPort: 80,
EndPort: 80,
Created: time.Now().Unix(),
},
},
ServiceEndpoints: []*EndpointPermission{
&EndpointPermission{
DomainOrIP: "",
Wildcard: true,
Permit: true,
Protocol: 17,
StartPort: 12345,
EndPort: 12347,
Created: time.Now().Unix(),
Type: EptAny,
Permit: true,
Protocol: 17,
StartPort: 12345,
EndPort: 12347,
Created: time.Now().Unix(),
},
&EndpointPermission{ // default deny
DomainOrIP: "",
Wildcard: true,
Permit: false,
Created: time.Now().Unix(),
Type: EptAny,
Permit: false,
Created: time.Now().Unix(),
},
},
}
@@ -104,25 +102,39 @@ func init() {
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)
t.Errorf("unexpected result: flag %s: active=%v, expected=%v", flagNames[flag], active, shouldBeActive)
}
}
func testEndpoint(t *testing.T, set *Set, domainOrIP string, protocol uint8, port uint16, inbound bool, shouldBePermitted bool) {
var permitted, ok bool
permitted, _, ok = set.CheckEndpoint(domainOrIP, protocol, port, inbound)
if !ok {
t.Errorf("endpoint %s/%d/%d/%v should be in test profile set", domainOrIP, protocol, port, inbound)
}
if permitted != shouldBePermitted {
t.Errorf("unexpected result for endpoint %s/%d/%d/%v: permitted=%v, expected=%v", domainOrIP, protocol, port, inbound, permitted, shouldBePermitted)
func testEndpointDomain(t *testing.T, set *Set, domain string, expectedResult EPResult) {
var result EPResult
result, _ = set.CheckEndpointDomain(domain)
if result != expectedResult {
t.Errorf(
"line %d: unexpected result for endpoint domain %s: result=%s, expected=%s",
testutils.GetLineNumberOfCaller(1),
domain,
result,
expectedResult,
)
}
}
func testUnregulatedEndpoint(t *testing.T, set *Set, domainOrIP string, protocol uint8, port uint16, inbound bool) {
_, _, ok := set.CheckEndpoint(domainOrIP, protocol, port, inbound)
if ok {
t.Errorf("endpoint %s/%d/%d/%v should not be in test profile set", domainOrIP, protocol, port, inbound)
func testEndpointIP(t *testing.T, set *Set, domain string, ip net.IP, protocol uint8, port uint16, inbound bool, expectedResult EPResult) {
var result EPResult
result, _ = set.CheckEndpointIP(domain, ip, protocol, port, inbound)
if result != expectedResult {
t.Errorf(
"line %d: unexpected result for endpoint %s/%s/%d/%d/%v: result=%s, expected=%s",
testutils.GetLineNumberOfCaller(1),
domain,
ip,
protocol,
port,
inbound,
result,
expectedResult,
)
}
}
@@ -133,28 +145,28 @@ func TestProfileSet(t *testing.T) {
set.Update(status.SecurityLevelDynamic)
testFlag(t, set, Whitelist, false)
// testFlag(t, set, Internet, true)
testEndpoint(t, set, "example.com.", 0, 0, false, true)
testEndpoint(t, set, "bad.example.com.", 0, 0, false, false)
testEndpoint(t, set, "other.bad.example.com.", 0, 0, false, false)
testEndpoint(t, set, "good.bad.example.com.", 0, 0, false, true)
testEndpoint(t, set, "bad2.example.com.", 0, 0, false, false)
testEndpoint(t, set, "10.2.3.4", 6, 22000, false, true)
testEndpoint(t, set, "fd00::1", 6, 22000, false, true)
testEndpoint(t, set, "test.local.", 6, 22000, false, true)
testUnregulatedEndpoint(t, set, "other.example.com.", 0, 0, false)
testUnregulatedEndpoint(t, set, "10.2.3.4", 17, 53, false)
testUnregulatedEndpoint(t, set, "10.2.3.4", 17, 443, false)
testUnregulatedEndpoint(t, set, "10.2.3.4", 6, 12346, false)
testEndpoint(t, set, "10.2.3.4", 17, 12345, true, true)
testEndpoint(t, set, "fd00::1", 17, 12347, true, true)
testEndpointDomain(t, set, "example.com.", Permitted)
testEndpointDomain(t, set, "bad.example.com.", Denied)
testEndpointDomain(t, set, "other.bad.example.com.", Denied)
testEndpointDomain(t, set, "good.bad.example.com.", Permitted)
testEndpointDomain(t, set, "bad2.example.com.", Undeterminable)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 22000, false, Permitted)
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 6, 22000, false, Permitted)
testEndpointDomain(t, set, "test.local.", Undeterminable)
testEndpointDomain(t, set, "other.example.com.", Undeterminable)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 53, false, NoMatch)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 443, false, NoMatch)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 12346, false, NoMatch)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 12345, true, Permitted)
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 17, 12347, true, Permitted)
set.Update(status.SecurityLevelSecure)
// testFlag(t, set, Internet, true)
set.Update(status.SecurityLevelFortress) // Independent!
testFlag(t, set, Whitelist, true)
testEndpoint(t, set, "10.2.3.4", 17, 12345, true, false)
testEndpoint(t, set, "fd00::1", 17, 12347, true, false)
testUnregulatedEndpoint(t, set, "10.2.3.4", 6, 80, false)
testUnregulatedEndpoint(t, set, "bad2.example.com.", 0, 0, false)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 17, 12345, true, Denied)
testEndpointIP(t, set, "", net.ParseIP("fd00::1"), 17, 12347, true, Denied)
testEndpointIP(t, set, "", net.ParseIP("10.2.3.4"), 6, 80, false, NoMatch)
testEndpointDomain(t, set, "bad2.example.com.", Undeterminable)
}

View File

@@ -48,8 +48,7 @@ func getSpecialProfile(ID string) (*Profile, error) {
func ensureServiceEndpointsDenyAll(p *Profile) (changed bool) {
for _, ep := range p.ServiceEndpoints {
if ep != nil {
if ep.DomainOrIP == "" &&
ep.Wildcard == true &&
if ep.Type == EptAny &&
ep.Protocol == 0 &&
ep.StartPort == 0 &&
ep.EndPort == 0 &&
@@ -60,12 +59,11 @@ func ensureServiceEndpointsDenyAll(p *Profile) (changed bool) {
}
p.ServiceEndpoints = append(p.ServiceEndpoints, &EndpointPermission{
DomainOrIP: "",
Wildcard: true,
Protocol: 0,
StartPort: 0,
EndPort: 0,
Permit: false,
Type: EptAny,
Protocol: 0,
StartPort: 0,
EndPort: 0,
Permit: false,
})
return true
}

View File

@@ -2,6 +2,7 @@ package profile
import (
"strings"
"sync/atomic"
"github.com/Safing/portbase/database"
"github.com/Safing/portbase/database/query"
@@ -56,11 +57,27 @@ func updateListener(sub *database.Subscription) {
switch {
case strings.HasPrefix(profile.Key(), MakeProfileKey(UserNamespace, "")):
updateActiveUserProfile(profile)
increaseUpdateVersion()
case strings.HasPrefix(profile.Key(), MakeProfileKey(StampNamespace, "")):
updateActiveStampProfile(profile)
increaseUpdateVersion()
}
}
}
}
}
var (
updateVersion uint32
)
// GetUpdateVersion returns the current profiles internal update version
func GetUpdateVersion() uint32 {
return atomic.LoadUint32(&updateVersion)
}
func increaseUpdateVersion() {
// we intentially want to wrap
atomic.AddUint32(&updateVersion, 1)
}