From c3a78a7ea5638fdae712580980553ff080161a81 Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Fri, 1 Nov 2019 10:19:26 +0000
Subject: [PATCH] Documentation updates

---
 cmd/iprep/server.go   | 10 ++++++-
 cmd/iprep/tail.go     | 16 ++++++++++-
 script/script.go      | 63 ++++++++++++++++++++++++++++++++++---------
 script/script_test.go | 36 +++++++++++++++++++------
 submission/queue.go   |  5 ++++
 5 files changed, 108 insertions(+), 22 deletions(-)

diff --git a/cmd/iprep/server.go b/cmd/iprep/server.go
index cf0c5b6..c865b29 100644
--- a/cmd/iprep/server.go
+++ b/cmd/iprep/server.go
@@ -36,7 +36,8 @@ func (c *serverCommand) Name() string     { return "server" }
 func (c *serverCommand) Synopsis() string { return "run the rpc server" }
 func (c *serverCommand) Usage() string {
 	return `server [<flags>]:
-        Run the GRPC server.
+
+        Run the main database and analysis service, with a GRPC API.
 
 `
 }
@@ -112,6 +113,7 @@ func (c *serverCommand) run(ctx context.Context) error {
 		return grpcServer.Serve(l)
 	})
 
+	// Reload the scoring script on SIGHUP.
 	reloadSigCh := make(chan os.Signal, 1)
 	go func() {
 		for range reloadSigCh {
@@ -120,9 +122,13 @@ func (c *serverCommand) run(ctx context.Context) error {
 	}()
 	signal.Notify(reloadSigCh, syscall.SIGHUP)
 
+	// Terminate the program on SIGINT/SIGTERM.
 	stopSigCh := make(chan os.Signal, 1)
 	signal.Notify(stopSigCh, syscall.SIGINT, syscall.SIGTERM)
 
+	// At this point we got nothing else to do but wait for the
+	// serving goroutines to return. Notify systemd that we're
+	// ready.
 	daemon.SdNotify(false, daemon.SdNotifyReady) // nolint
 
 	select {
@@ -131,6 +137,8 @@ func (c *serverCommand) run(ctx context.Context) error {
 	case <-ctx.Done():
 	}
 
+	// We got here because of an error (or a termination request),
+	// so orderly shut down all the serving components.
 	cancel()
 
 	if httpServer != nil {
diff --git a/cmd/iprep/tail.go b/cmd/iprep/tail.go
index daf5448..27d615a 100644
--- a/cmd/iprep/tail.go
+++ b/cmd/iprep/tail.go
@@ -53,10 +53,24 @@ func (c *tailCommand) Name() string     { return "tail" }
 func (c *tailCommand) Synopsis() string { return "run the rpc tail" }
 func (c *tailCommand) Usage() string {
 	return `tail [<flags>]:
-        Monitor stdin in realtime and update counters based on
+
+        Monitor stdin in realtime and generate events based on
         user-specified detection rules (regexp-based). Meant to
         process third-party logs.
 
+        The patterns file should contain one rule per line, consisting
+        of a regexp bounded by / characters (Perl- style), followed by
+        whitespace, followed by the event type that should be set on
+        the generated event. Regular expressions must contain one
+        capture group for the IP address.
+
+        As an example, the following rule:
+
+            /auth failure from ip ([.0-9]+)/ auth_failure
+
+        will trigger an 'auth_failure' event with the IP extracted
+        from the message.
+
 `
 }
 
diff --git a/script/script.go b/script/script.go
index 8bfe9d3..55f0be3 100644
--- a/script/script.go
+++ b/script/script.go
@@ -10,10 +10,28 @@ import (
 	"github.com/d5/tengo/stdlib"
 )
 
+// A Script is a compiled Tengo script that will be executed on every
+// request in order to provide a final reputation score for an IP.
+//
+// The script will have a few global variables available:
+//
+// 'ip' is the IP being looked up
+//
+// 'counts' is a map[string]int64 of counters returned from the
+// database in the chosen time interval
+//
+// 'interval' is the duration in seconds of the time interval chosen
+// for this evaluation, so one can compute rates
+//
+// To return the final reputation score (in the range 0 - 1), the
+// script should set the 'score' variable.
+//
 type Script struct {
 	compiled *script.Compiled
 }
 
+// LoadScript creates a new Script by loading its source from the
+// specified file.
 func LoadScript(path string) (*Script, error) {
 	data, err := ioutil.ReadFile(path)
 	if err != nil {
@@ -26,43 +44,64 @@ var globalVars = []string{
 	"ip", "score", "counts", "interval", "ext",
 }
 
+// NewScript creates a new Script with the given source.
 func NewScript(src []byte) (*Script, error) {
 	s := script.New(src)
+
 	// Define all the global variables (input and output), so that
-	// they can be set later on the compiled object.
+	// they can be set later on the compiled object. The object
+	// type we use here does not matter.
 	for _, v := range globalVars {
 		if err := s.Add(v, 0); err != nil {
 			return nil, err
 		}
 	}
 
-	// Add available imports.
-	s.SetImports(stdlib.GetModuleMap("math", "times"))
+	// Load the Tengo standard library.
+	s.SetImports(stdlib.GetModuleMap(stdlib.AllModuleNames()...))
 
+	// Compile the script. We're going to Clone the compiled
+	// version on every request.
 	c, err := s.Compile()
 	if err != nil {
 		return nil, err
 	}
+
 	return &Script{compiled: c}, nil
 }
 
+// RunIP evaluates the script once with the provided context and
+// returns the resulting score.
 func (script *Script) RunIP(ctx context.Context, ip string, counts map[string]int64, intervalSecs float64, ext map[string]interface{}) (float64, error) {
 	c := script.compiled.Clone()
-	c.Set("ip", ip)
-	c.Set("score", 0.0)
-	c.Set("interval", intervalSecs)
-	c.Set("ext", ext)
+
+	// Set the global variables that constitute the script
+	// execution context. We expect no errors here even if the
+	// script does not reference some of these variables, as we
+	// have pre-defined them in NewScript.
+	if err := c.Set("ip", ip); err != nil {
+		return 0, err
+	}
+	if err := c.Set("score", 0.0); err != nil {
+		return 0, err
+	}
+	if err := c.Set("interval", intervalSecs); err != nil {
+		return 0, err
+	}
+	if err := c.Set("ext", ext); err != nil {
+		return 0, err
+	}
 	if err := c.Set("counts", intMap(counts)); err != nil {
 		return 0, err
 	}
+
+	// Run the script and extract the result from the global
+	// variable 'score'. Acceptable values for score are anything
+	// that can be converted to float by Tengo.
 	if err := c.RunContext(ctx); err != nil {
 		return 0, err
 	}
-	score := c.Get("score")
-	if i := score.Int(); i != 0 {
-		return float64(i), nil
-	}
-	return score.Float(), nil
+	return c.Get("score").Float(), nil
 }
 
 type Manager struct {
diff --git a/script/script_test.go b/script/script_test.go
index 49946f6..2a0b71a 100644
--- a/script/script_test.go
+++ b/script/script_test.go
@@ -5,12 +5,7 @@ import (
 	"testing"
 )
 
-func TestScript(t *testing.T) {
-	src := `
-score = counts["test"] / 2 + counts["test2"]
-//score = 7
-`
-
+func runTestScript(t *testing.T, src string, expected float64) {
 	s, err := NewScript([]byte(src))
 	if err != nil {
 		t.Fatalf("NewScript: %v", err)
@@ -24,7 +19,32 @@ score = counts["test"] / 2 + counts["test2"]
 	if err != nil {
 		t.Fatalf("runScript: %v", err)
 	}
-	if score != 7 {
-		t.Fatalf("bad score, expected 7 got %f", score)
+	if score != expected {
+		t.Fatalf("bad score, expected %f got %f", expected, score)
 	}
 }
+
+func TestScript_WithLookup(t *testing.T) {
+	runTestScript(t, `
+score = counts["test"] / 2 + counts["test2"]
+//score = 7
+`, 7)
+}
+
+func TestScript_FloatScore(t *testing.T) {
+	runTestScript(t, `
+score = 7.0
+`, 7)
+}
+
+func TestScript_IntScore(t *testing.T) {
+	runTestScript(t, `
+score = 7
+`, 7)
+}
+
+func TestScript_StringScore(t *testing.T) {
+	runTestScript(t, `
+score = "7"
+`, 7)
+}
diff --git a/submission/queue.go b/submission/queue.go
index 7ffa88f..3e2d30a 100644
--- a/submission/queue.go
+++ b/submission/queue.go
@@ -44,6 +44,10 @@ func (o *Options) withDefaults() *Options {
 	return o
 }
 
+// A Submitter sends events to a remote iprep collector, trying to
+// keep the overall request rate under control: above a certain rate
+// of requests (defined via Options), the Submitter will start
+// aggregating and batching events.
 type Submitter interface {
 	AddEvent(*ippb.Event)
 	AddAggregate(*ippb.Aggregate)
@@ -72,6 +76,7 @@ func newSubmitter(client client.Client, opts *Options) Submitter {
 	return s
 }
 
+// New creates a new Submitter pointing at the specified collector addr.
 func New(addr string, opts *Options) (Submitter, error) {
 	c, err := client.New(addr, opts.ClientOptions)
 	if err != nil {
-- 
GitLab