Improve endpoint/rule lists and filtering of DNS requests

This commit is contained in:
Daniel 2021-08-19 23:29:29 +02:00
parent 1b4a7568f2
commit f34dccb8f3
8 changed files with 52 additions and 45 deletions

View file

@ -54,13 +54,6 @@ var defaultDeciders = []deciderFn{
checkAutoPermitRelated, checkAutoPermitRelated,
} }
var dnsFromSystemResolverDeciders = []deciderFn{
checkEndpointListsForSystemResolverDNSRequests,
checkConnectivityDomain,
checkBypassPrevention,
checkFilterLists,
}
// DecideOnConnection makes a decision about a connection. // DecideOnConnection makes a decision about a connection.
// When called, the connection and profile is already locked. // When called, the connection and profile is already locked.
func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) { func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packet.Packet) {
@ -99,25 +92,18 @@ func DecideOnConnection(ctx context.Context, conn *network.Connection, pkt packe
conn.Entity.EnableCNAMECheck(ctx, layeredProfile.FilterCNAMEs()) conn.Entity.EnableCNAMECheck(ctx, layeredProfile.FilterCNAMEs())
conn.Entity.LoadLists(ctx) conn.Entity.LoadLists(ctx)
// DNS request from the system resolver require a special decision process,
// because the original requesting process is not known. Here, we only check
// global-only and the most important per-app aspects. The resulting
// connection is then blocked when the original requesting process is known.
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
// Run all deciders and return if they came to a conclusion.
done, _ := runDeciders(ctx, dnsFromSystemResolverDeciders, conn, layeredProfile, pkt)
if !done {
conn.Accept("allowing system resolver dns request", noReasonOptionKey)
}
return
}
// Run all deciders and return if they came to a conclusion. // Run all deciders and return if they came to a conclusion.
done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt) done, defaultAction := runDeciders(ctx, defaultDeciders, conn, layeredProfile, pkt)
if done { if done {
return return
} }
// DNS Request are always default allowed, as the endpoint lists could not
// be checked fully.
if conn.Type == network.DNSRequest {
conn.Accept("allowing dns request", noReasonOptionKey)
}
// Deciders did not conclude, use default action. // Deciders did not conclude, use default action.
switch defaultAction { switch defaultAction {
case profile.DefaultActionPermit: case profile.DefaultActionPermit:
@ -197,6 +183,14 @@ func checkSelfCommunication(ctx context.Context, conn *network.Connection, _ *pr
} }
func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
// DNS request from the system resolver require a special decision process,
// because the original requesting process is not known. Here, we only check
// global-only and the most important per-app aspects. The resulting
// connection is then blocked when the original requesting process is known.
if conn.Type == network.DNSRequest && conn.Process().IsSystemResolver() {
return checkEndpointListsForSystemResolverDNSRequests(ctx, conn, p)
}
var result endpoints.EPResult var result endpoints.EPResult
var reason endpoints.Reason var reason endpoints.Reason
@ -210,7 +204,7 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil
optionKey = profile.CfgOptionEndpointsKey optionKey = profile.CfgOptionEndpointsKey
} }
switch result { switch result {
case endpoints.Denied: case endpoints.Denied, endpoints.MatchError:
conn.DenyWithContext(reason.String(), optionKey, reason.Context()) conn.DenyWithContext(reason.String(), optionKey, reason.Context())
return true return true
case endpoints.Permitted: case endpoints.Permitted:
@ -225,13 +219,13 @@ func checkEndpointLists(ctx context.Context, conn *network.Connection, p *profil
// checkEndpointLists that is only meant for DNS queries by the system // checkEndpointLists that is only meant for DNS queries by the system
// resolver. It only checks the endpoint filter list of the local profile and // resolver. It only checks the endpoint filter list of the local profile and
// does not include the global profile. // does not include the global profile.
func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { func checkEndpointListsForSystemResolverDNSRequests(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile) bool {
profileEndpoints := p.LocalProfile().GetEndpoints() profileEndpoints := p.LocalProfile().GetEndpoints()
if profileEndpoints.IsSet() { if profileEndpoints.IsSet() {
result, reason := profileEndpoints.Match(ctx, conn.Entity) result, reason := profileEndpoints.Match(ctx, conn.Entity)
if endpoints.IsDecision(result) { if endpoints.IsDecision(result) {
switch result { switch result {
case endpoints.Denied: case endpoints.Denied, endpoints.MatchError:
conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context()) conn.DenyWithContext(reason.String(), profile.CfgOptionEndpointsKey, reason.Context())
return true return true
case endpoints.Permitted: case endpoints.Permitted:
@ -396,11 +390,13 @@ func checkResolverScope(_ context.Context, conn *network.Connection, p *profile.
} }
func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool { func checkDomainHeuristics(ctx context.Context, conn *network.Connection, p *profile.LayeredProfile, _ packet.Packet) bool {
if !p.DomainHeuristics() { // Don't check if no domain is available.
if conn.Entity.Domain == "" {
return false return false
} }
if conn.Entity.Domain == "" { // Check if domain heuristics are enabled.
if !p.DomainHeuristics() {
return false return false
} }
@ -485,10 +481,11 @@ func checkAutoPermitRelated(_ context.Context, conn *network.Connection, p *prof
// checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware. // checkRelation tries to find a relation between a process and a communication. This is for better out of the box experience and is _not_ meant to thwart intentional malware.
func checkRelation(conn *network.Connection) (related bool, reason string) { func checkRelation(conn *network.Connection) (related bool, reason string) {
if conn.Entity.Domain != "" { // Don't check if no domain is available.
if conn.Entity.Domain == "" {
return false, "" return false, ""
} }
// don't check for unknown processes // Don't check for unknown processes.
if conn.Process().Pid < 0 { if conn.Process().Pid < 0 {
return false, "" return false, ""
} }

View file

@ -22,9 +22,18 @@ type EndpointASN struct {
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
func (ep *EndpointASN) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) { func (ep *EndpointASN) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
if entity.IP == nil {
return NoMatch, nil
}
if !entity.IPScope.IsGlobal() {
return NoMatch, nil
}
asn, ok := entity.GetASN(ctx) asn, ok := entity.GetASN(ctx)
if !ok { if !ok {
return Undeterminable, nil asnStr := strconv.Itoa(int(ep.ASN))
return MatchError, ep.makeReason(ep, asnStr, "ASN data not available to match")
} }
if asn == ep.ASN { if asn == ep.ASN {

View file

@ -21,9 +21,17 @@ type EndpointCountry struct {
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) { func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (EPResult, Reason) {
if entity.IP == nil {
return NoMatch, nil
}
if !entity.IPScope.IsGlobal() {
return NoMatch, nil
}
country, ok := entity.GetCountry(ctx) country, ok := entity.GetCountry(ctx)
if !ok { if !ok {
return Undeterminable, nil return MatchError, ep.makeReason(ep, country, "country data not available to match")
} }
if country == ep.Country { if country == ep.Country {

View file

@ -17,7 +17,7 @@ type EndpointIP struct {
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
func (ep *EndpointIP) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { func (ep *EndpointIP) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
if entity.IP == nil { if entity.IP == nil {
return Undeterminable, nil return NoMatch, nil
} }
if ep.IP.Equal(entity.IP) { if ep.IP.Equal(entity.IP) {

View file

@ -17,8 +17,9 @@ type EndpointIPRange struct {
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
func (ep *EndpointIPRange) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { func (ep *EndpointIPRange) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
if entity.IP == nil { if entity.IP == nil {
return Undeterminable, nil return NoMatch, nil
} }
if ep.Net.Contains(entity.IP) { if ep.Net.Contains(entity.IP) {
return ep.match(ep, entity, ep.Net.String(), "IP is in") return ep.match(ep, entity, ep.Net.String(), "IP is in")
} }

View file

@ -33,7 +33,7 @@ type EndpointScope struct {
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) { func (ep *EndpointScope) Matches(_ context.Context, entity *intel.Entity) (EPResult, Reason) {
if entity.IP == nil { if entity.IP == nil {
return Undeterminable, nil return NoMatch, nil
} }
var scope uint8 var scope uint8

View file

@ -27,7 +27,7 @@ type EndpointBase struct { //nolint:maligned // TODO
func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keyval ...interface{}) (EPResult, Reason) { func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keyval ...interface{}) (EPResult, Reason) {
result := ep.matchesPPP(entity) result := ep.matchesPPP(entity)
if result == Undeterminable || result == NoMatch { if result == NoMatch {
return result, nil return result, nil
} }
@ -57,10 +57,6 @@ func (ep *EndpointBase) makeReason(s fmt.Stringer, value, desc string, keyval ..
func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) { func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
// only check if protocol is defined // only check if protocol is defined
if ep.Protocol > 0 { if ep.Protocol > 0 {
// if protocol is unknown, return Undeterminable
if entity.Protocol == 0 {
return Undeterminable
}
// if protocol does not match, return NoMatch // if protocol does not match, return NoMatch
if entity.Protocol != ep.Protocol { if entity.Protocol != ep.Protocol {
return NoMatch return NoMatch
@ -69,10 +65,6 @@ func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
// only check if port is defined // only check if port is defined
if ep.StartPort > 0 { if ep.StartPort > 0 {
// if port is unknown, return Undeterminable
if entity.DstPort() == 0 {
return Undeterminable
}
// if port does not match, return NoMatch // if port does not match, return NoMatch
if entity.DstPort() < ep.StartPort || entity.DstPort() > ep.EndPort { if entity.DstPort() < ep.StartPort || entity.DstPort() > ep.EndPort {
return NoMatch return NoMatch

View file

@ -17,7 +17,7 @@ type EPResult uint8
// Endpoint matching return values // Endpoint matching return values
const ( const (
NoMatch EPResult = iota NoMatch EPResult = iota
Undeterminable MatchError
Denied Denied
Permitted Permitted
) )
@ -25,7 +25,7 @@ const (
// IsDecision returns true if result represents a decision // IsDecision returns true if result represents a decision
// and false if result is NoMatch or Undeterminable. // and false if result is NoMatch or Undeterminable.
func IsDecision(result EPResult) bool { func IsDecision(result EPResult) bool {
return result == Denied || result == Permitted || result == Undeterminable return result == Denied || result == Permitted || result == MatchError
} }
// ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching. // ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching.
@ -88,8 +88,8 @@ func (epr EPResult) String() string {
switch epr { switch epr {
case NoMatch: case NoMatch:
return "No Match" return "No Match"
case Undeterminable: case MatchError:
return "Undeterminable" return "Match Error"
case Denied: case Denied:
return "Denied" return "Denied"
case Permitted: case Permitted: