bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/pkg/parser/node.go (about)

     1  package parser
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"strings"
     7  
     8  	"github.com/antonmedv/expr"
     9  	"github.com/logrusorgru/grokky"
    10  	yaml "gopkg.in/yaml.v2"
    11  
    12  	"github.com/antonmedv/expr/vm"
    13  	"bitbucket.org/Aishee/synsec/pkg/exprhelpers"
    14  	"bitbucket.org/Aishee/synsec/pkg/types"
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/prometheus/client_golang/prometheus"
    17  	"github.com/sirupsen/logrus"
    18  	log "github.com/sirupsen/logrus"
    19  )
    20  
    21  type Node struct {
    22  	FormatVersion string `yaml:"format"`
    23  	//Enable config + runtime debug of node via config o/
    24  	Debug bool `yaml:"debug,omitempty"`
    25  	//If enabled, the node (and its child) will report their own statistics
    26  	Profiling bool `yaml:"profiling,omitempty"`
    27  	//Name, author, description and reference(s) for parser pattern
    28  	Name        string   `yaml:"name,omitempty"`
    29  	Author      string   `yaml:"author,omitempty"`
    30  	Description string   `yaml:"description,omitempty"`
    31  	Rerferences []string `yaml:"references,omitempty"`
    32  	//if debug is present in the node, keep its specific Logger in runtime structure
    33  	Logger *log.Entry `yaml:"-"`
    34  	//This is mostly a hack to make writting less repetive.
    35  	//relying on stage, we know which field to parse, and we
    36  	//can as well promote log to next stage on success
    37  	Stage string `yaml:"stage,omitempty"`
    38  	//OnSuccess allows to tag a node to be able to move log to next stage on success
    39  	OnSuccess string `yaml:"onsuccess,omitempty"`
    40  	rn        string //this is only for us in debug, a random generated name for each node
    41  	//Filter is executed at runtime (with current log line as context)
    42  	//and must succeed or node is exited
    43  	Filter        string                    `yaml:"filter,omitempty"`
    44  	RunTimeFilter *vm.Program               `yaml:"-" json:"-"` //the actual compiled filter
    45  	ExprDebugger  *exprhelpers.ExprDebugger `yaml:"-" json:"-"` //used to debug expression by printing the content of each variable of the expression
    46  	//If node has leafs, execute all of them until one asks for a 'break'
    47  	LeavesNodes []Node `yaml:"nodes,omitempty"`
    48  	//Flag used to describe when to 'break' or return an 'error'
    49  	EnrichFunctions []EnricherCtx
    50  
    51  	/* If the node is actually a leaf, it can have : grok, enrich, statics */
    52  	//pattern_syntax are named grok patterns that are re-utilised over several grok patterns
    53  	SubGroks yaml.MapSlice `yaml:"pattern_syntax,omitempty"`
    54  
    55  	//Holds a grok pattern
    56  	Grok types.GrokPattern `yaml:"grok,omitempty"`
    57  	//Statics can be present in any type of node and is executed last
    58  	Statics []types.ExtraField `yaml:"statics,omitempty"`
    59  	//Whitelists
    60  	Whitelist types.Whitelist     `yaml:"whitelist,omitempty"`
    61  	Data      []*types.DataSource `yaml:"data,omitempty"`
    62  }
    63  
    64  func (n *Node) validate(pctx *UnixParserCtx, ectx []EnricherCtx) error {
    65  
    66  	//stage is being set automagically
    67  	if n.Stage == "" {
    68  		return fmt.Errorf("stage needs to be an existing stage")
    69  	}
    70  
    71  	/* "" behaves like continue */
    72  	if n.OnSuccess != "continue" && n.OnSuccess != "next_stage" && n.OnSuccess != "" {
    73  		return fmt.Errorf("onsuccess '%s' not continue,next_stage", n.OnSuccess)
    74  	}
    75  	if n.Filter != "" && n.RunTimeFilter == nil {
    76  		return fmt.Errorf("non-empty filter '%s' was not compiled", n.Filter)
    77  	}
    78  
    79  	if n.Grok.RunTimeRegexp != nil || n.Grok.TargetField != "" {
    80  		if n.Grok.TargetField == "" {
    81  			return fmt.Errorf("grok's apply_on can't be empty")
    82  		}
    83  		if n.Grok.RegexpName == "" && n.Grok.RegexpValue == "" {
    84  			return fmt.Errorf("grok needs 'pattern' or 'name'")
    85  		}
    86  	}
    87  
    88  	for idx, static := range n.Statics {
    89  		if static.Method != "" {
    90  			if static.ExpValue == "" {
    91  				return fmt.Errorf("static %d : when method is set, expression must be present", idx)
    92  			}
    93  			method_found := false
    94  			for _, enricherCtx := range ectx {
    95  				if _, ok := enricherCtx.Funcs[static.Method]; ok && enricherCtx.initiated {
    96  					method_found = true
    97  					break
    98  				}
    99  			}
   100  			if !method_found {
   101  				return fmt.Errorf("the method '%s' doesn't exist or the plugin has not been initialized", static.Method)
   102  			}
   103  		} else {
   104  			if static.Meta == "" && static.Parsed == "" && static.TargetByName == "" {
   105  				return fmt.Errorf("static %d : at least one of meta/event/target must be set", idx)
   106  			}
   107  			if static.Value == "" && static.RunTimeValue == nil {
   108  				return fmt.Errorf("static %d value or expression must be set", idx)
   109  			}
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  func (n *Node) process(p *types.Event, ctx UnixParserCtx) (bool, error) {
   116  	var NodeState bool
   117  	clog := n.Logger
   118  
   119  	clog.Tracef("Event entering node")
   120  	if n.RunTimeFilter != nil {
   121  		//Evaluate node's filter
   122  		output, err := expr.Run(n.RunTimeFilter, exprhelpers.GetExprEnv(map[string]interface{}{"evt": p}))
   123  		if err != nil {
   124  			clog.Warningf("failed to run filter : %v", err)
   125  			clog.Debugf("Event leaving node : ko")
   126  			return false, nil
   127  		}
   128  
   129  		switch out := output.(type) {
   130  		case bool:
   131  			if n.Debug {
   132  				n.ExprDebugger.Run(clog, out, exprhelpers.GetExprEnv(map[string]interface{}{"evt": p}))
   133  			}
   134  			if !out {
   135  				clog.Debugf("Event leaving node : ko (failed filter)")
   136  				return false, nil
   137  			}
   138  		default:
   139  			clog.Warningf("Expr '%s' returned non-bool, abort : %T", n.Filter, output)
   140  			clog.Debugf("Event leaving node : ko")
   141  			return false, nil
   142  		}
   143  		NodeState = true
   144  	} else {
   145  		clog.Tracef("Node has not filter, enter")
   146  		NodeState = true
   147  	}
   148  
   149  	if n.Name != "" {
   150  		NodesHits.With(prometheus.Labels{"source": p.Line.Src, "name": n.Name}).Inc()
   151  	}
   152  	isWhitelisted := false
   153  	hasWhitelist := false
   154  	var srcs []net.IP
   155  	/*overflow and log don't hold the source ip in the same field, should be changed */
   156  	/* perform whitelist checks for ips, cidr accordingly */
   157  	/* TODO move whitelist elsewhere */
   158  	if p.Type == types.LOG {
   159  		if _, ok := p.Meta["source_ip"]; ok {
   160  			srcs = append(srcs, net.ParseIP(p.Meta["source_ip"]))
   161  		}
   162  	} else if p.Type == types.OVFLW {
   163  		for k, _ := range p.Overflow.Sources {
   164  			srcs = append(srcs, net.ParseIP(k))
   165  		}
   166  	}
   167  	for _, src := range srcs {
   168  		if isWhitelisted {
   169  			break
   170  		}
   171  		for _, v := range n.Whitelist.B_Ips {
   172  			if v.Equal(src) {
   173  				clog.Debugf("Event from [%s] is whitelisted by Ips !", src)
   174  				isWhitelisted = true
   175  			} else {
   176  				clog.Tracef("whitelist: %s is not eq [%s]", src, v)
   177  			}
   178  			hasWhitelist = true
   179  		}
   180  		for _, v := range n.Whitelist.B_Cidrs {
   181  			if v.Contains(src) {
   182  				clog.Debugf("Event from [%s] is whitelisted by Cidrs !", src)
   183  				isWhitelisted = true
   184  			} else {
   185  				clog.Tracef("whitelist: %s not in [%s]", src, v)
   186  			}
   187  			hasWhitelist = true
   188  		}
   189  	}
   190  
   191  	if isWhitelisted {
   192  		p.Whitelisted = true
   193  	}
   194  	/* run whitelist expression tests anyway */
   195  	for eidx, e := range n.Whitelist.B_Exprs {
   196  		output, err := expr.Run(e.Filter, exprhelpers.GetExprEnv(map[string]interface{}{"evt": p}))
   197  		if err != nil {
   198  			clog.Warningf("failed to run whitelist expr : %v", err)
   199  			clog.Debugf("Event leaving node : ko")
   200  			return false, nil
   201  		}
   202  		switch out := output.(type) {
   203  		case bool:
   204  			if n.Debug {
   205  				e.ExprDebugger.Run(clog, out, exprhelpers.GetExprEnv(map[string]interface{}{"evt": p}))
   206  			}
   207  			if out {
   208  				clog.Infof("Event is whitelisted by Expr !")
   209  				p.Whitelisted = true
   210  				isWhitelisted = true
   211  			}
   212  			hasWhitelist = true
   213  		default:
   214  			log.Errorf("unexpected type %t (%v) while running '%s'", output, output, n.Whitelist.Exprs[eidx])
   215  		}
   216  	}
   217  	if isWhitelisted {
   218  		p.WhiteListReason = n.Whitelist.Reason
   219  		/*huglily wipe the ban order if the event is whitelisted and it's an overflow */
   220  		if p.Type == types.OVFLW { /*don't do this at home kids */
   221  			ips := []string{}
   222  			for _, src := range srcs {
   223  				ips = append(ips, src.String())
   224  			}
   225  			clog.Infof("Ban for %s whitelisted, reason [%s]", strings.Join(ips, ","), n.Whitelist.Reason)
   226  			p.Overflow.Whitelisted = true
   227  		}
   228  	}
   229  
   230  	//Process grok if present, should be exclusive with nodes :)
   231  	gstr := ""
   232  	if n.Grok.RunTimeRegexp != nil {
   233  		clog.Tracef("Processing grok pattern : %s : %p", n.Grok.RegexpName, n.Grok.RunTimeRegexp)
   234  		//for unparsed, parsed etc. set sensible defaults to reduce user hassle
   235  		if n.Grok.TargetField == "" {
   236  			clog.Fatalf("not default field and no specified on stage '%s'", n.Stage)
   237  
   238  		} else {
   239  			//it's a hack to avoid using real reflect
   240  			if n.Grok.TargetField == "Line.Raw" {
   241  				gstr = p.Line.Raw
   242  			} else if val, ok := p.Parsed[n.Grok.TargetField]; ok {
   243  				gstr = val
   244  			} else {
   245  				clog.Debugf("(%s) target field '%s' doesn't exist in %v", n.rn, n.Grok.TargetField, p.Parsed)
   246  				NodeState = false
   247  				//return false, nil
   248  			}
   249  		}
   250  		var groklabel string
   251  		if n.Grok.RegexpName == "" {
   252  			groklabel = fmt.Sprintf("%5.5s...", n.Grok.RegexpValue)
   253  		} else {
   254  			groklabel = n.Grok.RegexpName
   255  		}
   256  		grok := n.Grok.RunTimeRegexp.Parse(gstr)
   257  		if len(grok) > 0 {
   258  			clog.Debugf("+ Grok '%s' returned %d entries to merge in Parsed", groklabel, len(grok))
   259  			//We managed to grok stuff, merged into parse
   260  			for k, v := range grok {
   261  				clog.Debugf("\t.Parsed['%s'] = '%s'", k, v)
   262  				p.Parsed[k] = v
   263  			}
   264  			// if the grok succeed, process associated statics
   265  			err := n.ProcessStatics(n.Grok.Statics, p)
   266  			if err != nil {
   267  				clog.Fatalf("(%s) Failed to process statics : %v", n.rn, err)
   268  			}
   269  		} else {
   270  			//grok failed, node failed
   271  			clog.Debugf("+ Grok '%s' didn't return data on '%s'", groklabel, gstr)
   272  			//clog.Tracef("on '%s'", gstr)
   273  			NodeState = false
   274  		}
   275  
   276  	} else {
   277  		clog.Tracef("! No grok pattern : %p", n.Grok.RunTimeRegexp)
   278  	}
   279  
   280  	//Iterate on leafs
   281  	if len(n.LeavesNodes) > 0 {
   282  		for _, leaf := range n.LeavesNodes {
   283  			//clog.Debugf("Processing sub-node %d/%d : %s", idx, len(n.SuccessNodes), leaf.rn)
   284  			ret, err := leaf.process(p, ctx)
   285  			if err != nil {
   286  				clog.Tracef("\tNode (%s) failed : %v", leaf.rn, err)
   287  				clog.Debugf("Event leaving node : ko")
   288  				return false, err
   289  			}
   290  			clog.Tracef("\tsub-node (%s) ret : %v (strategy:%s)", leaf.rn, ret, n.OnSuccess)
   291  			if ret {
   292  				NodeState = true
   293  				/* if child is successful, stop processing */
   294  				if n.OnSuccess == "next_stage" {
   295  					clog.Debugf("child is success, OnSuccess=next_stage, skip")
   296  					break
   297  				}
   298  			} else {
   299  				NodeState = false
   300  			}
   301  		}
   302  	}
   303  	/*todo : check if a node made the state change ?*/
   304  	/* should the childs inherit the on_success behaviour */
   305  
   306  	clog.Tracef("State after nodes : %v", NodeState)
   307  
   308  	//grok or leafs failed, don't process statics
   309  	if !NodeState {
   310  		if n.Name != "" {
   311  			NodesHitsKo.With(prometheus.Labels{"source": p.Line.Src, "name": n.Name}).Inc()
   312  		}
   313  		clog.Debugf("Event leaving node : ko")
   314  		return NodeState, nil
   315  	}
   316  
   317  	if n.Name != "" {
   318  		NodesHitsOk.With(prometheus.Labels{"source": p.Line.Src, "name": n.Name}).Inc()
   319  	}
   320  	/*
   321  		Please kill me. this is to apply statics when the node *has* whitelists that successfully matched the node.
   322  	*/
   323  	if hasWhitelist && isWhitelisted && len(n.Statics) > 0 || len(n.Statics) > 0 && !hasWhitelist {
   324  		clog.Debugf("+ Processing %d statics", len(n.Statics))
   325  		// if all else is good in whitelist, process node's statics
   326  		err := n.ProcessStatics(n.Statics, p)
   327  		if err != nil {
   328  			clog.Fatalf("Failed to process statics : %v", err)
   329  		}
   330  	} else {
   331  		clog.Tracef("! No node statics")
   332  	}
   333  
   334  	if NodeState {
   335  		clog.Debugf("Event leaving node : ok")
   336  		log.Tracef("node is successful, check strategy")
   337  		if n.OnSuccess == "next_stage" {
   338  			idx := stageidx(p.Stage, ctx.Stages)
   339  			//we're at the last stage
   340  			if idx+1 == len(ctx.Stages) {
   341  				clog.Debugf("node reached the last stage : %s", p.Stage)
   342  			} else {
   343  				clog.Debugf("move Event from stage %s to %s", p.Stage, ctx.Stages[idx+1])
   344  				p.Stage = ctx.Stages[idx+1]
   345  			}
   346  		} else {
   347  			clog.Tracef("no strategy on success (%s), continue !", n.OnSuccess)
   348  		}
   349  	} else {
   350  		clog.Debugf("Event leaving node : ko")
   351  	}
   352  	clog.Tracef("Node successful, continue")
   353  	return NodeState, nil
   354  }
   355  
   356  func (n *Node) compile(pctx *UnixParserCtx, ectx []EnricherCtx) error {
   357  	var err error
   358  	var valid bool
   359  
   360  	valid = false
   361  
   362  	dumpr := spew.ConfigState{MaxDepth: 1, DisablePointerAddresses: true}
   363  	n.rn = seed.Generate()
   364  
   365  	n.EnrichFunctions = ectx
   366  	log.Tracef("compile, node is %s", n.Stage)
   367  	/* if the node has debugging enabled, create a specific logger with debug
   368  	that will be used only for processing this node ;) */
   369  	if n.Debug {
   370  		var clog = logrus.New()
   371  		if err := types.ConfigureLogger(clog); err != nil {
   372  			log.Fatalf("While creating bucket-specific logger : %s", err)
   373  		}
   374  		clog.SetLevel(log.DebugLevel)
   375  		n.Logger = clog.WithFields(log.Fields{
   376  			"id": n.rn,
   377  		})
   378  		n.Logger.Infof("%s has debug enabled", n.Name)
   379  	} else {
   380  		/* else bind it to the default one (might find something more elegant here)*/
   381  		n.Logger = log.WithFields(log.Fields{
   382  			"id": n.rn,
   383  		})
   384  	}
   385  
   386  	/* display info about top-level nodes, they should be the only one with explicit stage name ?*/
   387  	n.Logger = n.Logger.WithFields(log.Fields{"stage": n.Stage, "name": n.Name})
   388  
   389  	n.Logger.Tracef("Compiling : %s", dumpr.Sdump(n))
   390  
   391  	//compile filter if present
   392  	if n.Filter != "" {
   393  		n.RunTimeFilter, err = expr.Compile(n.Filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   394  		if err != nil {
   395  			return fmt.Errorf("compilation of '%s' failed: %v", n.Filter, err)
   396  		}
   397  
   398  		if n.Debug {
   399  			n.ExprDebugger, err = exprhelpers.NewDebugger(n.Filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   400  			if err != nil {
   401  				log.Errorf("unable to build debug filter for '%s' : %s", n.Filter, err)
   402  			}
   403  		}
   404  
   405  	}
   406  
   407  	/* handle pattern_syntax and groks */
   408  	for _, pattern := range n.SubGroks {
   409  		n.Logger.Tracef("Adding subpattern '%s' : '%s'", pattern.Key, pattern.Value)
   410  		if err := pctx.Grok.Add(pattern.Key.(string), pattern.Value.(string)); err != nil {
   411  			if err == grokky.ErrAlreadyExist {
   412  				n.Logger.Warningf("grok '%s' already registred", pattern.Key)
   413  				continue
   414  			}
   415  			n.Logger.Errorf("Unable to compile subpattern %s : %v", pattern.Key, err)
   416  			return err
   417  		}
   418  	}
   419  
   420  	/* load grok by name or compile in-place */
   421  	if n.Grok.RegexpName != "" {
   422  		n.Logger.Tracef("+ Regexp Compilation '%s'", n.Grok.RegexpName)
   423  		n.Grok.RunTimeRegexp, err = pctx.Grok.Get(n.Grok.RegexpName)
   424  		if err != nil {
   425  			return fmt.Errorf("Unable to find grok '%s' : %v", n.Grok.RegexpName, err)
   426  		}
   427  		if n.Grok.RunTimeRegexp == nil {
   428  			return fmt.Errorf("Empty grok '%s'", n.Grok.RegexpName)
   429  		}
   430  		n.Logger.Tracef("%s regexp: %s", n.Grok.RegexpName, n.Grok.RunTimeRegexp.Regexp.String())
   431  		valid = true
   432  	} else if n.Grok.RegexpValue != "" {
   433  		if strings.HasSuffix(n.Grok.RegexpValue, "\n") {
   434  			n.Logger.Debugf("Beware, pattern ends with \\n : '%s'", n.Grok.RegexpValue)
   435  		}
   436  		n.Grok.RunTimeRegexp, err = pctx.Grok.Compile(n.Grok.RegexpValue)
   437  		if err != nil {
   438  			return fmt.Errorf("Failed to compile grok '%s': %v\n", n.Grok.RegexpValue, err)
   439  		}
   440  		if n.Grok.RunTimeRegexp == nil {
   441  			// We shouldn't be here because compilation succeeded, so regexp shouldn't be nil
   442  			return fmt.Errorf("Grok compilation failure: %s", n.Grok.RegexpValue)
   443  		}
   444  		n.Logger.Tracef("%s regexp : %s", n.Grok.RegexpValue, n.Grok.RunTimeRegexp.Regexp.String())
   445  		valid = true
   446  	}
   447  	/* load grok statics */
   448  	if len(n.Grok.Statics) > 0 {
   449  		//compile expr statics if present
   450  		for idx := range n.Grok.Statics {
   451  			if n.Grok.Statics[idx].ExpValue != "" {
   452  				n.Grok.Statics[idx].RunTimeValue, err = expr.Compile(n.Grok.Statics[idx].ExpValue,
   453  					expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   454  				if err != nil {
   455  					return err
   456  				}
   457  			}
   458  		}
   459  		valid = true
   460  	}
   461  	/* compile leafs if present */
   462  	if len(n.LeavesNodes) > 0 {
   463  		for idx := range n.LeavesNodes {
   464  			if n.LeavesNodes[idx].Name == "" {
   465  				n.LeavesNodes[idx].Name = fmt.Sprintf("child-%s", n.Name)
   466  			}
   467  			/*propagate debug/stats to child nodes*/
   468  			if !n.LeavesNodes[idx].Debug && n.Debug {
   469  				n.LeavesNodes[idx].Debug = true
   470  			}
   471  			if !n.LeavesNodes[idx].Profiling && n.Profiling {
   472  				n.LeavesNodes[idx].Profiling = true
   473  			}
   474  			n.LeavesNodes[idx].Stage = n.Stage
   475  			err = n.LeavesNodes[idx].compile(pctx, ectx)
   476  			if err != nil {
   477  				return err
   478  			}
   479  		}
   480  		valid = true
   481  	}
   482  	/* load statics if present */
   483  	for idx := range n.Statics {
   484  		if n.Statics[idx].ExpValue != "" {
   485  			n.Statics[idx].RunTimeValue, err = expr.Compile(n.Statics[idx].ExpValue, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   486  			if err != nil {
   487  				n.Logger.Errorf("Statics Compilation failed %v.", err)
   488  				return err
   489  			}
   490  		}
   491  		valid = true
   492  	}
   493  
   494  	/* compile whitelists if present */
   495  	for _, v := range n.Whitelist.Ips {
   496  		n.Whitelist.B_Ips = append(n.Whitelist.B_Ips, net.ParseIP(v))
   497  		n.Logger.Debugf("adding ip %s to whitelists", net.ParseIP(v))
   498  		valid = true
   499  	}
   500  	for _, v := range n.Whitelist.Cidrs {
   501  		_, tnet, err := net.ParseCIDR(v)
   502  		if err != nil {
   503  			n.Logger.Fatalf("Unable to parse cidr whitelist '%s' : %v.", v, err)
   504  		}
   505  		n.Whitelist.B_Cidrs = append(n.Whitelist.B_Cidrs, tnet)
   506  		n.Logger.Debugf("adding cidr %s to whitelists", tnet)
   507  		valid = true
   508  	}
   509  	for _, filter := range n.Whitelist.Exprs {
   510  		expression := &types.ExprWhitelist{}
   511  		expression.Filter, err = expr.Compile(filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   512  		if err != nil {
   513  			n.Logger.Fatalf("Unable to compile whitelist expression '%s' : %v.", filter, err)
   514  		}
   515  		expression.ExprDebugger, err = exprhelpers.NewDebugger(filter, expr.Env(exprhelpers.GetExprEnv(map[string]interface{}{"evt": &types.Event{}})))
   516  		if err != nil {
   517  			log.Errorf("unable to build debug filter for '%s' : %s", filter, err)
   518  		}
   519  		n.Whitelist.B_Exprs = append(n.Whitelist.B_Exprs, expression)
   520  		n.Logger.Debugf("adding expression %s to whitelists", filter)
   521  		valid = true
   522  	}
   523  
   524  	if !valid {
   525  		/* node is empty, error force return */
   526  		n.Logger.Infof("Node is empty: %s", spew.Sdump(n))
   527  		n.Stage = ""
   528  	}
   529  	if err := n.validate(pctx, ectx); err != nil {
   530  		return err
   531  		//n.logger.Fatalf("Node is invalid : %s", err)
   532  	}
   533  	return nil
   534  }