diff --git a/arch-linux/archlinux.go b/arch-linux/archlinux.go new file mode 100644 index 00000000..d1924a82 --- /dev/null +++ b/arch-linux/archlinux.go @@ -0,0 +1,79 @@ +package arch_linux + +import ( + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + + "github.com/aquasecurity/vuln-list-update/utils" + "golang.org/x/xerrors" + pb "gopkg.in/cheggaaa/pb.v1" +) + +const ( + archLinuxDir = "arch-linux" + securityTrackerURL = "https://security.archlinux.org/json" + retry = 3 +) + +type ArchLinuxConfig struct { + URL string + VulnListDir string + Retry int +} + +func NewArchLinuxConfig() ArchLinuxConfig { + return NewArchLinuxWithConfig(securityTrackerURL, filepath.Join(utils.VulnListDir(), archLinuxDir), 5) +} + +func NewArchLinuxWithConfig(url, path string, retryTimes int) ArchLinuxConfig { + return ArchLinuxConfig{ + URL: url, + VulnListDir: path, + Retry: retryTimes, + } +} + +func (alc ArchLinuxConfig) Update() error { + log.Println("Fetching Arch Linux data...") + vulns, err := alc.retrieveArchLinuxCveDetails() + if err != nil { + return xerrors.Errorf("failed to retrieve Arch Linux CVE details: %w", err) + } + + log.Println("Removing old data...") + if err = os.RemoveAll(alc.VulnListDir); err != nil { + return xerrors.Errorf("failed to remove Arch Linux dir: %w", err) + } + + // Save all JSON files + log.Println("Saving new data...") + bar := pb.StartNew(len(vulns)) + for _, cves := range vulns { + dir := filepath.Join(alc.VulnListDir) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return xerrors.Errorf("failed to create the directory: %w", err) + } + filePath := filepath.Join(dir, fmt.Sprintf("%s.json", cves.Name)) + if err = utils.Write(filePath, cves); err != nil { + return xerrors.Errorf("failed to write Debian CVE details: %w", err) + } + bar.Increment() + } + bar.Finish() + return nil +} + +func (alc ArchLinuxConfig) retrieveArchLinuxCveDetails() (vulns ArchLinuxCVE, err error) { + cveJSON, err := utils.FetchURL(alc.URL, "", alc.Retry) + if err != nil { + return vulns, xerrors.Errorf("failed to fetch cve data from Arch Linux. err: %w", err) + } + + if err = json.Unmarshal(cveJSON, &vulns); err != nil { + return vulns, xerrors.Errorf("error in unmarshal json: %w", err) + } + return vulns, nil +} diff --git a/arch-linux/archlinux_test.go b/arch-linux/archlinux_test.go new file mode 100644 index 00000000..a297dfd5 --- /dev/null +++ b/arch-linux/archlinux_test.go @@ -0,0 +1,77 @@ +package arch_linux + +import ( + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func TestUpdate(t *testing.T) { + testCases := []struct { + name string + inputJSONFile string + expectedOutputJSONFile string + expectedError string + ArchLinuxServerUrl string + }{ + { + name: "happy path", + inputJSONFile: "testdata/archlinux.json", + expectedOutputJSONFile: "testdata/AVG-4.json", + }, + { + name: "sad path, unreachable Arch Linux service", + expectedError: "failed to retrieve Arch Linux CVE details", + ArchLinuxServerUrl: "http://foo/bar/baz", + }, + { + name: "sad path, invalid json", + inputJSONFile: "testdata/invalid.json", + expectedError: "failed to retrieve Arch Linux CVE details", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var ArchLinuxURL string + if tc.ArchLinuxServerUrl != "" { + ArchLinuxURL = tc.ArchLinuxServerUrl + } else { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, _ := ioutil.ReadFile(tc.inputJSONFile) + _, _ = io.WriteString(w, string(b)) + })) + ArchLinuxURL = ts.URL + defer func() { + ts.Close() + }() + } + + dir, _ := ioutil.TempDir("", "TestUpdate-*") + defer func() { + _ = os.RemoveAll(dir) + }() + + c := NewArchLinuxWithConfig(ArchLinuxURL, filepath.Join(dir), 0) + err := c.Update() + switch { + case tc.expectedError != "": + require.Error(t, err, tc.name) + default: + gotJSON, err := ioutil.ReadFile(filepath.Join(dir, "AVG-4.json")) + require.NoError(t, err, tc.name) + + wantJSON, _ := ioutil.ReadFile(tc.expectedOutputJSONFile) + assert.JSONEq(t, string(wantJSON), string(gotJSON), tc.name) + } + }) + } +} diff --git a/arch-linux/testdata/AVG-4.json b/arch-linux/testdata/AVG-4.json new file mode 100644 index 00000000..c5f5b41f --- /dev/null +++ b/arch-linux/testdata/AVG-4.json @@ -0,0 +1,17 @@ +{ + "name":"AVG-4", + "packages":[ + "bzip2" + ], + "status":"Fixed", + "severity":"Low", + "type":"denial of service", + "affected":"1.0.6-5", + "fixed":"1.0.6-6", + "issues":[ + "CVE-2016-3189" + ], + "advisories":[ + "ASA-201702-19" + ] +} \ No newline at end of file diff --git a/arch-linux/testdata/archlinux.json b/arch-linux/testdata/archlinux.json new file mode 100644 index 00000000..fde7cc22 --- /dev/null +++ b/arch-linux/testdata/archlinux.json @@ -0,0 +1,38 @@ +[ + { + "name":"AVG-20", + "packages":[ + "curl" + ], + "status":"Fixed", + "severity":"Low", + "type":"denial of service", + "affected":"7.50.2-1", + "fixed":"7.50.3-1", + "ticket":null, + "issues":[ + "CVE-2016-7167" + ], + "advisories":[ + "ASA-201609-19" + ] + }, + { + "name":"AVG-4", + "packages":[ + "bzip2" + ], + "status":"Fixed", + "severity":"Low", + "type":"denial of service", + "affected":"1.0.6-5", + "fixed":"1.0.6-6", + "ticket":null, + "issues":[ + "CVE-2016-3189" + ], + "advisories":[ + "ASA-201702-19" + ] + } +] \ No newline at end of file diff --git a/arch-linux/testdata/invalid.json b/arch-linux/testdata/invalid.json new file mode 100644 index 00000000..6b7f8497 --- /dev/null +++ b/arch-linux/testdata/invalid.json @@ -0,0 +1 @@ +invalid json \ No newline at end of file diff --git a/arch-linux/types.go b/arch-linux/types.go new file mode 100644 index 00000000..9a00d896 --- /dev/null +++ b/arch-linux/types.go @@ -0,0 +1,13 @@ +package arch_linux + +type ArchLinuxCVE []struct { + Name string `json:"name"` + Packages []string `json:"packages"` + Status string `json:"status"` + Severity string `json:"severity"` + Type string `json:"type"` + Affected string `json:"affected"` + Fixed string `json:"fixed"` + Issues []string `json:"issues"` + Advisories []string `json:"advisories"` +}