cmd/cvetriage,internal: add initial command

An initial layout for cvetriage is added, which reads data from
triaged-cve-list and prints it out. Logic for triaging actual CVEs from
CVE list will be added in the next few CLs.

Change-Id: Ibc2e3a7e78e9ee8310f0717b14cbdfafca9c2eb9
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/356390
Trust: Julie Qiu <julie@golang.org>
Run-TryBot: Julie Qiu <julie@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Roland Shoemaker <roland@golang.org>
diff --git a/cmd/cvetriage/main.go b/cmd/cvetriage/main.go
new file mode 100644
index 0000000..f06d851
--- /dev/null
+++ b/cmd/cvetriage/main.go
@@ -0,0 +1,80 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Command cvetriage is used to manage the processing and triaging of CVE data
+// from the github.com/CVEProject/cvelist git repository. It is intended to be
+// run by a third-party scheduler, such as Cloud Run, at some predefined interval.
+//
+// Running this tool will do the following: run the tool does the following things:
+//  1. Reads each CVE JSON file, filtering them based on possible indicators
+//     that the CVE is related to a Go project.
+//  2. Reads a list of already processed CVEs (currently stored at
+//     triaged-cve-list, but will likely be moved to a database in the future), skipping
+//     any CVEs from the previous step that have already been processed.
+//  3. For each unprocessed CVE, a preliminary YAML vulnerability report will be generated, and a
+//     GitHub issue will be created.
+package main
+
+import (
+	"fmt"
+	"log"
+	"strings"
+
+	"golang.org/x/vulndb/internal"
+)
+
+func main() {
+	if err := run(); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func run() error {
+	triaged, err := readTriagedCVEList()
+	if err != nil {
+		return err
+	}
+	// TODO: implement CVE list triage logic and replace this print.
+	for k := range triaged {
+		fmt.Println(k)
+	}
+	return nil
+}
+
+const (
+	triagedCVEList      = "triaged-cve-list"
+	statusFalsePositive = "false-positive"
+	statusTriaged       = "triaged"
+)
+
+func readTriagedCVEList() (map[string]bool, error) {
+	triaged := map[string]bool{}
+	lines, err := internal.ReadFileLines(triagedCVEList)
+	if err != nil {
+		return nil, err
+	}
+	for _, l := range lines {
+		vuln := strings.Fields(l)
+		if len(vuln) < 2 {
+			return nil, fmt.Errorf("unexpected syntax: %q", l)
+		}
+		var (
+			cveID  = vuln[0]
+			status = vuln[1]
+		)
+		if status != statusFalsePositive && status != statusTriaged {
+			return nil, fmt.Errorf("unexpected syntax: %q", l)
+		}
+		if status == statusTriaged {
+			if len(vuln) != 3 {
+				return nil, fmt.Errorf("unexpected syntax: %q", l)
+			}
+			triaged[cveID] = true
+		}
+		if status == statusFalsePositive {
+			triaged[cveID] = true
+		}
+	}
+	return triaged, nil
+}
diff --git a/internal/internal.go b/internal/internal.go
new file mode 100644
index 0000000..d8055b5
--- /dev/null
+++ b/internal/internal.go
@@ -0,0 +1,36 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package internal contains functionality for x/vulndb.
+package internal
+
+import (
+	"bufio"
+	"os"
+	"strings"
+)
+
+// Readfilelines reads and returns the lines from a file.
+// Whitespace on each line is trimmed.
+// Blank lines and lines beginning with '#' are ignored.
+func ReadFileLines(filename string) (lines []string, err error) {
+	f, err := os.Open(filename)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	s := bufio.NewScanner(f)
+	for s.Scan() {
+		line := strings.TrimSpace(s.Text())
+		if line == "" || strings.HasPrefix(line, "#") {
+			continue
+		}
+		lines = append(lines, line)
+	}
+	if s.Err() != nil {
+		return nil, s.Err()
+	}
+	return lines, nil
+}