Commit Diff


commit - 4bdba0c89beaf1b4416b25d6d4ce09cfa21b35e1
commit + f6ce674e922dc6e54e26ebd3b0d6098f6274ca57
blob - /dev/null
blob + 7ae56b610b25acf6875284f89c2baa424d7d0f3e (mode 644)
--- /dev/null
+++ server/README.md
@@ -0,0 +1,35 @@
+This is a sample semgrep rule server for serving individual rules and packs.
+
+To run:
+
+```sh
+$ ./server -listen=:8080 -d=/path/to/semgrep-rules/rules -packs=packs.yml
+```
+
+The `packs.yml` file contains rule packs.
+
+```yaml
+packs:
+  tarantool:
+    - box_cfg_raw_access
+    - grant_guest_full_access
+    - missed_if_not_exist
+    - set_trigger_once
+    - insecure-hash-algorithm
+    - insecure-hash-algorithm
+    - bad_hash_func
+```
+
+The rule names must be the `id`s of the rules, *not* the filenames.
+
+To serve and run a rule pack:
+
+```sh
+$ semgrep --config=http://localhost:8080/p/tarantool
+```
+
+To serve and run an individual rule:
+
+```sh
+$ semgrep --config=http://localhost:8080/r/box_cfg_raw_access
+```
blob - /dev/null
blob + 5024f02b2ebe88f27a3bf0887212ac4bbdca2c6a (mode 644)
--- /dev/null
+++ server/main.go
@@ -0,0 +1,144 @@
+// Author: Damian Gryski
+
+package main
+
+import (
+	"flag"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"gopkg.in/yaml.v2"
+)
+
+type RuleFile struct {
+	Rules []Rule `yaml:"rules"`
+}
+
+type Rule map[string]interface{}
+
+type PackFile struct {
+	Packs map[string][]string `yaml:"packs"`
+}
+
+func main() {
+
+	dir := flag.String("dir", ".", "directory to scan for yaml files")
+	listen := flag.String("listen", ":8080", "address to listen on")
+	packyml := flag.String("packs", "packs.yml", "list of rule packs")
+
+	flag.Parse()
+
+	rules := make(map[string]Rule)
+
+	filepath.Walk(*dir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if filepath.Ext(path) != ".yml" {
+			return nil
+		}
+
+		b, err := ioutil.ReadFile(path)
+		if err != nil {
+			log.Println("error reading", path, ":", err)
+			return nil
+		}
+
+		var r RuleFile
+		if err := yaml.Unmarshal(b, &r); err != nil {
+			log.Println("error loading", path, ":", err)
+			return nil
+		}
+
+		for _, rr := range r.Rules {
+			rules[rr["id"].(string)] = rr
+		}
+
+		return nil
+	})
+
+	log.Println("loaded", len(rules), "rules")
+
+	var packs PackFile
+
+	if *packyml != "" {
+		b, err := ioutil.ReadFile(*packyml)
+		if err != nil {
+			log.Fatalln("error reading", *packyml, ":", err)
+		}
+
+		if err := yaml.Unmarshal(b, &packs); err != nil {
+			log.Fatalln("error loading", *packyml, ":", err)
+		}
+
+		// ensure all referenced rules exist
+		for _, v := range packs.Packs {
+			for _, r := range v {
+				if _, ok := rules[r]; !ok {
+					log.Printf("error: pack %q contained unknown rule %q", v, r)
+				}
+			}
+		}
+	}
+
+	log.Println("loaded", len(packs.Packs), "packs")
+
+	handler := rulesHandler{rules, packs.Packs}
+
+	http.HandleFunc("/r/", handler.HandleRule)
+	http.HandleFunc("/p/", handler.HandlePack)
+
+	log.Println("listening on", *listen)
+	http.ListenAndServe(*listen, nil)
+}
+
+type rulesHandler struct {
+	rules map[string]Rule
+	packs map[string][]string
+}
+
+// TODO(dgryski): These responses should really be precomputed and cached rather than computed on-the-fly
+
+func (rh rulesHandler) HandleRule(w http.ResponseWriter, r *http.Request) {
+	p := strings.TrimPrefix(r.RequestURI, "/r/")
+	log.Println("rule", p)
+
+	rule, ok := rh.rules[p]
+	if !ok {
+		http.NotFound(w, r)
+		return
+	}
+
+	w.Header().Set("Content-Type", "text/yaml")
+
+	y := yaml.NewEncoder(w)
+	defer y.Close()
+	y.Encode(RuleFile{Rules: []Rule{rule}})
+}
+
+func (rh rulesHandler) HandlePack(w http.ResponseWriter, r *http.Request) {
+	p := strings.TrimPrefix(r.RequestURI, "/p/")
+	log.Println("pack", p)
+
+	rules, ok := rh.packs[p]
+	if !ok {
+		http.NotFound(w, r)
+		return
+	}
+
+	var f RuleFile
+	for _, rule := range rules {
+		f.Rules = append(f.Rules, rh.rules[rule])
+	}
+
+	w.Header().Set("Content-Type", "text/yaml")
+
+	y := yaml.NewEncoder(w)
+	defer y.Close()
+	y.Encode(f)
+}