Add support for verdict and decision reason context
This commit is contained in:
@@ -8,8 +8,8 @@ type EndpointAny struct {
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointAny) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
return ep.matchesPPP(entity), "matches *"
|
||||
func (ep *EndpointAny) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
return ep.match(ep, entity, "*", "matches")
|
||||
}
|
||||
|
||||
func (ep *EndpointAny) String() string {
|
||||
|
||||
@@ -16,24 +16,22 @@ var (
|
||||
type EndpointASN struct {
|
||||
EndpointBase
|
||||
|
||||
ASN uint
|
||||
Reason string
|
||||
ASN uint
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointASN) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
if entity.IP == nil {
|
||||
return Undeterminable, ""
|
||||
}
|
||||
|
||||
func (ep *EndpointASN) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
asn, ok := entity.GetASN()
|
||||
if !ok {
|
||||
return Undeterminable, ""
|
||||
return Undeterminable, nil
|
||||
}
|
||||
|
||||
if asn == ep.ASN {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
asnStr := strconv.Itoa(int(ep.ASN))
|
||||
return ep.match(ep, entity, asnStr, "IP is part of AS")
|
||||
}
|
||||
return NoMatch, ""
|
||||
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointASN) String() string {
|
||||
@@ -48,8 +46,7 @@ func parseTypeASN(fields []string) (Endpoint, error) {
|
||||
}
|
||||
|
||||
ep := &EndpointASN{
|
||||
ASN: uint(asn),
|
||||
Reason: "IP is part of AS" + strconv.FormatInt(int64(asn), 10),
|
||||
ASN: uint(asn),
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
@@ -19,19 +19,16 @@ type EndpointCountry struct {
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointCountry) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
if entity.IP == nil {
|
||||
return Undeterminable, ""
|
||||
}
|
||||
|
||||
func (ep *EndpointCountry) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
country, ok := entity.GetCountry()
|
||||
if !ok {
|
||||
return Undeterminable, ""
|
||||
return Undeterminable, nil
|
||||
}
|
||||
|
||||
if country == ep.Country {
|
||||
return ep.matchesPPP(entity), "IP is located in " + country
|
||||
return ep.match(ep, entity, country, "IP is located in")
|
||||
}
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointCountry) String() string {
|
||||
|
||||
@@ -28,47 +28,48 @@ type EndpointDomain struct {
|
||||
Domain string
|
||||
DomainZone string
|
||||
MatchType uint8
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (ep *EndpointDomain) check(entity *intel.Entity, domain string) (EPResult, string) {
|
||||
func (ep *EndpointDomain) check(entity *intel.Entity, domain string) (EPResult, Reason) {
|
||||
result, reason := ep.match(ep, entity, ep.Domain, "domain matches")
|
||||
|
||||
switch ep.MatchType {
|
||||
case domainMatchTypeExact:
|
||||
if domain == ep.Domain {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
case domainMatchTypeZone:
|
||||
if domain == ep.Domain {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
if strings.HasSuffix(domain, ep.DomainZone) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
case domainMatchTypeSuffix:
|
||||
if strings.HasSuffix(domain, ep.Domain) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
case domainMatchTypePrefix:
|
||||
if strings.HasPrefix(domain, ep.Domain) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
case domainMatchTypeContains:
|
||||
if strings.Contains(domain, ep.Domain) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return result, reason
|
||||
}
|
||||
}
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointDomain) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
func (ep *EndpointDomain) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
if entity.Domain == "" {
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
result, reason = ep.check(entity, entity.Domain)
|
||||
result, reason := ep.check(entity, entity.Domain)
|
||||
if result != NoMatch {
|
||||
return
|
||||
return result, reason
|
||||
}
|
||||
|
||||
if entity.CNAMECheckEnabled() {
|
||||
@@ -80,7 +81,7 @@ func (ep *EndpointDomain) Matches(entity *intel.Entity) (result EPResult, reason
|
||||
}
|
||||
}
|
||||
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointDomain) String() string {
|
||||
@@ -93,7 +94,6 @@ func parseTypeDomain(fields []string) (Endpoint, error) {
|
||||
if domainRegex.MatchString(domain) || altDomainRegex.MatchString(domain) {
|
||||
ep := &EndpointDomain{
|
||||
OriginalValue: domain,
|
||||
Reason: "domain matches " + domain,
|
||||
}
|
||||
|
||||
// fix domain ending
|
||||
|
||||
@@ -10,19 +10,19 @@ import (
|
||||
type EndpointIP struct {
|
||||
EndpointBase
|
||||
|
||||
IP net.IP
|
||||
Reason string
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointIP) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
func (ep *EndpointIP) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
if entity.IP == nil {
|
||||
return Undeterminable, ""
|
||||
return Undeterminable, nil
|
||||
}
|
||||
|
||||
if ep.IP.Equal(entity.IP) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return ep.match(ep, entity, ep.IP.String(), "IP matches")
|
||||
}
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointIP) String() string {
|
||||
@@ -33,8 +33,7 @@ func parseTypeIP(fields []string) (Endpoint, error) {
|
||||
ip := net.ParseIP(fields[1])
|
||||
if ip != nil {
|
||||
ep := &EndpointIP{
|
||||
IP: ip,
|
||||
Reason: "IP is " + ip.String(),
|
||||
IP: ip,
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
@@ -10,19 +10,18 @@ import (
|
||||
type EndpointIPRange struct {
|
||||
EndpointBase
|
||||
|
||||
Net *net.IPNet
|
||||
Reason string
|
||||
Net *net.IPNet
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointIPRange) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
func (ep *EndpointIPRange) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
if entity.IP == nil {
|
||||
return Undeterminable, ""
|
||||
return Undeterminable, nil
|
||||
}
|
||||
if ep.Net.Contains(entity.IP) {
|
||||
return ep.matchesPPP(entity), ep.Reason
|
||||
return ep.match(ep, entity, ep.Net.String(), "IP is in")
|
||||
}
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointIPRange) String() string {
|
||||
@@ -33,8 +32,7 @@ func parseTypeIPRange(fields []string) (Endpoint, error) {
|
||||
_, net, err := net.ParseCIDR(fields[1])
|
||||
if err == nil {
|
||||
ep := &EndpointIPRange{
|
||||
Net: net,
|
||||
Reason: "IP is part of " + net.String(),
|
||||
Net: net,
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
@@ -12,18 +12,19 @@ type EndpointLists struct {
|
||||
|
||||
ListSet []string
|
||||
Lists string
|
||||
Reason string
|
||||
}
|
||||
|
||||
// Matches checks whether the given entity matches this endpoint definition.
|
||||
func (ep *EndpointLists) Matches(entity *intel.Entity) (result EPResult, reason string) {
|
||||
entity.LoadLists()
|
||||
|
||||
if entity.MatchLists(ep.ListSet) {
|
||||
return ep.matchesPPP(entity), entity.ListBlockReason().String()
|
||||
func (ep *EndpointLists) Matches(entity *intel.Entity) (EPResult, Reason) {
|
||||
if !entity.LoadLists() {
|
||||
return Undeterminable, nil
|
||||
}
|
||||
|
||||
return NoMatch, ""
|
||||
if entity.MatchLists(ep.ListSet) {
|
||||
return ep.match(ep, entity, ep.Lists, "filterlist contains", "filterlist", entity.ListBlockReason())
|
||||
}
|
||||
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (ep *EndpointLists) String() string {
|
||||
@@ -36,7 +37,6 @@ func parseTypeList(fields []string) (Endpoint, error) {
|
||||
ep := &EndpointLists{
|
||||
ListSet: lists,
|
||||
Lists: "L:" + strings.Join(lists, ","),
|
||||
Reason: "matched lists " + strings.Join(lists, ","),
|
||||
}
|
||||
return ep.parsePPP(ep, fields)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// Endpoint describes an Endpoint Matcher
|
||||
type Endpoint interface {
|
||||
Matches(entity *intel.Entity) (EPResult, string)
|
||||
Matches(entity *intel.Entity) (EPResult, Reason)
|
||||
String() string
|
||||
}
|
||||
|
||||
@@ -24,6 +24,35 @@ type EndpointBase struct { //nolint:maligned // TODO
|
||||
Permitted bool
|
||||
}
|
||||
|
||||
func (ep *EndpointBase) match(s fmt.Stringer, entity *intel.Entity, value, desc string, keval ...interface{}) (EPResult, Reason) {
|
||||
result := ep.matchesPPP(entity)
|
||||
if result == Undeterminable || result == NoMatch {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return result, ep.makeReason(s, value, desc)
|
||||
}
|
||||
|
||||
func (ep *EndpointBase) makeReason(s fmt.Stringer, value, desc string, keyval ...interface{}) Reason {
|
||||
r := &reason{
|
||||
description: desc,
|
||||
Filter: ep.renderPPP(s.String()),
|
||||
Permitted: ep.Permitted,
|
||||
Value: value,
|
||||
}
|
||||
|
||||
r.Extra = make(map[string]interface{})
|
||||
|
||||
for idx := 0; idx < int(len(keyval)/2); idx += 2 {
|
||||
key := keyval[idx]
|
||||
val := keyval[idx+1]
|
||||
|
||||
r.Extra[key.(string)] = val
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (ep *EndpointBase) matchesPPP(entity *intel.Entity) (result EPResult) {
|
||||
// only check if protocol is defined
|
||||
if ep.Protocol > 0 {
|
||||
|
||||
@@ -21,6 +21,12 @@ const (
|
||||
Permitted
|
||||
)
|
||||
|
||||
// IsDecision returns true if result represents a decision
|
||||
// and false if result is NoMatch or Undeterminable.
|
||||
func IsDecision(result EPResult) bool {
|
||||
return result == Denied || result == Permitted
|
||||
}
|
||||
|
||||
// ParseEndpoints parses a list of endpoints and returns a list of Endpoints for matching.
|
||||
func ParseEndpoints(entries []string) (Endpoints, error) {
|
||||
var firstErr error
|
||||
@@ -57,7 +63,7 @@ func (e Endpoints) IsSet() bool {
|
||||
}
|
||||
|
||||
// Match checks whether the given entity matches any of the endpoint definitions in the list.
|
||||
func (e Endpoints) Match(entity *intel.Entity) (result EPResult, reason string) {
|
||||
func (e Endpoints) Match(entity *intel.Entity) (result EPResult, reason Reason) {
|
||||
for _, entry := range e {
|
||||
if entry != nil {
|
||||
if result, reason = entry.Matches(entity); result != NoMatch {
|
||||
@@ -66,7 +72,7 @@ func (e Endpoints) Match(entity *intel.Entity) (result EPResult, reason string)
|
||||
}
|
||||
}
|
||||
|
||||
return NoMatch, ""
|
||||
return NoMatch, nil
|
||||
}
|
||||
|
||||
func (e Endpoints) String() string {
|
||||
|
||||
34
profile/endpoints/reason.go
Normal file
34
profile/endpoints/reason.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package endpoints
|
||||
|
||||
// Reason describes the reason why an endpoint has been
|
||||
// permitted or blocked.
|
||||
type Reason interface {
|
||||
// String should return a human readable string
|
||||
// describing the decision reason.
|
||||
String() string
|
||||
|
||||
// Context returns the context that was used
|
||||
// for the decision.
|
||||
Context() interface{}
|
||||
}
|
||||
|
||||
type reason struct {
|
||||
description string
|
||||
Filter string
|
||||
Value string
|
||||
Permitted bool
|
||||
Extra map[string]interface{}
|
||||
}
|
||||
|
||||
func (r *reason) String() string {
|
||||
prefix := "endpoint in blocklist: "
|
||||
if r.Permitted {
|
||||
prefix = "endpoint in whitelist: "
|
||||
}
|
||||
|
||||
return prefix + r.description + " " + r.Value
|
||||
}
|
||||
|
||||
func (r *reason) Context() interface{} {
|
||||
return r
|
||||
}
|
||||
@@ -204,12 +204,12 @@ func (lp *LayeredProfile) DefaultAction() uint8 {
|
||||
}
|
||||
|
||||
// MatchEndpoint checks if the given endpoint matches an entry in any of the profiles.
|
||||
func (lp *LayeredProfile) MatchEndpoint(entity *intel.Entity) (result endpoints.EPResult, reason string) {
|
||||
func (lp *LayeredProfile) MatchEndpoint(entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
||||
for _, layer := range lp.layers {
|
||||
if layer.endpoints.IsSet() {
|
||||
result, reason = layer.endpoints.Match(entity)
|
||||
if result != endpoints.NoMatch {
|
||||
return
|
||||
result, reason := layer.endpoints.Match(entity)
|
||||
if endpoints.IsDecision(result) {
|
||||
return result, reason
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,14 +220,14 @@ func (lp *LayeredProfile) MatchEndpoint(entity *intel.Entity) (result endpoints.
|
||||
}
|
||||
|
||||
// MatchServiceEndpoint checks if the given endpoint of an inbound connection matches an entry in any of the profiles.
|
||||
func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (result endpoints.EPResult, reason string) {
|
||||
func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
||||
entity.EnableReverseResolving()
|
||||
|
||||
for _, layer := range lp.layers {
|
||||
if layer.serviceEndpoints.IsSet() {
|
||||
result, reason = layer.serviceEndpoints.Match(entity)
|
||||
if result != endpoints.NoMatch {
|
||||
return
|
||||
result, reason := layer.serviceEndpoints.Match(entity)
|
||||
if endpoints.IsDecision(result) {
|
||||
return result, reason
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -239,7 +239,7 @@ func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (result end
|
||||
|
||||
// MatchFilterLists matches the entity against the set of filter
|
||||
// lists.
|
||||
func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPResult, string) {
|
||||
func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPResult, endpoints.Reason) {
|
||||
entity.ResolveSubDomainLists(lp.FilterSubDomains())
|
||||
entity.EnableCNAMECheck(lp.FilterCNAMEs())
|
||||
|
||||
@@ -249,10 +249,10 @@ func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPRe
|
||||
entity.LoadLists()
|
||||
|
||||
if entity.MatchLists(layer.filterListIDs) {
|
||||
return endpoints.Denied, entity.ListBlockReason().String()
|
||||
return endpoints.Denied, entity.ListBlockReason()
|
||||
}
|
||||
|
||||
return endpoints.NoMatch, ""
|
||||
return endpoints.NoMatch, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,11 +262,11 @@ func (lp *LayeredProfile) MatchFilterLists(entity *intel.Entity) (endpoints.EPRe
|
||||
entity.LoadLists()
|
||||
|
||||
if entity.MatchLists(cfgFilterLists) {
|
||||
return endpoints.Denied, entity.ListBlockReason().String()
|
||||
return endpoints.Denied, entity.ListBlockReason()
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints.NoMatch, ""
|
||||
return endpoints.NoMatch, nil
|
||||
}
|
||||
|
||||
// AddEndpoint adds an endpoint to the local endpoint list, saves the local profile and reloads the configuration.
|
||||
|
||||
Reference in New Issue
Block a user