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