commit - 4bdba0c89beaf1b4416b25d6d4ce09cfa21b35e1
commit + f6ce674e922dc6e54e26ebd3b0d6098f6274ca57
blob - /dev/null
blob + 7ae56b610b25acf6875284f89c2baa424d7d0f3e (mode 644)
--- /dev/null
+++ server/README.md
+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
+// 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)
+}