Reevaluate and update firewall core logic
This commit is contained in:
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user