Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions alt/alt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package alt

import (
"archive/zip"
"bytes"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"

"github.com/cheggaaa/pb/v3"
"github.com/spf13/afero"
"golang.org/x/xerrors"

"github.com/aquasecurity/vuln-list-update/utils"
)

const (
altDir = "oval"

branchURL = "https://rdb.altlinux.org/api/errata/export/oval/%s"
branchListURL = "https://rdb.altlinux.org/api/errata/export/oval/branches"

retry = 5
)

type Config struct {
VulnListDir string
BranchURL string
BranchListURL string
AppFs afero.Fs
Retry int
}

func NewConfig() Config {
return Config{
VulnListDir: utils.VulnListDir(),
BranchURL: branchURL,
BranchListURL: branchListURL,
AppFs: afero.NewOsFs(),
Retry: retry,
}
}

type BranchList struct {
Length int
Branches []string
}

func (c Config) Update() error {
dirPath := filepath.Join(c.VulnListDir, altDir)
log.Printf("Remove ALT's OVAL directoty: %s", dirPath)
if err := os.RemoveAll(dirPath); err != nil {
return xerrors.Errorf("failed to remove ALT's OVAL directory: %w", err)
}

log.Println("Fetching ALT's OVAL branch list...")
branchList, err := c.fetchBranchList()
if err != nil {
return err
}

for _, branch := range branchList.Branches {
log.Printf("Fetching ALT's OVAL branch: %s", branch)
if err := c.updateOVAL(branch); err != nil {
return err
}
}

return nil
}

func (c Config) fetchBranchList() (BranchList, error) {
resp, err := utils.FetchURL(c.BranchListURL, "", c.Retry)
if err != nil {
return BranchList{}, xerrors.Errorf("failed to get ALT's OVAL branch list: %w", err)
}

var branchList BranchList
if err := json.Unmarshal(resp, &branchList); err != nil {
return BranchList{}, xerrors.Errorf("failed to unmarshal branch list JSON response: %w", err)
}

return branchList, nil
}

func (c Config) updateOVAL(branch string) error {
ovalURL := fmt.Sprintf(c.BranchURL, branch)

resp, err := utils.FetchURL(ovalURL, "", c.Retry)
if err != nil {
return xerrors.Errorf("failed to get ALT's OVAL branch archive: %w", err)
}

reader, err := zip.NewReader(bytes.NewReader(resp), int64(len(resp)))
if err != nil {
return xerrors.Errorf("failed to init zip reader: %w", err)
}

pbar := pb.StartNew(len(reader.File))
for _, file := range reader.File {
var oval OVALDefinitions
rc, err := file.Open()
if err != nil {
return xerrors.Errorf("failed to open file: %w", err)
}
content, err := io.ReadAll(rc)
if err != nil {
rc.Close()
return xerrors.Errorf("failed to read file content: %w", err)
}

err = xml.Unmarshal(content, &oval)
if err != nil {
rc.Close()
return xerrors.Errorf("failed to unmarshal ALT's OVAL xml: %w", err)
}

ovalName := strings.TrimSuffix(file.Name, ".xml")
ovalPath := filepath.Join(c.VulnListDir, altDir, branch, ovalName)

if err := utils.WriteJSON(c.AppFs, ovalPath, "tests.json", oval.Tests); err != nil {
rc.Close()
return xerrors.Errorf("failed to write tests.json: %w", err)
}

if err := utils.WriteJSON(c.AppFs, ovalPath, "objects.json", oval.Objects); err != nil {
rc.Close()
return xerrors.Errorf("failed to write objects.json: %w", err)
}

if err = utils.WriteJSON(c.AppFs, ovalPath, "states.json", oval.States); err != nil {
rc.Close()
return xerrors.Errorf("failed to write states: %w", err)
}

if err = utils.WriteJSON(c.AppFs, ovalPath, "definitions.json", oval.Definitions); err != nil {
rc.Close()
return xerrors.Errorf("failed to write definitions: %w", err)
}

pbar.Increment()
rc.Close()
}

pbar.Finish()
return nil
}
61 changes: 61 additions & 0 deletions alt/alt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package alt

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConfig_Update(t *testing.T) {
testCases := []struct {
name string
dir string
wantFiles int
wantErr string
}{
{
name: "happy path",
dir: "testdata/happy",
},
{
name: "404",
dir: "testdata/missing-oval",
wantErr: "failed to get ALT's OVAL branch archive: failed to fetch URL: HTTP error. status code: 404, url:",
},
{
name: "broken XML",
dir: "testdata/broken",
wantErr: "failed to unmarshal ALT's OVAL xml: XML syntax error on line 4: element <cpe> closed by </cp>",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ts := httptest.NewServer(http.FileServer(http.Dir(tc.dir)))
defer ts.Close()

tmpDir := "/tmp" // It is a virtual filesystem of afero.
appFs := afero.NewMemMapFs()
c := Config{
VulnListDir: tmpDir,
BranchURL: ts.URL + "/%s/oval_definitions.zip",
BranchListURL: ts.URL + "/branches.json",
AppFs: appFs,
Retry: 0,
}

err := c.Update()
if tc.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.wantErr)
return
}

require.NoError(t, err, tc.name)
assert.NoError(t, err, tc.name)
})
}
}
1 change: 1 addition & 0 deletions alt/testdata/broken/branches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"length": 6, "branches": ["p9", "p10", "p11", "c9f2", "c10f1", "c10f2"]}
Binary file added alt/testdata/broken/p9/oval_definitions.zip
Binary file not shown.
1 change: 1 addition & 0 deletions alt/testdata/happy/branches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"length": 6, "branches": ["p9", "p10", "p11", "c9f2", "c10f1", "c10f2"]}
Binary file added alt/testdata/happy/c10f1/oval_definitions.zip
Binary file not shown.
Binary file added alt/testdata/happy/c10f2/oval_definitions.zip
Binary file not shown.
Binary file added alt/testdata/happy/c9f2/oval_definitions.zip
Binary file not shown.
Binary file added alt/testdata/happy/p10/oval_definitions.zip
Binary file not shown.
Binary file added alt/testdata/happy/p11/oval_definitions.zip
Binary file not shown.
Binary file added alt/testdata/happy/p9/oval_definitions.zip
Binary file not shown.
1 change: 1 addition & 0 deletions alt/testdata/missing-oval/branches.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"length": 6, "branches": ["p9", "p10", "p11", "c9f2", "c10f2", "c10f1"]}
Loading