mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Revamp Profile Domains and Ports to Endpoints and ServiceEndpoints
This commit is contained in:
parent
4017de7dac
commit
bde81d815d
13 changed files with 417 additions and 249 deletions
38
intel/main_test.go
Normal file
38
intel/main_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package intel
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Safing/portbase/database/dbmodule"
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portbase/modules"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// setup
|
||||
testDir := os.TempDir()
|
||||
dbmodule.SetDatabaseLocation(testDir)
|
||||
err := modules.Start()
|
||||
if err != nil {
|
||||
if err == modules.ErrCleanExit {
|
||||
os.Exit(0)
|
||||
} else {
|
||||
err = modules.Shutdown()
|
||||
if err != nil {
|
||||
log.Shutdown()
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// run tests
|
||||
rv := m.Run()
|
||||
|
||||
// teardown
|
||||
modules.Shutdown()
|
||||
os.RemoveAll(testDir)
|
||||
|
||||
// exit with test run return value
|
||||
os.Exit(rv)
|
||||
}
|
72
intel/reverse.go
Normal file
72
intel/reverse.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package intel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// ResolveIPAndValidate finds (reverse DNS), validates (forward DNS) and returns the domain name assigned to the given IP.
|
||||
func ResolveIPAndValidate(ip string, securityLevel uint8) (domain string, err error) {
|
||||
// get reversed DNS address
|
||||
rQ, err := dns.ReverseAddr(ip)
|
||||
if err != nil {
|
||||
log.Tracef("intel: failed to get reverse address of %s: %s", ip, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get PTR record
|
||||
rrCache := Resolve(rQ, dns.Type(dns.TypePTR), securityLevel)
|
||||
if rrCache == nil {
|
||||
return "", errors.New("querying for PTR record failed (may be NXDomain)")
|
||||
}
|
||||
|
||||
// get result from record
|
||||
var ptrName string
|
||||
for _, rr := range rrCache.Answer {
|
||||
ptrRec, ok := rr.(*dns.PTR)
|
||||
if ok {
|
||||
ptrName = ptrRec.Ptr
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// check for nxDomain
|
||||
if ptrName == "" {
|
||||
return "", errors.New("no PTR record for IP (nxDomain)")
|
||||
}
|
||||
|
||||
log.Infof("ptrName: %s", ptrName)
|
||||
|
||||
// get forward record
|
||||
if strings.Contains(ip, ":") {
|
||||
rrCache = Resolve(ptrName, dns.Type(dns.TypeAAAA), securityLevel)
|
||||
} else {
|
||||
rrCache = Resolve(ptrName, dns.Type(dns.TypeA), securityLevel)
|
||||
}
|
||||
if rrCache == nil {
|
||||
return "", errors.New("querying for A/AAAA record failed (may be NXDomain)")
|
||||
}
|
||||
|
||||
// check for matching A/AAAA record
|
||||
log.Infof("rr: %s", rrCache)
|
||||
for _, rr := range rrCache.Answer {
|
||||
switch v := rr.(type) {
|
||||
case *dns.A:
|
||||
log.Infof("A: %s", v.A.String())
|
||||
if ip == v.A.String() {
|
||||
return ptrName, nil
|
||||
}
|
||||
case *dns.AAAA:
|
||||
log.Infof("AAAA: %s", v.AAAA.String())
|
||||
if ip == v.AAAA.String() {
|
||||
return ptrName, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no match
|
||||
return "", errors.New("validation failed")
|
||||
}
|
28
intel/reverse_test.go
Normal file
28
intel/reverse_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package intel
|
||||
|
||||
import "testing"
|
||||
|
||||
func testReverse(t *testing.T, ip, result, expectedErr string) {
|
||||
domain, err := ResolveIPAndValidate(ip, 0)
|
||||
if err != nil {
|
||||
if expectedErr == "" || err.Error() != expectedErr {
|
||||
t.Errorf("reverse-validating %s: unexpected error: %s", ip, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if domain != result {
|
||||
t.Errorf("reverse-validating %s: unexpected result: %s", ip, domain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveIPAndValidate(t *testing.T) {
|
||||
testReverse(t, "198.41.0.4", "a.root-servers.net.", "")
|
||||
testReverse(t, "9.9.9.9", "dns.quad9.net.", "")
|
||||
testReverse(t, "2620:fe::fe", "dns.quad9.net.", "")
|
||||
testReverse(t, "1.1.1.1", "one.one.one.one.", "")
|
||||
testReverse(t, "2606:4700:4700::1111", "one.one.one.one.", "")
|
||||
|
||||
testReverse(t, "93.184.216.34", "example.com.", "no PTR record for IP (nxDomain)")
|
||||
testReverse(t, "185.199.109.153", "sites.github.io.", "no PTR record for IP (nxDomain)")
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
|
@ -32,50 +30,14 @@ func makeDefaultFallbackProfile() *Profile {
|
|||
Related: status.SecurityLevelDynamic,
|
||||
PeerToPeer: status.SecurityLevelDynamic,
|
||||
},
|
||||
Ports: map[int16][]*Port{
|
||||
6: []*Port{
|
||||
&Port{ // SSH
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 22,
|
||||
End: 22,
|
||||
},
|
||||
&Port{ // HTTP
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 80,
|
||||
End: 80,
|
||||
},
|
||||
&Port{ // HTTPS
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 443,
|
||||
End: 443,
|
||||
},
|
||||
&Port{ // SMTP (TLS)
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 465,
|
||||
End: 465,
|
||||
},
|
||||
&Port{ // SMTP (STARTTLS)
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 587,
|
||||
End: 587,
|
||||
},
|
||||
&Port{ // IMAP (TLS)
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 993,
|
||||
End: 993,
|
||||
},
|
||||
&Port{ // IMAP (STARTTLS)
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 143,
|
||||
End: 143,
|
||||
},
|
||||
ServiceEndpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@ package profile
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portmaster/intel"
|
||||
)
|
||||
|
||||
// Endpoints is a list of permitted or denied endpoints.
|
||||
|
@ -10,13 +13,13 @@ type Endpoints []*EndpointPermission
|
|||
|
||||
// EndpointPermission holds a decision about an endpoint.
|
||||
type EndpointPermission struct {
|
||||
DomainOrIP string
|
||||
IncludeSubdomains bool
|
||||
Protocol uint8
|
||||
PortStart uint16
|
||||
PortEnd uint16
|
||||
Permit bool
|
||||
Created int64
|
||||
DomainOrIP string
|
||||
Wildcard bool
|
||||
Protocol uint8
|
||||
StartPort uint16
|
||||
EndPort uint16
|
||||
Permit bool
|
||||
Created int64
|
||||
}
|
||||
|
||||
// IsSet returns whether the Endpoints object is "set".
|
||||
|
@ -28,59 +31,105 @@ func (e Endpoints) IsSet() bool {
|
|||
}
|
||||
|
||||
// Check checks if the given domain is governed in the list of domains and returns whether it is permitted.
|
||||
func (e Endpoints) Check(domainOrIP string, protocol uint8, port uint16) (permit, ok bool) {
|
||||
// check for exact domain
|
||||
ed, ok := d[domain]
|
||||
if ok {
|
||||
return ed.Permit, true
|
||||
}
|
||||
// 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) {
|
||||
|
||||
for _, entry := range e {
|
||||
if entry.Matches(domainOrIP, protocol, port) {
|
||||
return entry.Permit, true
|
||||
// ip resolving
|
||||
var cachedGetDomainOfIP func() string
|
||||
if checkReverseIP {
|
||||
var ipResolved bool
|
||||
var ipName string
|
||||
// setup caching wrapper
|
||||
cachedGetDomainOfIP = func() string {
|
||||
if !ipResolved {
|
||||
result, err := intel.ResolveIPAndValidate(domainOrIP, securityLevel)
|
||||
if err != nil {
|
||||
// log.Debug()
|
||||
ipName = result
|
||||
}
|
||||
ipResolved = true
|
||||
}
|
||||
return ipName
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
isDomain := strings.HasSuffix(domainOrIP, ".")
|
||||
|
||||
for _, entry := range e {
|
||||
if ok, reason := entry.Matches(domainOrIP, protocol, port, isDomain, cachedGetDomainOfIP); ok {
|
||||
return entry.Permit, reason, true
|
||||
}
|
||||
}
|
||||
|
||||
return false, "", false
|
||||
}
|
||||
|
||||
// Matches checks whether a port object matches the given port.
|
||||
func (ep EndpointPermission) Matches(domainOrIP string, protocol uint8, port uint16) bool {
|
||||
if domainOrIP != ep.DomainOrIP {
|
||||
return false
|
||||
}
|
||||
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
|
||||
return false, ""
|
||||
}
|
||||
|
||||
if ep.PortStart > 0 && (port < ep.PortStart || port > ep.PortEnd) {
|
||||
return false
|
||||
if ep.StartPort > 0 && (port < ep.StartPort || port > ep.EndPort) {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
return true
|
||||
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)
|
||||
default:
|
||||
// no match
|
||||
return false, ""
|
||||
}
|
||||
}
|
||||
|
||||
func (e Endpoints) String() string {
|
||||
var s []string
|
||||
for _, entry := range e {
|
||||
s = append(s, entry.String())
|
||||
}
|
||||
return fmt.Sprintf("[%s]", strings.Join(s, ", "))
|
||||
}
|
||||
|
||||
func (ep EndpointPermission) String() string {
|
||||
s := ep.DomainOrIP
|
||||
|
||||
if ep.Protocol > 0 || ep.Start {
|
||||
s += " "
|
||||
}
|
||||
s += " "
|
||||
|
||||
if ep.Protocol > 0 {
|
||||
s += strconv.Itoa(int(ep.Protocol))
|
||||
if ep.Start > 0 {
|
||||
s += "/"
|
||||
}
|
||||
} else {
|
||||
s += "*"
|
||||
}
|
||||
|
||||
if ep.Start > 0 {
|
||||
if p.Start == p.End {
|
||||
s += strconv.Itoa(int(ep.Start))
|
||||
s += "/"
|
||||
|
||||
if ep.StartPort > 0 {
|
||||
if ep.StartPort == ep.EndPort {
|
||||
s += strconv.Itoa(int(ep.StartPort))
|
||||
} else {
|
||||
s += fmt.Sprintf("%d-%d", ep.Start, ep.End)
|
||||
s += fmt.Sprintf("%d-%d", ep.StartPort, ep.EndPort)
|
||||
}
|
||||
} else {
|
||||
s += "*"
|
||||
}
|
||||
|
||||
return s
|
||||
|
|
|
@ -2,47 +2,60 @@ package profile
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPorts(t *testing.T) {
|
||||
var ports Ports
|
||||
ports = map[int16][]*Port{
|
||||
6: []*Port{
|
||||
&Port{ // SSH
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 22,
|
||||
End: 22,
|
||||
},
|
||||
// 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 TestEPString(t *testing.T) {
|
||||
var endpoints Endpoints
|
||||
endpoints = []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "example.com",
|
||||
Wildcard: false,
|
||||
Protocol: 6,
|
||||
Permit: true,
|
||||
},
|
||||
-17: []*Port{
|
||||
&Port{ // HTTP
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 80,
|
||||
End: 81,
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "8.8.8.8",
|
||||
Protocol: 17, // TCP
|
||||
StartPort: 53, // DNS
|
||||
EndPort: 53,
|
||||
Permit: false,
|
||||
},
|
||||
93: []*Port{
|
||||
&Port{ // HTTP
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 93,
|
||||
End: 93,
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "google.com",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
},
|
||||
}
|
||||
if ports.String() != "TCP:[permit:22], <UDP:[deny:80-81], 93:[permit:93]" &&
|
||||
ports.String() != "93:[permit:93], TCP:[permit:22], <UDP:[deny:80-81]" &&
|
||||
ports.String() != "<UDP:[deny:80-81], 93:[permit:93], TCP:[permit:22]" {
|
||||
t.Errorf("unexpected result: %s", ports.String())
|
||||
if endpoints.String() != "[example.com 6/*, 8.8.8.8 17/53, google.com */*]" {
|
||||
t.Errorf("unexpected result: %s", endpoints.String())
|
||||
}
|
||||
|
||||
var noPorts Ports
|
||||
noPorts = map[int16][]*Port{}
|
||||
if noPorts.String() != "None" {
|
||||
t.Errorf("unexpected result: %s", ports.String())
|
||||
var noEndpoints Endpoints
|
||||
noEndpoints = []*EndpointPermission{}
|
||||
if noEndpoints.String() != "[]" {
|
||||
t.Errorf("unexpected result: %s", noEndpoints.String())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package profile
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portmaster/status"
|
||||
|
@ -115,7 +116,7 @@ func (flags Flags) String() string {
|
|||
markedFlags = append(markedFlags, s)
|
||||
}
|
||||
}
|
||||
return strings.Join(markedFlags, ", ")
|
||||
return fmt.Sprintf("[%s]", strings.Join(markedFlags, ", "))
|
||||
}
|
||||
|
||||
// Add adds a flag to the Flags with the given level.
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestProfileFlags(t *testing.T) {
|
|||
RequireGate17: status.SecurityLevelsSecureAndFortress,
|
||||
}
|
||||
|
||||
if testFlags.String() != "Prompt, Internet++-, LAN++-, Localhost, Related+--, RequireGate17-++" {
|
||||
if testFlags.String() != "[Prompt, Internet++-, LAN++-, Localhost, Related+--, RequireGate17-++]" {
|
||||
t.Errorf("unexpected output: %s", testFlags.String())
|
||||
}
|
||||
|
||||
|
|
|
@ -37,10 +37,10 @@ type Profile struct {
|
|||
Fingerprints []*Fingerprint
|
||||
|
||||
// The mininum security level to apply to connections made with this profile
|
||||
SecurityLevel uint8
|
||||
Flags Flags
|
||||
Domains Domains
|
||||
Ports Ports
|
||||
SecurityLevel uint8
|
||||
Flags Flags
|
||||
Endpoints Endpoints
|
||||
ServiceEndpoints Endpoints
|
||||
|
||||
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
|
||||
// Framework *Framework `json:",omitempty bson:",omitempty"`
|
||||
|
@ -97,7 +97,7 @@ func (profile *Profile) String() string {
|
|||
|
||||
// DetailedString returns a more detailed string representation of theProfile.
|
||||
func (profile *Profile) DetailedString() string {
|
||||
return fmt.Sprintf("%s(SL=%s Flags=[%s] Ports=[%s] #Domains=%d)", profile.Name, status.FmtSecurityLevel(profile.SecurityLevel), profile.Flags.String(), profile.Ports.String(), len(profile.Domains))
|
||||
return fmt.Sprintf("%s(SL=%s Flags=%s Endpoints=%s)", profile.Name, status.FmtSecurityLevel(profile.SecurityLevel), profile.Flags.String(), profile.Endpoints.String())
|
||||
}
|
||||
|
||||
// GetUserProfile loads a profile from the database.
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
var (
|
||||
emptyFlags = Flags{}
|
||||
emptyPorts = Ports{}
|
||||
)
|
||||
|
||||
// Set handles Profile chaining.
|
||||
|
@ -120,8 +119,8 @@ func (set *Set) CheckFlag(flag uint8) (active bool) {
|
|||
return false
|
||||
}
|
||||
|
||||
// CheckDomain checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
|
||||
func (set *Set) CheckDomain(domain string) (permit, ok bool) {
|
||||
// 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) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
|
||||
|
@ -131,39 +130,19 @@ func (set *Set) CheckDomain(domain string) (permit, ok bool) {
|
|||
}
|
||||
|
||||
if profile != nil {
|
||||
permit, ok = profile.Domains.Check(domain)
|
||||
if ok {
|
||||
return
|
||||
if inbound {
|
||||
if permit, reason, ok = profile.ServiceEndpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if permit, reason, ok = profile.Endpoints.Check(domainOrIP, protocol, port, inbound, set.combinedSecurityLevel); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
// CheckPort checks if the given protocol and port are governed in any the lists of ports and returns whether it is permitted.
|
||||
func (set *Set) CheckPort(listen bool, protocol uint8, port uint16) (permit, ok bool) {
|
||||
set.Lock()
|
||||
defer set.Unlock()
|
||||
|
||||
signedProtocol := int16(protocol)
|
||||
if listen {
|
||||
signedProtocol = -1 * signedProtocol
|
||||
}
|
||||
|
||||
for i, profile := range set.profiles {
|
||||
if i == 2 && set.independent {
|
||||
continue
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
if permit, ok = profile.Ports.Check(signedProtocol, port); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
return false, "", false
|
||||
}
|
||||
|
||||
// getSecurityLevel returns the highest prioritized security level.
|
||||
|
|
|
@ -26,26 +26,33 @@ func init() {
|
|||
Flags: map[uint8]uint8{
|
||||
Independent: status.SecurityLevelFortress,
|
||||
},
|
||||
Domains: map[string]*DomainDecision{
|
||||
"example.com": &DomainDecision{
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
IncludeSubdomains: false,
|
||||
Endpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "good.bad.example.com.",
|
||||
Wildcard: false,
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
"bad.example.com": &DomainDecision{
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
IncludeSubdomains: true,
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "bad.example.com.",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
Ports: map[int16][]*Port{
|
||||
6: []*Port{
|
||||
&Port{
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 22000,
|
||||
End: 22000,
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "example.com.",
|
||||
Wildcard: false,
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 22000,
|
||||
EndPort: 22000,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -54,37 +61,41 @@ func init() {
|
|||
ID: "unit-test-stamp",
|
||||
Name: "Unit Test Stamp Profile",
|
||||
SecurityLevel: status.SecurityLevelFortress,
|
||||
Flags: map[uint8]uint8{
|
||||
Internet: status.SecurityLevelsAll,
|
||||
},
|
||||
Domains: map[string]*DomainDecision{
|
||||
"bad2.example.com": &DomainDecision{
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
IncludeSubdomains: true,
|
||||
// Flags: map[uint8]uint8{
|
||||
// Internet: status.SecurityLevelsAll,
|
||||
// },
|
||||
Endpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "bad2.example.com.",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
"good.bad.example.com": &DomainDecision{
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
IncludeSubdomains: false,
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 6,
|
||||
StartPort: 80,
|
||||
EndPort: 80,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
Ports: map[int16][]*Port{
|
||||
6: []*Port{
|
||||
&Port{
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 80,
|
||||
End: 80,
|
||||
},
|
||||
ServiceEndpoints: []*EndpointPermission{
|
||||
&EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: true,
|
||||
Protocol: 17,
|
||||
StartPort: 12345,
|
||||
EndPort: 12347,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
-17: []*Port{
|
||||
&Port{
|
||||
Permit: true,
|
||||
Created: time.Now().Unix(),
|
||||
Start: 12345,
|
||||
End: 12347,
|
||||
},
|
||||
&EndpointPermission{ // default deny
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Permit: false,
|
||||
Created: time.Now().Unix(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -97,37 +108,21 @@ func testFlag(t *testing.T, set *Set, flag uint8, shouldBeActive bool) {
|
|||
}
|
||||
}
|
||||
|
||||
func testDomain(t *testing.T, set *Set, domain string, shouldBePermitted bool) {
|
||||
permitted, ok := set.CheckDomain(domain)
|
||||
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("domain %s should be in test profile set", domain)
|
||||
t.Errorf("endpoint %s/%d/%d/%v should be in test profile set", domainOrIP, protocol, port, inbound)
|
||||
}
|
||||
if permitted != shouldBePermitted {
|
||||
t.Errorf("unexpected result: domain %s: permitted=%v, expected=%v", domain, permitted, shouldBePermitted)
|
||||
t.Errorf("unexpected result for endpoint %s/%d/%d/%v: permitted=%v, expected=%v", domainOrIP, protocol, port, inbound, permitted, shouldBePermitted)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnregulatedDomain(t *testing.T, set *Set, domain string) {
|
||||
_, ok := set.CheckDomain(domain)
|
||||
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("domain %s should not be in test profile set", domain)
|
||||
}
|
||||
}
|
||||
|
||||
func testPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16, shouldBePermitted bool) {
|
||||
permitted, ok := set.CheckPort(listen, protocol, port)
|
||||
if !ok {
|
||||
t.Errorf("port [%v %d %d] should be in test profile set", listen, protocol, port)
|
||||
}
|
||||
if permitted != shouldBePermitted {
|
||||
t.Errorf("unexpected result: port [%v %d %d]: permitted=%v, expected=%v", listen, protocol, port, permitted, shouldBePermitted)
|
||||
}
|
||||
}
|
||||
|
||||
func testUnregulatedPort(t *testing.T, set *Set, listen bool, protocol uint8, port uint16) {
|
||||
_, ok := set.CheckPort(listen, protocol, port)
|
||||
if ok {
|
||||
t.Errorf("port [%v %d %d] should not be in test profile set", listen, protocol, port)
|
||||
t.Errorf("endpoint %s/%d/%d/%v should not be in test profile set", domainOrIP, protocol, port, inbound)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,31 +132,29 @@ func TestProfileSet(t *testing.T) {
|
|||
|
||||
set.Update(status.SecurityLevelDynamic)
|
||||
testFlag(t, set, Whitelist, false)
|
||||
testFlag(t, set, Internet, true)
|
||||
testDomain(t, set, "example.com", true)
|
||||
testDomain(t, set, "bad.example.com", false)
|
||||
testDomain(t, set, "other.bad.example.com", false)
|
||||
testDomain(t, set, "good.bad.example.com", false)
|
||||
testDomain(t, set, "bad2.example.com", false)
|
||||
testPort(t, set, false, 6, 443, true)
|
||||
testPort(t, set, false, 6, 143, true)
|
||||
testPort(t, set, false, 6, 22, true)
|
||||
testPort(t, set, false, 6, 80, false)
|
||||
testPort(t, set, false, 6, 80, false)
|
||||
testPort(t, set, true, 17, 12345, true)
|
||||
testPort(t, set, true, 17, 12346, true)
|
||||
testPort(t, set, true, 17, 12347, true)
|
||||
testUnregulatedDomain(t, set, "other.example.com")
|
||||
testUnregulatedPort(t, set, false, 17, 53)
|
||||
testUnregulatedPort(t, set, false, 17, 443)
|
||||
testUnregulatedPort(t, set, true, 6, 443)
|
||||
// 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)
|
||||
|
||||
set.Update(status.SecurityLevelSecure)
|
||||
testFlag(t, set, Internet, true)
|
||||
// testFlag(t, set, Internet, true)
|
||||
|
||||
set.Update(status.SecurityLevelFortress) // Independent!
|
||||
testFlag(t, set, Internet, false)
|
||||
testPort(t, set, false, 6, 80, true)
|
||||
testUnregulatedDomain(t, set, "bad2.example.com")
|
||||
testUnregulatedPort(t, set, true, 17, 12346)
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -33,8 +33,10 @@ func initSpecialProfiles() (err error) {
|
|||
return err
|
||||
}
|
||||
fallbackProfile = makeDefaultFallbackProfile()
|
||||
ensureServiceEndpointsDenyAll(fallbackProfile)
|
||||
fallbackProfile.Save(SpecialNamespace)
|
||||
}
|
||||
ensureServiceEndpointsDenyAll(fallbackProfile)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -42,3 +44,26 @@ func initSpecialProfiles() (err error) {
|
|||
func getSpecialProfile(ID string) (*Profile, error) {
|
||||
return getProfile(SpecialNamespace, ID)
|
||||
}
|
||||
|
||||
func ensureServiceEndpointsDenyAll(p *Profile) (changed bool) {
|
||||
for _, ep := range p.ServiceEndpoints {
|
||||
if ep.DomainOrIP == "" &&
|
||||
ep.Wildcard == true &&
|
||||
ep.Protocol == 0 &&
|
||||
ep.StartPort == 0 &&
|
||||
ep.EndPort == 0 &&
|
||||
ep.Permit == false {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
p.ServiceEndpoints = append(p.ServiceEndpoints, &EndpointPermission{
|
||||
DomainOrIP: "",
|
||||
Wildcard: true,
|
||||
Protocol: 0,
|
||||
StartPort: 0,
|
||||
EndPort: 0,
|
||||
Permit: false,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -35,12 +35,20 @@ func updateListener(sub *database.Subscription) {
|
|||
continue
|
||||
}
|
||||
|
||||
switch profile.ID {
|
||||
case "global":
|
||||
switch profile.DatabaseKey() {
|
||||
case "profiles/special/global":
|
||||
specialProfileLock.Lock()
|
||||
globalProfile = profile
|
||||
specialProfileLock.Unlock()
|
||||
case "fallback":
|
||||
case "profiles/special/fallback":
|
||||
profile.Lock()
|
||||
if ensureServiceEndpointsDenyAll(profile) {
|
||||
profile.Unlock()
|
||||
profile.Save(SpecialNamespace)
|
||||
continue
|
||||
}
|
||||
profile.Unlock()
|
||||
|
||||
specialProfileLock.Lock()
|
||||
fallbackProfile = profile
|
||||
specialProfileLock.Unlock()
|
||||
|
|
Loading…
Add table
Reference in a new issue